油猴脚本进阶如何精准拦截B站、知乎等特定网站的Fetch请求而不‘误伤’在浏览器自动化工具中油猴脚本Tampermonkey因其轻量级和灵活性备受开发者青睐。特别是对于需要拦截和修改网页请求的中高级用户来说精准控制脚本的作用范围至关重要——一个设计不当的拦截逻辑可能导致目标网站功能异常甚至影响其他无关页面的正常运行。本文将深入探讨如何构建一套健壮的拦截机制确保脚本仅在预设条件下触发。1. 理解Fetch拦截的核心原理浏览器中的Fetch API是现代Web应用进行网络请求的主要方式。要拦截这些请求油猴脚本通常采用覆盖原生fetch函数的方式。但这种暴力替换存在明显缺陷它会全局生效影响所有页面的所有请求。关键问题在于作用域控制。油猴脚本默认运行在隔离的沙盒环境中但通过unsafeWindow可以访问页面的全局对象。以下是一个基础拦截示例// UserScript // run-at document-start // grant unsafeWindow // /UserScript (function() { const originalFetch unsafeWindow.fetch; unsafeWindow.fetch function(...args) { console.log(拦截到请求:, args[0]); return originalFetch.apply(this, args); }; })();这种简单实现会记录所有fetch请求但明显缺乏针对性。我们需要建立更精细的过滤机制。2. 构建精准拦截的三层过滤体系2.1 URL匹配第一道防线最直接的过滤方式是通过请求URL识别目标。但简单的字符串包含如indexOf容易产生误判。更可靠的做法是使用URL对象解析完整路径正则表达式精确匹配域名和路径考虑参数变化的影响function shouldIntercept(url) { const parsedUrl new URL(url); // 精确匹配B站视频域名 if(/^(www\.)?bilibili\.com$/.test(parsedUrl.hostname)) { return true; } // 匹配知乎特定API路径 if(parsedUrl.hostname www.zhihu.com parsedUrl.pathname.startsWith(/api/v4/answers)) { return true; } return false; }2.2 请求特征验证第二层确认仅靠URL匹配还不够还需要检查请求的其他特征检查项示例作用请求方法GET/POST区分读写操作请求头Content-Type识别数据格式请求体JSON结构验证具体内容unsafeWindow.fetch async function(input, init) { const reqUrl typeof input string ? input : input.url; if(shouldIntercept(reqUrl)) { const reqMethod (init?.method || GET).toUpperCase(); // 只拦截知乎的GET请求 if(reqUrl.includes(zhihu.com) reqMethod ! GET) { return originalFetch(input, init); } // 特殊处理逻辑... } return originalFetch(input, init); };2.3 上下文环境检测最终保障即使请求本身符合条件还需要确认当前页面环境function isTargetPage() { // 检查当前域名 if(![bilibili.com, zhihu.com].some(d location.hostname.includes(d))) { return false; } // 检查页面特定元素 if(document.querySelector(#bilibili-player)) { return true; } // 其他环境验证... return false; }3. 高级拦截模式与安全实践3.1 动态规则管理系统硬编码的规则缺乏灵活性。更优解是设计可配置的规则引擎const interceptionRules [ { name: B站视频拦截, condition: (url, init) { return new URL(url).hostname.endsWith(bilivideo.com) init?.headers?.[Referer]?.includes(bilibili.com); }, action: async (response) { const data await response.json(); data.modified true; return new Response(JSON.stringify(data), response); } } // 更多规则... ]; unsafeWindow.fetch async function(input, init) { const url typeof input string ? input : input.url; for(const rule of interceptionRules) { if(rule.condition(url, init)) { const response await originalFetch(input, init); return rule.action(response.clone()); } } return originalFetch(input, init); };3.2 安全使用unsafeWindow的准则unsafeWindow虽然强大但存在风险使用时需注意最小权限原则只在必要时使用避免污染全局使用唯一命名空间错误隔离try-catch包裹关键操作(function() { const MY_NAMESPACE { originalFetch: unsafeWindow.fetch, interceptors: [] }; unsafeWindow.fetch function(...args) { try { // 拦截逻辑... } catch(e) { console.error(拦截失败:, e); return MY_NAMESPACE.originalFetch(...args); } }; // 清理钩子 window.addEventListener(unload, () { unsafeWindow.fetch MY_NAMESPACE.originalFetch; }); })();4. 实战B站视频请求拦截优化让我们通过一个实际案例展示如何逐步优化拦截逻辑。假设目标是拦截B站的视频流请求但不影响其他功能。初始方案问题明显unsafeWindow.fetch function(url) { if(url.includes(bilivideo.com)) { return Promise.reject(拦截成功); } return originalFetch(url); };问题分析仅检查主URL忽略Request对象形式没有区分视频类型如直播与点播粗暴拒绝会触发页面错误优化后的方案unsafeWindow.fetch async function(input, init) { const reqUrl (typeof input string ? input : input.url) || ; const parsedUrl new URL(reqUrl, location.href); // B站视频拦截 if(parsedUrl.hostname.endsWith(bilivideo.com)) { // 检查Referer确保是当前页面发起的请求 const referer init?.headers?.get?.(Referer) || ; if(!referer.includes(location.hostname)) { return originalFetch(input, init); } // 区分直播和点播 const isLive parsedUrl.pathname.includes(/live/); if(isLive) { // 特殊处理直播流 const response await originalFetch(input, init); return processLiveStream(response); } else { // 正常视频处理 return originalFetch(input, init); } } return originalFetch(input, init); }; async function processLiveStream(response) { // 实现具体的流处理逻辑... }5. 调试与问题排查技巧即使设计了严密的拦截逻辑实际运行中仍可能遇到意外情况。以下是一些实用调试方法请求日志系统const requestLogger { log: [], maxEntries: 100, add: function(url, status) { this.log.push({url, status, time: new Date()}); if(this.log.length this.maxEntries) { this.log.shift(); } } }; // 在fetch拦截器中添加日志 requestLogger.add(reqUrl, intercepted);动态调试开关// 通过URL参数控制调试模式 const debugMode new URLSearchParams(location.search).has(tm_debug); if(debugMode) { console.log(当前拦截规则:, interceptionRules); window._debugInterceptors { rules: interceptionRules, logger: requestLogger }; }性能影响监控const perf { originalTime: 0, interceptedTime: 0, get overhead() { return this.interceptedTime / (this.originalTime || 1); } }; // 在拦截逻辑中添加计时 const start performance.now(); const response await originalFetch(input, init); perf.originalTime performance.now() - start;在实际项目中我发现最棘手的往往不是技术实现而是对特定网站请求流的准确理解。以知乎为例其API调用存在复杂的依赖关系草率拦截一个请求可能导致后续一连串功能异常。这时采用观察-小范围测试-逐步放开的策略更为稳妥——先记录所有请求模式再针对性地设计拦截规则。
油猴脚本进阶:如何精准拦截B站、知乎等特定网站的Fetch请求而不‘误伤’?
发布时间:2026/5/24 14:15:08
油猴脚本进阶如何精准拦截B站、知乎等特定网站的Fetch请求而不‘误伤’在浏览器自动化工具中油猴脚本Tampermonkey因其轻量级和灵活性备受开发者青睐。特别是对于需要拦截和修改网页请求的中高级用户来说精准控制脚本的作用范围至关重要——一个设计不当的拦截逻辑可能导致目标网站功能异常甚至影响其他无关页面的正常运行。本文将深入探讨如何构建一套健壮的拦截机制确保脚本仅在预设条件下触发。1. 理解Fetch拦截的核心原理浏览器中的Fetch API是现代Web应用进行网络请求的主要方式。要拦截这些请求油猴脚本通常采用覆盖原生fetch函数的方式。但这种暴力替换存在明显缺陷它会全局生效影响所有页面的所有请求。关键问题在于作用域控制。油猴脚本默认运行在隔离的沙盒环境中但通过unsafeWindow可以访问页面的全局对象。以下是一个基础拦截示例// UserScript // run-at document-start // grant unsafeWindow // /UserScript (function() { const originalFetch unsafeWindow.fetch; unsafeWindow.fetch function(...args) { console.log(拦截到请求:, args[0]); return originalFetch.apply(this, args); }; })();这种简单实现会记录所有fetch请求但明显缺乏针对性。我们需要建立更精细的过滤机制。2. 构建精准拦截的三层过滤体系2.1 URL匹配第一道防线最直接的过滤方式是通过请求URL识别目标。但简单的字符串包含如indexOf容易产生误判。更可靠的做法是使用URL对象解析完整路径正则表达式精确匹配域名和路径考虑参数变化的影响function shouldIntercept(url) { const parsedUrl new URL(url); // 精确匹配B站视频域名 if(/^(www\.)?bilibili\.com$/.test(parsedUrl.hostname)) { return true; } // 匹配知乎特定API路径 if(parsedUrl.hostname www.zhihu.com parsedUrl.pathname.startsWith(/api/v4/answers)) { return true; } return false; }2.2 请求特征验证第二层确认仅靠URL匹配还不够还需要检查请求的其他特征检查项示例作用请求方法GET/POST区分读写操作请求头Content-Type识别数据格式请求体JSON结构验证具体内容unsafeWindow.fetch async function(input, init) { const reqUrl typeof input string ? input : input.url; if(shouldIntercept(reqUrl)) { const reqMethod (init?.method || GET).toUpperCase(); // 只拦截知乎的GET请求 if(reqUrl.includes(zhihu.com) reqMethod ! GET) { return originalFetch(input, init); } // 特殊处理逻辑... } return originalFetch(input, init); };2.3 上下文环境检测最终保障即使请求本身符合条件还需要确认当前页面环境function isTargetPage() { // 检查当前域名 if(![bilibili.com, zhihu.com].some(d location.hostname.includes(d))) { return false; } // 检查页面特定元素 if(document.querySelector(#bilibili-player)) { return true; } // 其他环境验证... return false; }3. 高级拦截模式与安全实践3.1 动态规则管理系统硬编码的规则缺乏灵活性。更优解是设计可配置的规则引擎const interceptionRules [ { name: B站视频拦截, condition: (url, init) { return new URL(url).hostname.endsWith(bilivideo.com) init?.headers?.[Referer]?.includes(bilibili.com); }, action: async (response) { const data await response.json(); data.modified true; return new Response(JSON.stringify(data), response); } } // 更多规则... ]; unsafeWindow.fetch async function(input, init) { const url typeof input string ? input : input.url; for(const rule of interceptionRules) { if(rule.condition(url, init)) { const response await originalFetch(input, init); return rule.action(response.clone()); } } return originalFetch(input, init); };3.2 安全使用unsafeWindow的准则unsafeWindow虽然强大但存在风险使用时需注意最小权限原则只在必要时使用避免污染全局使用唯一命名空间错误隔离try-catch包裹关键操作(function() { const MY_NAMESPACE { originalFetch: unsafeWindow.fetch, interceptors: [] }; unsafeWindow.fetch function(...args) { try { // 拦截逻辑... } catch(e) { console.error(拦截失败:, e); return MY_NAMESPACE.originalFetch(...args); } }; // 清理钩子 window.addEventListener(unload, () { unsafeWindow.fetch MY_NAMESPACE.originalFetch; }); })();4. 实战B站视频请求拦截优化让我们通过一个实际案例展示如何逐步优化拦截逻辑。假设目标是拦截B站的视频流请求但不影响其他功能。初始方案问题明显unsafeWindow.fetch function(url) { if(url.includes(bilivideo.com)) { return Promise.reject(拦截成功); } return originalFetch(url); };问题分析仅检查主URL忽略Request对象形式没有区分视频类型如直播与点播粗暴拒绝会触发页面错误优化后的方案unsafeWindow.fetch async function(input, init) { const reqUrl (typeof input string ? input : input.url) || ; const parsedUrl new URL(reqUrl, location.href); // B站视频拦截 if(parsedUrl.hostname.endsWith(bilivideo.com)) { // 检查Referer确保是当前页面发起的请求 const referer init?.headers?.get?.(Referer) || ; if(!referer.includes(location.hostname)) { return originalFetch(input, init); } // 区分直播和点播 const isLive parsedUrl.pathname.includes(/live/); if(isLive) { // 特殊处理直播流 const response await originalFetch(input, init); return processLiveStream(response); } else { // 正常视频处理 return originalFetch(input, init); } } return originalFetch(input, init); }; async function processLiveStream(response) { // 实现具体的流处理逻辑... }5. 调试与问题排查技巧即使设计了严密的拦截逻辑实际运行中仍可能遇到意外情况。以下是一些实用调试方法请求日志系统const requestLogger { log: [], maxEntries: 100, add: function(url, status) { this.log.push({url, status, time: new Date()}); if(this.log.length this.maxEntries) { this.log.shift(); } } }; // 在fetch拦截器中添加日志 requestLogger.add(reqUrl, intercepted);动态调试开关// 通过URL参数控制调试模式 const debugMode new URLSearchParams(location.search).has(tm_debug); if(debugMode) { console.log(当前拦截规则:, interceptionRules); window._debugInterceptors { rules: interceptionRules, logger: requestLogger }; }性能影响监控const perf { originalTime: 0, interceptedTime: 0, get overhead() { return this.interceptedTime / (this.originalTime || 1); } }; // 在拦截逻辑中添加计时 const start performance.now(); const response await originalFetch(input, init); perf.originalTime performance.now() - start;在实际项目中我发现最棘手的往往不是技术实现而是对特定网站请求流的准确理解。以知乎为例其API调用存在复杂的依赖关系草率拦截一个请求可能导致后续一连串功能异常。这时采用观察-小范围测试-逐步放开的策略更为稳妥——先记录所有请求模式再针对性地设计拦截规则。