前端自制接口抓取工具:一键收集并导出接口列表

在日常开发或者做接口文档的时候,我们经常会遇到这样一个场景:

👉 想要快速知道某个页面到底调用了哪些接口。

手动去 Network 面板 一个个筛选、复制,既繁琐又容易遗漏。于是,我写了一个小工具,可以在页面中自动劫持 fetch、XHR、axios 的请求,实时收集接口,并一键导出到 Excel 文件。

下面分享一下完整实现和思路。

功能目标

  1. 一键开启接口收集:点击按钮后,所有页面请求都会被记录下来。
  2. 过滤非接口请求 :过滤掉 js/css/png/jpg 等静态资源。
  3. 接口去重、排序:相同接口只保留一条,按请求方法和路径排序。
  4. 导出 Excel 文件:收集结果一键导出,便于做接口文档或测试。
  5. 清空记录:可以重新开始收集。

实现思路

核心思路其实很简单:

  • 通过劫持 fetchXMLHttpRequest.openaxios.request,在发出请求时把接口地址和方法存下来。
  • 提供一个小浮窗按钮,便于操作。
  • xlsx.js 生成 Excel 文件并下载。

核心代码

1. 注入 XLSX 库

用于导出 Excel 文件:

ini 复制代码
const xlsxScript = document.createElement("script");
xlsxScript.src = "https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.5/xlsx.full.min.js";
xlsxScript.onload = () => console.log("✅ XLSX 已加载");
document.head.appendChild(xlsxScript);

2. 按钮 UI

简单的三个按钮:开始收集、导出接口、清空记录。

ini 复制代码
function makeBtn(text, onclick) {
  const btn = document.createElement("button");
  btn.textContent = text;
  btn.style.cssText = `
    padding:6px 10px;background:#4CAF50;color:white;
    border:none;border-radius:4px;cursor:pointer;font-size:14px
  `;
  btn.onclick = onclick;
  return btn;
}

const container = document.createElement("div");
container.style = `
  position:fixed;top:10px;right:10px;z-index:999999;
  display:flex;flex-direction:column;gap:8px;
`;
container.appendChild(makeBtn("开始收集接口", () => { collecting = true; alert("✅ 开始收集接口,请点击页面操作产生请求"); }));
container.appendChild(makeBtn("导出接口列表", exportExcel));
container.appendChild(makeBtn("清空记录", () => { requests.length = 0; alert("✅ 已清空记录"); }));
document.body.appendChild(container);

3. 劫持请求

这里支持 fetchXHRaxios,保证覆盖大部分场景。

ini 复制代码
// 劫持 fetch
const originalFetch = window.fetch;
window.fetch = async (...args) => {
  if (collecting) {
    const [url, options] = args;
    const method = (options && options.method) || "GET";
    if (isApiRequest(url)) requests.push({ url, method });
  }
  return originalFetch.apply(this, args);
};

// 劫持 XHR
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
  if (collecting && isApiRequest(url)) {
    requests.push({ url, method });
  }
  return originalOpen.call(this, method, url, ...rest);
};

// 劫持 axios
if (window.axios) {
  const originalRequest = window.axios.request;
  window.axios.request = function(config) {
    if (collecting && isApiRequest(config.url)) {
      requests.push({ url: config.url, method: (config.method || "GET").toUpperCase() });
    }
    return originalRequest.call(this, config);
  };
}

4. 导出 Excel

利用 xlsx.js,将收集到的接口列表转成表格并下载。

ini 复制代码
function exportExcel() {
  if (!window.XLSX) return alert("❌ XLSX 库未加载,请稍等");

  const unique = new Set();
  requests.filter(r => isApiRequest(r.url)).forEach(({ url, method }) => {
    unique.add(normalize(url) + " | " + method.toUpperCase());
  });

  const rows = [["接口地址", "请求方式"]];
  Array.from(unique).sort().forEach(entry => {
    const [url, method] = entry.split(" | ");
    rows.push([url, method]);
  });

  const worksheet = XLSX.utils.aoa_to_sheet(rows);
  worksheet["!cols"] = [{ wch: 60 }, { wch: 10 }];

  const workbook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workbook, worksheet, "接口列表");

  const timestamp = new Date().toISOString().replace(/[:T]/g, "-").split(".")[0];
  XLSX.writeFile(workbook, `接口列表_${timestamp}.xlsx`);

  alert("✅ 已导出接口列表");
}

使用效果

  1. 把这段代码粘贴到控制台执行(或写成书签脚本)。
  2. 页面右上角会出现三个按钮。
  3. 点击 "开始收集接口" ,然后正常操作页面,产生接口请求。
  4. 点击 "导出接口列表" ,自动下载 Excel 文件,包含接口地址和请求方式。

总结

这个小工具的优点:

  • 不依赖后端,纯前端搞定。
  • 不用再一个个去 Network 面板 抄接口。
  • 导出的 Excel 可以直接用作 接口文档初稿测试用例依据

缺点:

  • 只能抓取页面 运行时产生的请求,没触发的接口不会被记录。
  • 部分接口如果通过 WebSocket 或其他协议传输,不会被收集。

但对于大部分前端页面的接口抓取需求,足够好用了。 🚀

完整代码复制到控制台即用

js 复制代码
(function() {
  // -------------------- 加载 XLSX 库 --------------------
  const xlsxScript = document.createElement("script");
  xlsxScript.src = "https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.5/xlsx.full.min.js";
  xlsxScript.onload = () => console.log("✅ XLSX 已加载");
  document.head.appendChild(xlsxScript);

  // -------------------- 创建按钮 --------------------
  const container = document.createElement("div");
  container.style.position = "fixed";
  container.style.top = "10px";
  container.style.right = "10px";
  container.style.zIndex = 999999;
  container.style.display = "flex";
  container.style.flexDirection = "column";
  container.style.gap = "8px";

  let collecting = false;
  const requests = [];

  function makeBtn(text, onclick) {
    const btn = document.createElement("button");
    btn.textContent = text;
    btn.style.padding = "6px 10px";
    btn.style.background = "#4CAF50";
    btn.style.color = "white";
    btn.style.border = "none";
    btn.style.borderRadius = "4px";
    btn.style.cursor = "pointer";
    btn.style.fontSize = "14px";
    btn.onclick = onclick;
    return btn;
  }

  container.appendChild(makeBtn("开始收集接口", () => {
    collecting = true;
    alert("✅ 开始收集接口,请点击页面操作产生请求");
  }));

  container.appendChild(makeBtn("导出接口列表", exportExcel));
  container.appendChild(makeBtn("清空记录", () => {
    requests.length = 0;
    alert("✅ 已清空记录");
  }));

  document.body.appendChild(container);

  // -------------------- 工具函数 --------------------
  function normalize(url) {
    try {
      const u = new URL(url, location.origin);
      const path = u.pathname;
      const idx = path.indexOf("api/");
      return idx >= 0 ? path.slice(idx + 4) : path;
    } catch {
      const path = url.split("?")[0];
      const idx = path.indexOf("api/");
      return idx >= 0 ? path.slice(idx + 4) : path;
    }
  }

  function isApiRequest(url) {
    // 过滤掉静态资源
    return !url.match(/\.(js|json|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|map)(\?|$)/i);
  }

  function exportExcel() {
    if (!window.XLSX) {
      alert("❌ XLSX 库未加载,请稍等");
      return;
    }
    const filtered = requests.filter(r => isApiRequest(r.url));
    if (filtered.length === 0) {
      alert("⚠️ 没有接口记录,请先点击页面操作产生接口");
      return;
    }

    const unique = new Set();
    filtered.forEach(({ url, method }) => {
      const key = normalize(url) + " | " + method.toUpperCase();
      unique.add(key);
    });

    // 排序:先按 method,再按 url
    const methodOrder = { GET: 1, POST: 2, PUT: 3, DELETE: 4 };
    const sorted = Array.from(unique).sort((a, b) => {
      const [urlA, methodA] = a.split(" | ");
      const [urlB, methodB] = b.split(" | ");
      const orderA = methodOrder[methodA] || 99;
      const orderB = methodOrder[methodB] || 99;
      if (orderA === orderB) return urlA.localeCompare(urlB);
      return orderA - orderB;
    });

    const rows = [["接口地址", "请求方式"]];
    sorted.forEach(entry => {
      const [url, method] = entry.split(" | ");
      rows.push([url, method]);
    });

    const worksheet = XLSX.utils.aoa_to_sheet(rows);

    // 设置列宽
    worksheet["!cols"] = [{ wch: 60 }, { wch: 10 }];

    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, "接口列表");

    // 文件名带时间戳
    const timestamp = new Date().toISOString().replace(/[:T]/g, "-").split(".")[0];
    XLSX.writeFile(workbook, `接口列表_${timestamp}.xlsx`);

    alert("✅ 已导出接口列表");
  }

  // -------------------- 劫持 fetch --------------------
  const originalFetch = window.fetch;
  window.fetch = async (...args) => {
    if (collecting) {
      try {
        const [url, options] = args;
        const method = (options && options.method) || "GET";
        if (isApiRequest(url)) requests.push({ url, method });
      } catch (e) {}
    }
    return originalFetch.apply(this, args);
  };

  // -------------------- 劫持 XHR --------------------
  const originalOpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function(method, url, ...rest) {
    if (collecting) {
      try {
        if (isApiRequest(url)) requests.push({ url, method });
      } catch (e) {}
    }
    return originalOpen.call(this, method, url, ...rest);
  };

  // -------------------- 劫持 axios --------------------
  if (window.axios) {
    const originalRequest = window.axios.request;
    window.axios.request = function(config) {
      if (collecting) {
        try {
          if (isApiRequest(config.url))
            requests.push({ url: config.url, method: (config.method || "GET").toUpperCase() });
        } catch (e) {}
      }
      return originalRequest.call(this, config);
    };
  }

  console.log("✅ 接口收集工具已注入页面上下文,XLSX 库正在加载");
})();
相关推荐
wayne2142 小时前
跨平台开发框架全景分析:Flutter、RN、KMM 与腾讯 Kuikly 谁更值得选择?
前端
LuckySusu2 小时前
【js篇】JavaScript 对象创建的 6 种方式:从基础到高级
前端·javascript
LuckySusu2 小时前
【js篇】async/await 的五大核心优势:让异步代码像同步一样清晰
前端·javascript
艾雅法拉拉2 小时前
JS知识点回顾(1)
前端·javascript·面试
LuckySusu2 小时前
【js篇】Promise 解决了什么问题?—— 彻底告别“回调地狱”
前端·javascript
程序员海军2 小时前
如何让AI真正理解你的需求
前端·后端·aigc
passer9812 小时前
基于Vue的场景解决
前端·vue.js
用户458203153172 小时前
CSS过渡(Transition)详解:创建平滑状态变化
前端·css
春秋半夏2 小时前
本地项目一键开启 HTTPS(mkcert + Vite / Vue 配置教程)
前端