news 2026/6/14 10:56:56

用Playwright拦截和修改网络请求:不只是抓包那么简单

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Playwright拦截和修改网络请求:不只是抓包那么简单

在 Web 开发和测试领域,抓包工具如 Fiddler、Charles 和 Chrome DevTools 早已成为开发者的标配。它们能让我们查看网络请求和响应内容,帮助定位问题。但当我们需要更深度的网络控制 —— 比如自动化修改请求、模拟各种异常场景、与 UI 操作无缝集成时,传统抓包工具就显得力不从心了。

Playwright 作为现代浏览器自动化框架,提供了一套强大而灵活的网络拦截 API。它不仅仅是一个 "抓包工具",更是一个能让你完全掌控浏览器网络层的 "网络编程接口"。通过 Playwright,你可以在请求发出前修改它,在响应到达应用前篡改它,甚至完全替换整个网络通信流程。

一、为什么 Playwright 的网络拦截与众不同

传统抓包工具工作在系统代理或浏览器扩展层面,而 Playwright 直接与浏览器的 DevTools 协议深度集成。这种架构带来了几个关键优势:

  • 无侵入性:不需要修改系统代理或安装浏览器扩展,启动浏览器时自动配置
  • 时序精确:拦截发生在浏览器内部,比外部代理更早捕获请求
  • 自动化友好:所有操作都可以通过代码控制,完美融入自动化测试和爬虫流程
  • 上下文隔离:每个浏览器上下文可以有独立的网络拦截规则,互不干扰
  • 协议全面:支持 HTTP/HTTPS、WebSocket、Fetch 和 XHR 等所有现代 Web 协议

二、核心原理:page.route () 是如何工作的

Playwright 网络拦截的核心是page.route()方法。它允许你注册一个路由处理函数,当浏览器发出匹配特定 URL 模式的请求时,这个函数会被调用。

typescript

运行

// 基本语法 await page.route(urlPattern, async (route) => { // 在这里处理拦截到的请求 // route对象包含了请求的所有信息和操作方法 });

URL 模式支持三种匹配方式:

  • 精确匹配https://api.example.com/users
  • 通配符匹配**/api/users/*(最常用)
  • 正则表达式new RegExp('.*\\.json$')

当一个请求被拦截时,你有四种选择:

  1. route.continue():允许请求继续发送,可选修改请求头、URL 或请求体
  2. route.fulfill():直接返回一个自定义响应,不发送真实请求
  3. route.abort():完全中止请求
  4. route.fetch():先获取真实响应,修改后再返回给应用

三、基础操作:从拦截到修改

3.1 拦截并记录所有请求

最简单的用法是记录所有网络请求,这比 Chrome DevTools 更适合自动化分析:

typescript

运行

import { chromium } from 'playwright'; async function logAllRequests() { const browser = await chromium.launch(); const page = await browser.newPage(); // 拦截所有请求 await page.route('**/*', (route) => { const request = route.request(); console.log(`[${request.method()}] ${request.url()}`); console.log(` 资源类型: ${request.resourceType()}`); console.log(` 请求头: ${JSON.stringify(request.headers())}`); // 继续请求 route.continue(); }); await page.goto('https://example.com'); await browser.close(); } logAllRequests();

3.2 修改请求:在发送前篡改数据

你可以在请求发送到服务器之前修改它的任何部分:

typescript

运行

// 修改请求头 await page.route('**/api/**', async (route) => { const headers = { ...route.request().headers() }; // 添加认证令牌 headers['Authorization'] = 'Bearer your-token-here'; // 移除追踪头 delete headers['X-Tracking-Id']; // 修改User-Agent headers['User-Agent'] = 'Custom-Browser/1.0'; await route.continue({ headers }); }); // 修改POST请求体 await page.route('**/api/login', async (route) => { if (route.request().method() === 'POST') { const postData = JSON.parse(route.request().postData() || '{}'); // 修改用户名 postData.username = 'modified-user'; // 添加额外参数 postData.extraField = 'injected-by-playwright'; await route.continue({ postData: JSON.stringify(postData) }); } else { await route.continue(); } });

3.3 修改响应:在应用收到前篡改数据

这是 Playwright 最强大的功能之一。你可以先获取真实的 API 响应,修改其中的部分内容,然后再返回给应用:

typescript

运行

// 修改API响应数据 await page.route('**/api/products', async (route) => { // 先获取真实响应 const response = await route.fetch(); // 解析JSON数据 const data = await response.json(); // 修改数据:将所有商品价格打五折 data.products = data.products.map((product: any) => ({ ...product, price: product.price * 0.5, discounted: true })); // 添加一个新商品 data.products.push({ id: 999, name: 'Playwright专属商品', price: 0.99, discounted: true }); // 返回修改后的响应 await route.fulfill({ response, // 保留原始响应的状态码和头信息 json: data // 替换响应体 }); }); // 修改HTML页面内容 await page.route('**/index.html', async (route) => { const response = await route.fetch(); let body = await response.text(); // 注入自定义JavaScript body = body.replace( '</body>', '<script>alert("页面已被Playwright修改!");</script></body>' ); await route.fulfill({ response, body }); });

3.4 模拟响应:完全替代真实 API

当后端 API 尚未开发完成或不稳定时,你可以直接返回模拟数据:

typescript

运行

// 模拟成功响应 await page.route('**/api/users', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' } ]) }); }); // 模拟错误响应 await page.route('**/api/payment', async (route) => { await route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ error: '服务器内部错误', code: 'INTERNAL_SERVER_ERROR' }) }); });

3.5 中止请求:阻止不必要的资源加载

这对于加速测试和爬虫非常有用,可以阻止图片、广告和统计代码的加载:

typescript

运行

// 阻止所有图片加载 await page.route('**/*.{png,jpg,jpeg,gif,svg}', (route) => { route.abort(); }); // 阻止特定域名的请求 await page.route('**/*.google-analytics.com/**', (route) => { route.abort(); }); // 根据资源类型阻止 await page.route('**/*', (route) => { const resourceType = route.request().resourceType(); if (['image', 'stylesheet', 'font'].includes(resourceType)) { route.abort(); } else { route.continue(); } });

四、高级应用场景:超越基础抓包

4.1 GraphQL 请求的精准拦截与修改

GraphQL 请求通常都发送到同一个端点,传统的 URL 匹配方式无法区分不同的操作。Playwright 可以根据请求体中的operationName进行精准拦截:

typescript

运行

// 拦截特定的GraphQL操作 await page.route('**/graphql', async (route) => { const request = route.request(); const postData = JSON.parse(request.postData() || '{}'); // 根据操作名称区分处理 switch (postData.operationName) { case 'GetUserProfile': // 修改用户查询响应 const response = await route.fetch(); const data = await response.json(); data.data.user.name = 'Modified Name'; data.data.user.email = 'modified@example.com'; await route.fulfill({ response, json: data }); break; case 'CreateOrder': // 模拟订单创建失败 await route.fulfill({ status: 400, contentType: 'application/json', body: JSON.stringify({ errors: [{ message: '库存不足' }] }) }); break; default: // 其他请求正常发送 await route.continue(); } });

4.2 WebSocket 通信的完全控制

Playwright 不仅支持 HTTP 请求,还能拦截和修改 WebSocket 通信:

typescript

运行

// 监听WebSocket连接 page.on('websocket', (ws) => { console.log(`WebSocket已连接: ${ws.url()}`); // 监听发送的消息 ws.on('framesent', (event) => { console.log(`发送: ${event.payload}`); }); // 监听接收的消息 ws.on('framereceived', (event) => { console.log(`接收: ${event.payload}`); }); // 监听关闭事件 ws.on('close', () => { console.log('WebSocket已关闭'); }); }); // 你甚至可以发送自定义消息到WebSocket服务器 // 或者模拟服务器发送消息到客户端

4.3 模拟网络条件:测试弱网和离线场景

Playwright 可以模拟各种网络条件,包括 3G、4G、离线等:

typescript

运行

// 模拟3G网络 await page.context().setOffline(false); await page.context().setNetworkConditions({ offline: false, downloadThroughput: 750 * 1024 / 8, // 750 Kbps uploadThroughput: 250 * 1024 / 8, // 250 Kbps latency: 150 // 150ms延迟 }); // 模拟离线状态 await page.context().setOffline(true); // 恢复正常网络 await page.context().setNetworkConditions({ offline: false, downloadThroughput: -1, uploadThroughput: -1, latency: 0 });

4.4 动态路由:根据条件决定如何处理请求

你可以在路由处理函数中实现复杂的条件逻辑:

typescript

运行

// 动态决定是否使用Mock数据 let useMockData = true; await page.route('**/api/data', async (route) => { if (useMockData) { // 返回Mock数据 await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: 'mock-data' }) }); } else { // 使用真实API await route.continue(); } }); // 稍后可以切换模式 useMockData = false;

4.5 反爬虫绕过:修改请求指纹

许多网站使用请求指纹来检测爬虫。通过 Playwright 的网络拦截,你可以修改这些指纹:

typescript

运行

// 模拟真实浏览器的请求头 await page.route('**/*', async (route) => { const headers = { ...route.request().headers() }; // 移除Playwright特有的头信息 delete headers['X-Playwright']; // 添加真实浏览器的头信息 headers['Accept-Language'] = 'zh-CN,zh;q=0.9,en;q=0.8'; headers['Accept-Encoding'] = 'gzip, deflate, br'; headers['Sec-Fetch-Dest'] = 'document'; headers['Sec-Fetch-Mode'] = 'navigate'; headers['Sec-Fetch-Site'] = 'none'; headers['Sec-Fetch-User'] = '?1'; headers['Upgrade-Insecure-Requests'] = '1'; await route.continue({ headers }); });

五、完整实战案例:电商网站价格测试

让我们通过一个完整的案例来展示 Playwright 网络拦截的强大功能。假设我们需要测试一个电商网站的价格显示和折扣计算功能:

typescript

运行

import { test, expect } from '@playwright/test'; test.describe('电商价格测试', () => { test.beforeEach(async ({ page }) => { // 拦截商品列表API await page.route('**/api/products', async (route) => { const response = await route.fetch(); const data = await response.json(); // 修改商品价格:创建各种测试场景 data.products = [ // 正常价格商品 { id: 1, name: '普通商品', price: 100, originalPrice: 100 }, // 打折商品 { id: 2, name: '打折商品', price: 80, originalPrice: 100 }, // 免费商品 { id: 3, name: '免费商品', price: 0, originalPrice: 50 }, // 高价商品 { id: 4, name: '高价商品', price: 99999, originalPrice: 99999 }, // 负数价格(测试错误处理) { id: 5, name: '异常商品', price: -10, originalPrice: 100 } ]; await route.fulfill({ response, json: data }); }); // 拦截购物车API await page.route('**/api/cart', async (route) => { if (route.request().method() === 'POST') { const postData = JSON.parse(route.request().postData() || '{}'); // 模拟添加商品到购物车成功 await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, cart: { items: [postData], total: postData.price, discount: 0 } }) }); } else { await route.continue(); } }); }); test('应该正确显示各种价格', async ({ page }) => { await page.goto('/products'); // 验证普通商品价格 await expect(page.locator('[data-testid="product-1"] .price')).toHaveText('¥100.00'); // 验证打折商品显示原价和现价 await expect(page.locator('[data-testid="product-2"] .original-price')).toHaveText('¥100.00'); await expect(page.locator('[data-testid="product-2"] .current-price')).toHaveText('¥80.00'); // 验证免费商品显示"免费" await expect(page.locator('[data-testid="product-3"] .price')).toHaveText('免费'); // 验证高价商品正确格式化 await expect(page.locator('[data-testid="product-4"] .price')).toHaveText('¥99,999.00'); // 验证异常价格显示错误提示 await expect(page.locator('[data-testid="product-5"] .price')).toHaveText('价格异常'); }); test('应该正确计算购物车总价', async ({ page }) => { await page.goto('/products'); // 添加普通商品到购物车 await page.click('[data-testid="product-1"] .add-to-cart'); // 验证购物车总价 await expect(page.locator('[data-testid="cart-total"]')).toHaveText('¥100.00'); // 添加打折商品到购物车 await page.click('[data-testid="product-2"] .add-to-cart'); // 验证购物车总价更新 await expect(page.locator('[data-testid="cart-total"]')).toHaveText('¥180.00'); }); });

六、最佳实践与常见陷阱

6.1 最佳实践

  1. 尽早注册路由:在调用page.goto()或触发任何可能产生网络请求的操作之前注册路由
  2. 使用精确的 URL 模式:避免使用**/*拦截所有请求,这会影响性能
  3. 保持路由处理函数简洁:复杂的逻辑应该提取到单独的函数中
  4. 正确处理异步操作:确保所有异步操作都使用await
  5. 使用route.fetch()获取原始响应:这样可以保留原始的状态码、头信息和 cookie
  6. 在测试结束后清理路由:使用page.unroute()移除不再需要的路由
  7. 分层测试策略:大多数测试使用 Mock 数据提高速度,保留少量集成测试使用真实 API

6.2 常见陷阱

  1. 忘记调用route.continue()route.fulfill():这会导致请求挂起,页面永远加载不完
  2. 修改响应时丢失 Content-Type 头:确保在route.fulfill()中设置正确的contentType
  3. 路由处理函数中的错误未被捕获:使用 try-catch 块处理可能的异常
  4. 多个路由匹配同一个请求:Playwright 会按照注册顺序调用第一个匹配的路由
  5. 在路由处理函数中进行耗时操作:这会影响页面加载速度
  6. 模拟响应时忽略 CORS 头:如果跨域请求需要特定的 CORS 头,确保在模拟响应中包含它们

七、总结

Playwright 的网络拦截功能远不止是一个简单的抓包工具。它提供了对浏览器网络层的完全控制,让你能够:

  • 在请求发出前修改任何部分
  • 在响应到达应用前篡改数据
  • 完全模拟 API 响应,无需依赖后端
  • 模拟各种网络条件和异常场景
  • 拦截和修改 WebSocket 通信
  • 与 UI 自动化无缝集成

这些能力使得 Playwright 成为现代 Web 应用开发和测试的必备工具。无论是前端开发、自动化测试还是网络爬虫,Playwright 的网络拦截功能都能帮助你解决传统抓包工具无法解决的问题。

随着 Web 应用变得越来越复杂,对网络控制的需求也越来越高。掌握 Playwright 的网络拦截技术,将让你在 Web 开发和测试中如虎添翼,能够更高效地构建和维护高质量的 Web 应用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 10:54:19

混合专家模型突破:从稀疏激活到高效推理的工程实践

背景介绍 2023年,当GPT-4以1.8万亿参数的庞大体量震惊业界时,一个关键问题浮出水面:如何在有限的算力预算下训练更大规模的模型?答案隐藏在Mixtral 8x7B、DeepSeek MoE等模型的成功背后——混合专家模型(MoE)架构。这项并非全新的技术,在大型语言模型时代焕发出惊人活力…

作者头像 李华
网站建设 2026/6/14 10:54:19

视频转PPT自动化工具:智能提取演示内容的高效解决方案

视频转PPT自动化工具&#xff1a;智能提取演示内容的高效解决方案 【免费下载链接】extract-video-ppt extract the ppt in the video 项目地址: https://gitcode.com/gh_mirrors/ex/extract-video-ppt 在当今数字化工作环境中&#xff0c;视频内容已成为知识传递的重要…

作者头像 李华
网站建设 2026/6/14 10:53:17

SpaceX IPO后饭堂大妈秒变百万富翁

从SpaceX上市拆解AI算力新基建&#xff1a;“星舰”技术架构对开发者的启示一、引言&#xff1a;一家火箭公司&#xff0c;为什么把AI算力写进招股书第一行&#xff1f;2026年6月12日&#xff0c;SpaceX在纳斯达克正式挂牌上市&#xff08;股票代码SPCX&#xff09;。抛开2.1万…

作者头像 李华
网站建设 2026/6/14 10:50:46

终极键盘重映射工具:5种模式解决游戏SOCD冲突的完整指南

终极键盘重映射工具&#xff1a;5种模式解决游戏SOCD冲突的完整指南 【免费下载链接】socd Key remapper for epic gamers 项目地址: https://gitcode.com/gh_mirrors/so/socd 还在为游戏中的方向键冲突而烦恼吗&#xff1f;当你在激烈的游戏对抗中同时按下左右方向键时…

作者头像 李华
网站建设 2026/6/14 10:48:53

TC8301 单通道直流马达驱动器

一、特点  单通道内置功率MOS 全桥驱动  驱动前进、后退、停止及刹车功能  内置迟滞热效应过流保护功能  低导通电阻&#xff08;1.6Ω&#xff09;  最大连续输出电流可达1.5A,峰值电流2.5A  采用SOP-8 封装形式 二、产品应用  玩具马达驱动 三、引脚图及引脚说…

作者头像 李华
网站建设 2026/6/14 10:48:52

TC1305 单通道直流马达驱动器

一、特点  单通道内置功率MOS 全桥驱动  正向、反向及刹停功能  内置迟滞热效应过流保护功能  持续输出电流可达2.7A(SOP-8),峰值电流5.5A  采用SOP-8 封装形式 二、产品应用  玩具马达驱动 三、引脚图及引脚说明四、绝对最大额定值五、推荐工作条件(Ta25℃)六、电…

作者头像 李华