如何用Chrome扩展监听Next.js网站的Fetch请求响应?
如何用Chrome Extension监听Fetch请求并获取响应数据
肯定可以实现,你之前的fetch替换方案没生效,主要是因为注入时机不对、没覆盖到所有执行上下文,或者页面里有提前加载的fetch调用。下面给两种靠谱的解决办法:
方案一:优化Fetch钩子注入(内容脚本方式)
要让你的钩子能抓到所有页面的fetch请求,得解决注入时机和作用域的问题:
- 提前注入脚本
在manifest.json里配置内容脚本时,设置run_at: "document_start",确保脚本在页面其他资源加载前就跑起来,同时加上all_frames: true覆盖所有iframe:
{ "content_scripts": [ { "matches": ["*://目标域名/*"], "js": ["inject-fetch-hook.js"], "run_at": "document_start", "all_frames": true } ] }
- 修复钩子的响应体处理
你原来的代码里,读取response.text()会把响应体消耗掉,虽然重新造了个Response,但如果页面后续要读json格式就会出错。正确的做法是先克隆响应体再读取:
// inject-fetch-hook.js (function() { const originalFetch = window.fetch; window.fetch = async function(input, init) { const response = await originalFetch(input, init); // 克隆响应体,避免原响应被用掉 const clonedResponse = response.clone(); // 先尝试读JSON,失败就读文本 try { const data = await clonedResponse.json(); console.log('抓到Fetch响应:', input, data); // 可以把数据发给后台脚本 chrome.runtime.sendMessage({ type: 'FETCH_RESPONSE', url: input, data }); } catch (e) { const text = await clonedResponse.text(); console.log('抓到非JSON响应:', input, text); } return response; }; })();
- 突破隔离世界限制
Chrome的内容脚本是在隔离环境里跑的,直接替换的window.fetch管不到页面自己的脚本(页面脚本在另一个上下文)。这时候得把钩子代码直接注入到页面主上下文:
// inject-fetch-hook.js function injectHook() { const script = document.createElement('script'); script.textContent = ` (function() { const originalFetch = window.fetch; window.fetch = async function(input, init) { const response = await originalFetch(input, init); const clonedResponse = response.clone(); // 读取响应后通过自定义事件发给内容脚本 clonedResponse.json().then(data => { window.dispatchEvent(new CustomEvent('fetchCaptured', { detail: { url: input, data } })); }).catch(e => { clonedResponse.text().then(text => { window.dispatchEvent(new CustomEvent('fetchCaptured', { detail: { url: input, text } })); }); }); return response; }; })(); `; document.documentElement.appendChild(script); script.remove(); } // 监听页面发来的捕获事件 window.addEventListener('fetchCaptured', (event) => { console.log('内容脚本收到数据:', event.detail); // 转发给后台脚本 chrome.runtime.sendMessage({ type: 'FETCH_DATA', payload: event.detail }); }); injectHook();
这么做能确保钩子跑在页面自己的上下文里,所有页面脚本的fetch调用都能被抓到。
方案二:用Chrome WebRequest API(后台脚本方式)
如果只是想监听响应、不需要改请求,可以用Chrome的webRequest API。注意:这个API默认只能拿到响应头,要拿响应体得额外处理:
- 先在manifest里加权限
{ "permissions": ["webRequest", "webRequestBlocking", "*://目标域名/*"], "background": { "service_worker": "background.js" } }
- 后台脚本里监听并获取响应体
// background.js chrome.webRequest.onResponseStarted.addListener( function(details) { // 只处理JSON或文本类型的响应 const isTextOrJson = details.responseHeaders.some(h => h.name.toLowerCase() === 'content-type' && (h.value.includes('application/json') || h.value.includes('text/')) ); if (isTextOrJson) { // 重新发起请求拿响应体(注意:会多一次请求,适合无签名的普通请求) fetch(details.url, { headers: details.requestHeaders, credentials: 'include' }).then(res => res.json()).then(data => { console.log('WebRequest抓到响应:', details.url, data); }); } }, { urls: ["*://目标域名/*"] }, ["responseHeaders", "requestHeaders"] );
提醒:这种方式会发起二次请求,如果请求有一次性token或签名,二次请求可能失败。要是不想发二次请求,可以用chrome.debugger API,但配置比较复杂,适合进阶场景。
你的原方案为啥失效?
- 注入太晚:页面脚本可能在你的钩子生效前就调用了fetch
- 没覆盖全:没处理iframe或者页面主上下文的隔离问题
- 响应体处理错了:原代码读text()后返回新Response,页面后续要读json()会因为响应体被消耗而报错,还可能漏抓一些请求
内容的提问来源于stack exchange,提问作者Rob
相关产品推荐
相关产品推荐

