1. 油猴脚本与Fetch请求拦截基础作为一个前端开发者我经常遇到需要调试复杂单页应用(SPA)的场景。这时候油猴脚本就成了我的秘密武器。你可能已经用过一些简单的油猴脚本来屏蔽广告或修改页面样式但它的能力远不止于此。今天我要分享的是如何用油猴脚本精准拦截和修改网页的Fetch请求。Fetch API是现代Web应用中最常用的网络请求方式之一。相比老旧的XMLHttpRequest它提供了更强大、更灵活的功能。但这也意味着我们需要更精细的控制手段。想象一下当你在调试一个电商网站时只想拦截商品详情API的请求而不是把所有请求都拦下来——这就是精准拦截的价值所在。油猴脚本之所以能实现这个功能是因为它可以在页面加载的早期就注入我们的代码。通过run-at document-start这个元数据我们可以确保在页面发起任何Fetch请求之前就已经替换了原生的fetch函数。这就像是在快递公司的分拣中心安插了自己的工作人员可以在包裹发出前就进行拦截和检查。2. 从粗放到精准URL模式匹配实战2.1 基础URL拦截技巧让我们从一个最简单的例子开始。假设我们只想拦截B站视频流的请求可以这样写// UserScript // run-at document-start // grant unsafeWindow // /UserScript (function() { const originalFetch fetch; unsafeWindow.fetch function(...args) { const [url] args; if (typeof url string url.includes(bilivideo.com)) { console.log(拦截到B站视频请求:, url); // 这里可以修改请求或返回自定义响应 return Promise.reject(new Error(请求被拦截)); } return originalFetch.apply(this, args); }; })();这个脚本有几个关键点使用run-at document-start确保尽早执行通过grant unsafeWindow获取修改全局对象的权限保存原始的fetch函数引用以便放行不需要拦截的请求检查请求URL只对特定域名进行拦截2.2 高级URL模式匹配但现实中的需求往往更复杂。比如我们可能需要拦截特定路径下的API如/api/user/profile带有特定查询参数的请求如?typepremium多个域名的请求如测试环境和生产环境这时候就需要更强大的URL匹配策略。我推荐使用URL对象和正则表达式结合的方式unsafeWindow.fetch async function(...args) { let requestUrl; try { requestUrl new URL(args[0], location.href); } catch (e) { // 不是有效的URL直接放行 return originalFetch.apply(this, args); } // 匹配路径和查询参数 const isTargetApi requestUrl.pathname.startsWith(/api/v2) requestUrl.searchParams.has(debug); if (isTargetApi) { console.log(拦截到目标API:, requestUrl.href); // 自定义处理逻辑 } return originalFetch.apply(this, args); };3. 深入请求与响应操作3.1 修改请求参数有时候我们不仅需要拦截请求还需要修改请求的内容。比如在调试时我们可能想强制使用测试环境的APIunsafeWindow.fetch function(input, init {}) { const url new URL(input, location.href); if (url.hostname api.example.com) { url.hostname test-api.example.com; console.log(重定向到测试环境:, url.href); // 克隆init对象避免污染原始配置 const newInit { ...init, headers: { ...init.headers, X-Debug-Mode: true } }; return originalFetch(url.href, newInit); } return originalFetch(input, init); };3.2 修改响应数据更强大的功能是修改响应数据。比如我们想在返回的用户数据中添加调试信息unsafeWindow.fetch function(input, init) { return originalFetch(input, init).then(async response { if (!response.ok) return response; const url new URL(response.url); if (!url.pathname.startsWith(/api/user)) { return response; } // 克隆响应对象以便修改 const clonedResponse response.clone(); const data await clonedResponse.json(); // 添加调试信息 data.debugInfo { interceptedBy: Tampermonkey, timestamp: Date.now() }; // 创建新的响应对象 return new Response(JSON.stringify(data), { status: response.status, statusText: response.statusText, headers: response.headers }); }); };4. 实战构建健壮的拦截脚本4.1 错误处理与兼容性在实际使用中我们需要考虑各种边界情况unsafeWindow.fetch function() { try { const args Array.from(arguments); const input args[0]; // 处理Request对象的情况 if (input instanceof Request) { const newRequest new Request(input, { // 可以在这里修改Request的配置 }); args[0] newRequest; } return originalFetch.apply(this, args) .then(response { // 响应处理逻辑 }) .catch(error { console.error(Fetch拦截出错:, error); throw error; }); } catch (e) { console.error(拦截脚本出错:, e); return originalFetch.apply(this, arguments); } };4.2 性能优化技巧拦截脚本如果写得不好可能会显著影响页面性能。以下是一些优化建议尽早过滤不需要拦截的请求减少不必要的处理避免在拦截逻辑中进行同步的复杂计算谨慎使用response.clone()它会增加内存使用对于频繁调用的API考虑缓存处理结果// 使用Set来存储需要拦截的URL模式查找更快 const interceptPatterns new Set([ /api/user, /api/products ]); unsafeWindow.fetch function(input) { const url new URL(input, location.href); // 快速过滤 if (!interceptPatterns.has(url.pathname)) { return originalFetch.apply(this, arguments); } // 其余处理逻辑 };5. 高级应用场景5.1 数据Mock与接口调试在前后端分离开发中前端经常需要模拟后端接口。用油猴脚本可以轻松实现const mockData { /api/user/profile: { name: 测试用户, avatar: https://example.com/test-avatar.jpg } }; unsafeWindow.fetch function(input) { const url new URL(input, location.href); if (mockData[url.pathname]) { console.log(返回mock数据:, url.pathname); return Promise.resolve( new Response(JSON.stringify(mockData[url.pathname]), { status: 200, headers: {Content-Type: application/json} }) ); } return originalFetch.apply(this, arguments); };5.2 请求监控与分析对于复杂的SPA应用监控特定API的调用情况很有帮助const apiStats {}; unsafeWindow.fetch function(input, init) { const startTime performance.now(); const url new URL(input, location.href); return originalFetch.apply(this, arguments).then(response { const duration performance.now() - startTime; if (!apiStats[url.pathname]) { apiStats[url.pathname] { count: 0, totalDuration: 0, lastCalled: null }; } const stats apiStats[url.pathname]; stats.count; stats.totalDuration duration; stats.lastCalled new Date(); console.log(API ${url.pathname} 调用统计:, stats); return response; }); };6. 安全与最佳实践在使用这些强大功能的同时我们也需要注意安全性只在开发环境或信任的网站上使用这类脚本避免在脚本中硬编码敏感信息定期检查脚本是否与网站更新保持兼容考虑使用脚本的本地开发模式而不是直接安装一个相对安全的做法是将配置部分抽离出来// UserScript // name API调试助手 // description 用于开发和调试API的油猴脚本 // match https://your-dev-site.example.com/* // run-at document-start // grant unsafeWindow // /UserScript const CONFIG { debugMode: true, interceptPatterns: [ /api/user, /api/products ], mockData: { // mock数据配置 } }; // 主逻辑...在实际项目中我发现这些技巧可以大幅提升开发效率。特别是在调试那些没有完善文档的第三方API时能够实时查看和修改请求响应非常有用。不过要记住这些技术应该仅用于合法合规的开发调试目的。