为什么用户脚本无法拦截YouTube页面的部分请求?
为什么用户脚本无法拦截YouTube页面的部分请求?
提问内容
类似[相关问题],我写了一个用户脚本来拦截fetch和XMLHttpRequest请求并在控制台打印URL,但它没能拦截所有请求——比如以下两个资源的请求就没有出现在日志里:
https://www.youtube.com/s/desktop/508deff1/img/logos/favicon.icohttps://tpc.googlesyndication.com/sodar/56-y-0RG.js
我的脚本代码如下:
// ==UserScript== // @name YouTube Urls // @namespace http://tampermonkey.net/ // @version 0.1 // @description URLs on YouTube. // @author me // @match https://www.youtube.com/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // intercept fetch requests const originalFetch = window.fetch; window.fetch = function(url, options) { console.log(url) return originalFetch(url, options); }; // intercept XMLHttpRequest const originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, async, user, password) { console.log(url) return originalXhrOpen.apply(this, arguments); }; })();
这两个资源都不是预加载的:
编辑补充:
以下是这几个请求的调用栈信息:
第一个请求的调用栈:
(anonymous) @desktop_polymer.js:7320 zla @base.js:1425 nwa @base.js:2869 mya @base.js:3155 g.k.xz @base.js:9573 g.k.startRendering @base.js:9520 . . . g.k.loadVideoByPlayerVars @base.js:10439 g.k.saa @base.js:10612 UR.a.state.j.<computed> @base.js:4605 (anonymous) @desktop_polymer.js:9647 (anonymous) @desktop_polymer.js:18592 acquireApi_ @desktop_polymer.js:18592 (anonymous) @desktop_polymer.js:3853 (anonymous) @desktop_polymer.js:11370 Ov.resolveCommand @desktop_polymer.js:5622 fmb @desktop_polymer.js:7712 onEndpointClick_ @desktop_polymer.js:7719 (anonymous) @desktop_polymer.js:3853
第二个请求的调用栈截图:
第三个请求的调用栈:
eval @ VM118:1 gy @ VM110:1 t7 @ VM114:1 eval @ 5k7CCto5.html:1 Oa @ 5k7CCto5.html:1 M5 @ 5k7CCto5.html:1 X @ 5k7CCto5.html:1 eval @ 5k7CCto5.html:1 requestIdleCallback.timeout @ 5k7CCto5.html:1 requestIdleCallback R3 @ 5k7CCto5.html:1 M5 @ 5k7CCto5.html:1 X @ 5k7CCto5.html:1 eval @ 5k7CCto5.html:1 requestIdleCallback.timeout @ 5k7CCto5.html:1 requestIdleCallback R3 @ 5k7CCto5.html:1 M5 @ 5k7CCto5.html:1 X @ 5k7CCto5.html:1 eval @ 5k7CCto5.html:1 requestIdleCallback.timeout @ 5k7CCto5.html:1 requestIdleCallback R3 @ 5k7CCto5.html:1 M5 @ 5k7CCto5.html:1 X @ 5k7CCto5.html:1 eval @ 5k7CCto5.html:1 requestIdleCallback.timeout @ 5k7CCto5.html:1 requestIdleCallback R3 @ 5k7CCto5.html:1 M5 @ 5k7CCto5.html:1 X @ 5k7CCto5.html:1 eval @ 5k7CCto5.html:1 requestIdleCallback.timeout @ 5k7CCto5.html:1 requestIdleCallback R3 @ 5k7CCto5.html:1 M5 @ 5k7CCto5.html:1 X @ 5k7CCto5.html:1 . . . Promise.then C @ 5k7CCto5.html:13 D @ 5k7CCto5.html:13 db @ 5k7CCto5.html:19 E @ 5k7CCto5.html:18 (anonymous) @ 5k7CCto5.html:13 (anonymous) @ 5k7CCto5.html:41 (anonymous) @ 5k7CCto5.html:37
我的回答
咱们一步步拆解这个问题,你的脚本只覆盖了fetch和XMLHttpRequest.open,但有些请求没被捕获,主要是这几个原因:
1. 部分请求不走fetch/XMLHttpRequest通道
- 浏览器自动发起的请求:比如
favicon.ico,这是浏览器为加载网站图标自动发起的请求,完全不经过JS层面的fetch或XHR接口,你的拦截代码根本碰不到它。 - 第三方上下文的请求:像
tpc.googlesyndication.com的广告脚本,大概率是在隔离的iframe或沙箱环境里运行的。你的脚本只匹配了youtube.com/*,没法侵入第三方域名的JS上下文,自然抓不到那边的请求调用。 - Service Worker接管的请求:如果YouTube用了Service Worker处理资源请求,这些请求会直接走Service Worker的独立上下文,不会经过你在主页面替换的
fetch方法。
2. 你的fetch拦截没处理Request对象的情况
你现在的代码直接console.log(url),但如果页面里的代码调用fetch时传的是Request实例(而非字符串URL),控制台只会输出一个Request对象,不是直观的URL字符串,你可能会误以为没拦截到。
优化后的解决方案
针对这些问题,给你两个优化方向:
方案一:完善JS发起请求的拦截逻辑
修改fetch拦截来支持Request对象,同时加上对其他请求API的拦截:
// ==UserScript== // @name YouTube Urls // @namespace http://tampermonkey.net/ // @version 0.2 // @description URLs on YouTube. // @author me // @match https://www.youtube.com/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // 处理fetch请求,兼容Request对象 const originalFetch = window.fetch; window.fetch = function(input, options) { // 解析真实URL:如果是Request实例就取url属性,否则直接用字符串 const requestUrl = typeof input === 'string' ? input : input.url; console.log('[Fetch]', requestUrl); return originalFetch(input, options); }; // 拦截XMLHttpRequest const originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, async, user, password) { console.log('[XHR]', url); return originalXhrOpen.apply(this, arguments); }; // 拦截navigator.sendBeacon请求 const originalSendBeacon = navigator.sendBeacon; navigator.sendBeacon = function(url, data) { console.log('[Beacon]', url); return originalSendBeacon.apply(this, arguments); }; })();
方案二:用PerformanceObserver捕获所有资源请求
如果想无差别捕获所有资源加载(包括浏览器自动发起、第三方脚本、Service Worker处理的请求),PerformanceObserver是更靠谱的选择——它能监听页面上所有的资源加载事件,不管请求是怎么发起的:
// ==UserScript== // @name YouTube Urls // @namespace http://tampermonkey.net/ // @version 0.2 // @description URLs on YouTube. // @author me // @match https://www.youtube.com/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // 监听所有资源加载事件 const resourceObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'resource') { console.log('[所有资源]', entry.name); } } }); // 启动观察者 resourceObserver.observe({ entryTypes: ['resource'] }); // 保留原有的JS请求拦截,方便查看细节 const originalFetch = window.fetch; window.fetch = function(input, options) { const requestUrl = typeof input === 'string' ? input : input.url; console.log('[JS Fetch]', requestUrl); return originalFetch(input, options); }; const originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, async, user, password) { console.log('[JS XHR]', url); return originalXhrOpen.apply(this, arguments); }; })();
额外说明
- 对于第三方iframe的请求,除非你给脚本添加对应的
@match规则(比如*://tpc.googlesyndication.com/*),否则没法直接拦截它的JS上下文——不过浏览器的跨域安全策略会限制这种操作,所以用PerformanceObserver是更稳妥的方式。 - 如果你怀疑Service Worker在干扰,可以在Chrome开发者工具的
Application面板里临时禁用Service Worker,再测试脚本效果。
备注:内容来源于stack exchange,提问作者user2495207
相关产品推荐
相关产品推荐

