ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Chrome插件监听网络请求实战:background.js捕获真实流量

Chrome插件监听网络请求实战:background.js捕获真实流量 简介本资源是一份面向Web前端开发者与调试初学者的Chrome插件开发实践资料聚焦网络请求监听这一核心调试能力解决异步请求XHR/Fetch捕获、分析与自动化收集的实际问题。压缩包共10个文件含5个JavaScript文件含background.js、popup.js、main.js等核心逻辑、1个HTML页面popup.html、1个JSON清单文件manifest.json、1个PNG图标及1个README说明文档总大小仅48KB轻量易部署适合快速上手插件结构与网络事件监听机制。已有2139人学习下载体现了开发者对浏览器调试工具进阶应用的持续关注。读者可直接复用插件框架理解popup与background通信、chrome.webRequest API监听原理并掌握如何结构化采集请求URL、方法、响应状态等关键字段为性能监控、API行为审计或教学演示提供可定制的技术原型。1. 谷歌插件学习监听网络请求不是调开发者工具而是用 background.js 主动捕获真实请求流你有没有试过在 Chrome 开发者工具的 Network 面板里反复刷新、筛选、点开每个 XHR 查响应体结果发现——某个关键接口明明前端代码里写了 fetchNetwork 却压根没记录或者接口被 Service Worker 拦截后绕过 Network 面板调试直接失明这时候光靠「打开 F12 → 切到 Network → 点刷新」这套操作已经不够用了。真正的「监听网络请求」是让插件自己变成浏览器底层的“流量守门人”在 request 发出前、response 返回后甚至在 fetch/XHR 构造阶段就介入——这正是plug-feedback-collect.zip这个实战包的价值所在。它不教你怎么点按钮而是给你一套可复现、可调试、可嵌入业务逻辑的插件级监听方案用background.js基于chrome.webRequestAPI 捕获原始请求用popup.html popup.js实时展示过滤后的关键字段URL、method、status、耗时再通过pageScripts/main.js在页面上下文注入钩子补全 fetch/XHR 的 body 和 headers。这不是玩具 demo而是我在某电商大促监控项目里落地过的最小可行架构——能稳定抓取加密接口的原始 payload能区分重定向链中的真实目标 URL还能把请求日志导出为 JSON 供后续分析。适合正在做前端性能埋点、API 合规审计、或需要绕过 Network 面板盲区的中高级前端/测试/运维工程师。2. 从 manifest.json 到 background.js插件监听能力的底层契约与事件注册逻辑2.1 manifest.json 的权限声明为什么必须包含 webRequest 和 webRequestBlockingmanifest.json是整个插件的宪法它的权限配置直接决定你能监听什么、能改什么。打开plug-feedback-collect/manifest.json你会看到这两行关键声明permissions: [ activeTab, storage, webRequest, webRequestBlocking, https://*/*, http://*/* ],注意webRequestBlocking权限不是可选的——没有它你只能「观察」请求onBeforeRequest仅触发无法修改而无法实现「拦截重放」「动态改 header」「阻断恶意请求」等高阶动作。但代价是Chrome 会强制要求用户在安装时看到「此扩展可读取和更改您访问的网站的数据」的红色警告。这是安全机制不是 bug。https://*/*和http://*/*是 host permissions它们告诉浏览器“我需要监听所有网站的请求”。但这里有个血泪经验如果你只写*://*/*Chrome 88 会拒绝加载插件报错Invalid host permission。必须显式拆成https和http两行——这是 Manifest V3 的硬性要求老教程里常见的通配写法已失效。2.2 background.js 的核心监听链从 onBeforeRequest 到 onCompleted 的完整生命周期background.js是插件的后台常驻脚本它不依赖页面加载只要浏览器开着就一直运行。plug-feedback-collect的监听逻辑集中在chrome.webRequest.onBeforeRequest.addListener和chrome.webRequest.onCompleted.addListener两个事件上。我们来拆解一段典型代码// background.js 关键片段 chrome.webRequest.onBeforeRequest.addListener( (details) { // 1. 记录请求发起时间戳 const startTime Date.now(); // 2. 提取关键字段避免直接存 details 对象防止循环引用 const logEntry { id: details.requestId, url: details.url, method: details.method, tabId: details.tabId, timeStamp: startTime, // 注意headers 在 onBeforeRequest 中不可读除非声明 extraHeaders 权限 // 所以这里只存基础信息body 和 headers 留给 pageScripts 补充 }; // 3. 存入内存缓存实际项目建议用 chrome.storage.local requestCache.set(details.requestId, logEntry); }, { urls: [all_urls] }, // 必须匹配否则事件不触发 [requestBody] // 这个 extraInfoSpec 是关键没有它details.requestBody 为空 );urls: [all_urls]是匹配模式all_urls是 Manifest V3 中唯一允许的全局通配符替代了旧版的[*://*/*]。[requestBody]是extraInfoSpec数组它决定了details对象里有哪些额外字段可用。常见值有requestBody获取 POST/PUT 请求的原始 body需配合webRequestBlocking权限responseHeaders在onHeadersReceived中读取响应头blocking启用同步阻塞允许返回{ cancel: true }中断请求。参数说明details.requestId是 Chrome 分配的唯一 ID贯穿整个请求生命周期details.tabId可用于关联到具体标签页details.method是 HTTP 方法GET/POST/PUT/DELETE但注意fetch API 的method默认大写而 XHR 的method可能小写统一转大写再比较更稳妥。2.3 为什么需要 pageScripts/main.js补全 Network 面板看不到的请求细节background.js能捕获请求 URL、method、status但有两个致命盲区fetch/XHR 的 request bodywebRequestAPI 在onBeforeRequest中拿到的details.requestBody是原始二进制流解析成本高且不保证编码正确自定义 headers比如Authorization: Bearer xxx或X-Trace-ID这些由 JS 动态设置的 header在webRequest的onBeforeRequest中默认不可见需额外声明extraHeaders权限且仅对部分 header 有效。plug-feedback-collect的解法是双线程协作background.js负责捕获请求骨架pageScripts/main.js注入到页面 DOM 中劫持原生fetch和XMLHttpRequest// pageScripts/main.js 片段 const originalFetch window.fetch; window.fetch function(...args) { const [input, init] args; const url typeof input string ? input : input.url || ; // 1. 提取 body支持 Blob/FormData/URLSearchParams let body null; if (init init.body) { if (init.body instanceof Blob) { body Blob(size init.body.size ); } else if (init.body instanceof FormData) { body FormData(entries init.body.entries.length ); } else { body String(init.body).substring(0, 500); // 截断防爆内存 } } // 2. 提取 headers手动遍历 Headers 对象 const headers {}; if (init init.headers) { if (init.headers instanceof Headers) { for (let [key, value] of init.headers) { headers[key] value; } } else if (typeof init.headers object) { Object.assign(headers, init.headers); } } // 3. 发送补充数据到 background chrome.runtime.sendMessage({ type: FETCH_LOG, url, method: init?.method || GET, headers, body, timestamp: Date.now() }); return originalFetch.apply(this, args); };这个劫持逻辑覆盖了 95% 的现代前端请求场景。但要注意它只对当前页面生效且不能捕获 iframe 内的请求需单独注入 iframe。这也是为什么auto.js存在——它负责自动检测并注入main.js到所有 frame。3. popup.html 与 popup.js把原始请求数据变成可交互的调试界面3.1 popup.html 的极简结构为什么不用 React/Vue而用纯 HTML CSSpopup.html是点击插件图标弹出的小窗口它的设计哲学是「快、轻、稳」。plug-feedback-collect的结构极其克制!-- popup.html -- !DOCTYPE html html head meta charsetutf-8 titleFeedback Collector/title link relstylesheet hrefpopup.css /head body div classheader h2请求监控/h2 button idclearBtn清空/button /div div classfilter input typetext idurlFilter placeholderURL 包含... select idmethodFilter option value全部方法/option option valueGETGET/option option valuePOSTPOST/option option valueFETCHFETCH/option /select /div div classlist idrequestList/div script srcpopup.js/script /body /html零框架依赖不引入任何构建工具或打包流程直接用chrome.runtime.sendMessage与 background 通信。这样做的好处是启动速度 100ms调试时修改 HTML/CSS 立即生效不会因 Webpack HMR 失效而卡住。CSS 定位用position: absolute而非 Flex/GridPopup 窗口尺寸固定通常 300x500px绝对定位能避免浏览器渲染引擎在小尺寸下对 Flex 的兼容性问题尤其在旧版 Chrome 上。3.2 popup.js 的双向通信如何用 sendMessage/receiveMessage 实现低延迟刷新popup.js的核心任务是1初始化时拉取 background 缓存的请求列表2监听 background 的新增请求广播3响应用户过滤操作。关键代码如下// popup.js let currentRequests []; // 1. 初始化从 background 获取历史请求 chrome.runtime.sendMessage({ type: GET_HISTORY }, (response) { if (response response.requests) { currentRequests response.requests; renderList(); } }); // 2. 监听 background 的实时推送使用 onMessage非 onMessageExternal chrome.runtime.onMessage.addListener((request, sender, sendResponse) { if (request.type NEW_REQUEST) { currentRequests.push(request.data); // 限制最大条数防内存溢出 if (currentRequests.length 100) { currentRequests.shift(); } renderList(); } }); // 3. 渲染函数用 innerHTML 而非 DOM API性能敏感 function renderList() { const listEl document.getElementById(requestList); const filterUrl document.getElementById(urlFilter).value; const filterMethod document.getElementById(methodFilter).value; const filtered currentRequests.filter(item { const matchUrl !filterUrl || item.url.includes(filterUrl); const matchMethod !filterMethod || (filterMethod FETCH ? item.isFetch : item.method filterMethod); return matchUrl matchMethod; }); listEl.innerHTML filtered.map(item div classitem ${item.status 400 ? error : } div classurl${item.url.substring(0, 60)}${item.url.length 60 ? ... : }/div div classmeta span classmethod${item.method}/span span classstatus${item.status || -}/span span classtime${item.duration}ms/span /div div classheaders${JSON.stringify(item.headers || {}, null, 2)}/div /div ).join(); }chrome.runtime.onMessage是 popup 与 background 通信的唯一可靠通道。注意sender.id在 popup 场景下为undefined所以不要依赖它做身份校验。renderList()用innerHTML是经过实测的最优解当列表超过 50 条时用document.createElement循环创建节点比innerHTML慢 3~5 倍Chrome DevTools Performance 面板可验证。3.3 过滤与导出功能如何把调试数据变成可分析的结构化输出popup.js不只是展示更要支持二次分析。plug-feedback-collect提供了两个实用功能URL 模糊搜索输入api/user自动高亮所有匹配的请求无需正则降低使用门槛JSON 导出按钮点击后生成带时间戳的文件内容为过滤后的请求数组。document.getElementById(exportBtn).addEventListener(click, () { const now new Date().toISOString().replace(/[:.]/g, -); const blob new Blob([JSON.stringify(currentRequests, null, 2)], { type: application/json }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download requests-${now}.json; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); });参数说明JSON.stringify(obj, null, 2)的第三个参数2表示缩进 2 空格保证导出文件可读URL.createObjectURL是浏览器原生 API比FileSaver.js更轻量且无兼容性问题。4. 避坑指南五个让新手当场翻车的真实问题与解决方案4.1 现象background.js 里chrome.webRequest.onBeforeRequest.addListener完全不触发原因Manifest V3 中webRequest相关事件必须在service_worker字段声明的脚本中注册而plug-feedback-collect使用的是传统background字段background: { scripts: [background.js], persistent: true }。Chrome 91 已废弃persistent: true但plug-feedback-collect仍兼容旧版。若你在 Manifest V3 项目中直接复制此结构事件必然失效。解决确认你的manifest.json是 V2 还是 V3。V3 必须改用service_worker且background.js需重命名为sw.js并在其中用chrome.runtime.onInstalled触发监听注册// sw.jsManifest V3 chrome.runtime.onInstalled.addListener(() { chrome.webRequest.onBeforeRequest.addListener( /* ... */ ); });4.2 现象pageScripts/main.js注入后fetch 请求 body 显示为[object Blob]或undefined原因fetch的body参数可能是Blob、FormData、URLSearchParams或ReadableStream而String()强转对Blob和FormData返回[object Blob]对ReadableStream直接报错。解决按类型分别处理。plug-feedback-collect的main.js已预置判断逻辑但需注意FormData的entries属性在某些旧版 Chrome 中不可枚举应改用for (let pair of formData.entries())迭代。4.3 现象popup 界面打开后空白控制台报错Refused to load the script popup.js原因Chrome 严格限制 inline script 和未声明的资源加载。popup.html中若存在scriptconsole.log(test)/script或未在manifest.json的web_accessible_resources中声明popup.js就会被拦截。解决检查manifest.json是否包含web_accessible_resources: [ { resources: [popup.js, popup.css], matches: [all_urls] } ]且确保popup.html中所有 script 标签均为外链script srcpopup.js/script禁用内联脚本。4.4 现象监听到的details.url是 data URL 或 chrome-extension:// 开头而非目标 API 地址原因webRequest会捕获所有请求包括插件自身资源如chrome-extension://abc123/icon.png、data URLbase64 图片、以及被重定向后的最终 URL。plug-feedback-collect默认不过滤需手动筛除。解决在onBeforeRequest回调中添加白名单判断if (details.url.startsWith(data:) || details.url.startsWith(chrome-extension://) || details.url.includes(google.com) || // 排除 Google 自身请求 details.url.includes(gstatic.com)) { return; }4.5 现象同一请求在 Network 面板显示 200但插件捕获的details.statusCode为 0原因details.statusCode仅在onCompleted事件中有效且仅对成功完成的请求有值若请求被onBeforeRequest中断{ cancel: true }或发生 DNS 错误、连接超时statusCode就是 0。解决不要依赖onBeforeRequest中的statusCode它不存在改用onCompleted事件获取真实状态码并结合details.error字段判断失败类型chrome.webRequest.onCompleted.addListener( (details) { console.log(Status: ${details.statusCode}, Error: ${details.error}); }, { urls: [all_urls] } );5. 进阶技巧用 auto.js 实现全自动注入与跨域请求捕获5.1 auto.js 的工作原理如何绕过 CSP 限制注入 main.jspageScripts/auto.js是整个插件的“隐形推手”。它不直接处理请求而是负责在页面 DOM Ready 后动态创建script标签并插入main.js。关键在于它利用了 Chrome 插件的特权即使目标网站启用了Content-Security-Policy: script-src self插件注入的脚本仍能执行因为它是通过chrome.scripting.executeScriptV3或chrome.tabs.executeScriptV2API 注入的不受页面 CSP 约束。// auto.js chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) { if (changeInfo.status complete tab.url) { // 1. 检查是否为有效网页排除 chrome://、about:blank if (tab.url.startsWith(http) || tab.url.startsWith(https)) { // 2. 注入 main.jsV2 写法 chrome.tabs.executeScript(tabId, { file: pageScripts/main.js, runAt: document_idle // 等 DOM 加载完再注入 }); } } });runAt: document_idle是黄金参数它确保main.js在 DOM 构建完成、但window.onload未触发前执行此时fetch和XMLHttpRequest已被原生定义劫持成功率 100%。若目标页面使用了Shadow DOMmain.js默认无法穿透。此时需在auto.js中追加注入逻辑chrome.tabs.executeScript(tabId, { code: const shadowRoot document.querySelector(your-shadow-host)?.shadowRoot; if (shadowRoot) { const script document.createElement(script); script.src chrome.runtime.getURL(pageScripts/main.js); shadowRoot.appendChild(script); } , runAt: document_idle });5.2 表格跨域请求捕获能力对比fetch / XHR / img / iframe请求类型webRequest是否捕获pageScripts/main.js是否捕获需要额外权限备注fetch()✅所有域名✅同源 跨域无main.js可读取 body 和 headersXMLHttpRequest✅所有域名✅同源 跨域无需重写open()和send()方法img src...✅所有域名❌无webRequest可捕获但无法获取 referrer 或触发 JS 逻辑iframe src...✅所有域名⚠️仅主 frame无auto.js需递归注入到每个 iframenavigator.sendBeacon()✅所有域名❌无webRequest可捕获但sendBeacon的 body 无法通过requestBody获取提示navigator.sendBeacon()是埋点常用 API它发送请求时不等待响应webRequest能捕获其 URL 和 method但details.requestBody为空。若需 body只能在 Beacon 发起前由业务代码主动上报。5.3 实战技巧用 chrome.storage.local 替代内存缓存实现重启不丢数据plug-feedback-collect默认用Map对象缓存请求但浏览器重启后数据全丢。生产环境必须持久化。chrome.storage.local是最佳选择但它有 5MB 限额和异步 API需改造background.js// 替换原来的 requestCache new Map() async function saveRequest(logEntry) { try { const data await chrome.storage.local.get([requests]); const requests data.requests || []; requests.push(logEntry); // 限制存储条数防爆仓 if (requests.length 1000) { requests.splice(0, requests.length - 1000); } await chrome.storage.local.set({ requests }); } catch (e) { console.error(Save failed:, e); } } // 在 onBeforeRequest 回调中调用 chrome.webRequest.onBeforeRequest.addListener( (details) { saveRequest({ id: details.requestId, url: details.url, method: details.method, timestamp: Date.now() }); }, { urls: [all_urls] }, [requestBody] );chrome.storage.local的读写是异步的不能用await在事件回调中阻塞所以saveRequest内部用try/catch处理错误失败不影响主流程。每次set都是全量覆盖因此先get再push再set是唯一安全模式。从那以后我每次写插件监听逻辑都强制走一遍chrome.storage.local的读写链路压测——用chrome.storage.local.set({ test: new Array(1000000).fill(x) })模拟大数据写入看是否会触发QUOTA_BYTES_PER_ITEM错误。只有扛过这一关才敢说这个监听方案能上生产。希望帮到你。本文还有配套的精品资源点击获取
返回列表