Playwright Python 网络功能文档
一、简介
Playwright 提供了用于监控和修改浏览器网络流量的 API,支持 HTTP 和 HTTPS 协议。页面发起的所有请求(包括 XHR 和 fetch 请求)都可被跟踪、修改和处理。
此外,Playwright 还支持网络相关的扩展能力,具体可参考:
- API 模拟指南:了解如何模拟 API 请求(无需实际发起请求)、执行 API 请求并修改响应、使用 HAR 文件模拟网络请求。
- WebSocket 处理:开箱即用地支持 WebSocket 检查、模拟和修改。
二、HTTP 认证
通过配置浏览器上下文(BrowserContext)的 http_credentials 参数,可自动处理需要 HTTP 认证的页面访问。
同步代码示例(Sync)
# 创建带有 HTTP 认证信息的浏览器上下文
context = browser.new_context(
http_credentials={"username": "bill", "password": "pa55w0rd"}
)
# 从上下文创建新页面
page = context.new_page()
# 导航到需要认证的页面(会自动使用配置的用户名和密码)
page.goto("https://example.com")
异步代码示例(Async)
# 异步创建带有 HTTP 认证信息的浏览器上下文
context = await browser.new_context(
http_credentials={"username": "bill", "password": "pa55w0rd"}
)
# 异步从上下文创建新页面
page = await context.new_page()
# 异步导航到需要认证的页面
await page.goto("https://example.com")
三、HTTP 代理
Playwright 支持为浏览器配置 HTTP(S) 代理或 SOCKSv5 代理,可通过两种方式设置:
- 全局代理:为整个浏览器实例设置,所有上下文共享代理配置;
- 上下文代理:为单个浏览器上下文设置,仅当前上下文生效。
同时,可指定代理的用户名/密码,或配置无需通过代理的主机(绕过代理)。
1. 全局代理配置
同步代码示例(Sync)
# 启动 Chromium 浏览器时配置全局代理
browser = chromium.launch(proxy={
"server": "http://myproxy.com:3128", # 代理服务器地址(格式:协议://地址:端口)
"username": "usr", # 代理用户名(可选)
"password": "pwd" # 代理密码(可选)
})
异步代码示例(Async)
# 异步启动 Chromium 浏览器时配置全局代理
browser = await chromium.launch(proxy={
"server": "http://myproxy.com:3128",
"username": "usr",
"password": "pwd"
})
2. 上下文代理配置
同步代码示例(Sync)
# 先启动无代理的浏览器实例
browser = chromium.launch()
# 为单个上下文配置代理(仅当前上下文使用该代理)
context = browser.new_context(proxy={"server": "http://myproxy.com:3128"})
异步代码示例(Async)
# 异步启动无代理的浏览器实例
browser = await chromium.launch()
# 异步为单个上下文配置代理
context = await browser.new_context(proxy={"server": "http://myproxy.com:3128"})
四、网络事件监控
Playwright 可监控页面的请求(request) 和响应(response) 事件,也可等待特定响应完成(如点击按钮后等待 API 响应)。
1. 监控所有请求与响应
通过 page.on("request") 和 page.on("response") 订阅事件,实时打印请求方法/URL、响应状态码/URL。
同步代码示例(Sync)
from playwright.sync_api import sync_playwright, Playwright
def run(playwright: Playwright):
chromium = playwright.chromium
# 启动浏览器(默认无头模式)
browser = chromium.launch()
page = browser.new_page()
# 订阅请求事件:打印请求方法和 URL
page.on("request", lambda request: print(">>", request.method, request.url))
# 订阅响应事件:打印响应状态码和 URL
page.on("response", lambda response: print("<<", response.status, response.url))
# 导航到目标页面,触发网络事件
page.goto("https://example.com")
# 关闭浏览器
browser.close()
# 初始化 Playwright 并执行
with sync_playwright() as playwright:
run(playwright)
异步代码示例(Async)
import asyncio
from playwright.async_api import async_playwright, Playwright
async def run(playwright: Playwright):
chromium = playwright.chromium
# 异步启动浏览器
browser = await chromium.launch()
page = await browser.new_page()
# 订阅请求和响应事件
page.on("request", lambda request: print(">>", request.method, request.url))
page.on("response", lambda response: print("<<", response.status, response.url))
# 异步导航到目标页面
await page.goto("https://example.com")
# 异步关闭浏览器
await browser.close()
async def main():
# 异步初始化 Playwright 并执行
async with async_playwright() as playwright:
await run(playwright)
# 运行异步主函数
asyncio.run(main())
2. 等待特定响应
通过 page.expect_response() 等待符合条件的响应(如点击按钮后触发的 API 请求),支持三种匹配方式:
- Glob 路径模式(如
**/api/fetch_data); - 正则表达式(如
re.compile(r"\.jpeg$")); - 自定义断言函数(如判断 URL 包含特定token)。
方式1:Glob 路径模式匹配
同步代码示例(Sync)
# 等待 URL 匹配 "**/api/fetch_data" 的响应(** 表示任意层级路径)
with page.expect_response("**/api/fetch_data") as response_info:
# 触发请求(如点击“更新”按钮)
page.get_by_text("Update").click()
# 获取响应对象
response = response_info.value
异步代码示例(Async)
# 异步等待 URL 匹配 "**/api/fetch_data" 的响应
async with page.expect_response("**/api/fetch_data") as response_info:
# 异步触发请求
await page.get_by_text("Update").click()
# 异步获取响应对象
response = await response_info.value
方式2:正则表达式匹配
同步代码示例(Sync)
import re
# 等待 URL 以 .jpeg 结尾的响应(正则表达式匹配)
with page.expect_response(re.compile(r"\.jpeg$")) as response_info:
page.get_by_text("Update").click()
response = response_info.value
异步代码示例(Async)
import re
# 异步等待 URL 以 .jpeg 结尾的响应
async with page.expect_response(re.compile(r"\.jpeg$")) as response_info:
await page.get_by_text("Update").click()
response = await response_info.value
方式3:自定义断言函数匹配
同步代码示例(Sync)
token = "user123" # 假设需要匹配的 token
# 等待 URL 包含指定 token 的响应(自定义断言函数)
with page.expect_response(lambda response: token in response.url) as response_info:
page.get_by_text("Update").click()
response = response_info.value
异步代码示例(Async)
token = "user123"
# 异步等待 URL 包含指定 token 的响应
async with page.expect_response(lambda response: token in response.url) as response_info:
await page.get_by_text("Update").click()
response = await response_info.value
五、处理请求(模拟 API 端点)
通过 page.route() 或 context.route() 拦截特定请求,并自定义响应内容(如模拟 API 成功/失败状态),无需实际发起后端请求。
page.route():仅对当前页面生效,包括页面弹出的窗口和链接;context.route():对整个浏览器上下文生效,所有从该上下文创建的页面共享路由规则。
1. 页面级路由(page.route())
同步代码示例(Sync)
# 定义测试数据(模拟 API 响应体)
test_data = '{"status": "success", "data": [1, 2, 3]}'
# 拦截 URL 匹配 "**/api/fetch_data" 的请求,返回自定义响应
page.route(
"**/api/fetch_data", # 匹配规则(Glob 模式)
lambda route: route.fulfill( # 自定义响应
status=200, # 响应状态码
body=test_data, # 响应体
headers={"Content-Type": "application/json"} # 响应头(可选)
)
)
# 导航到页面,触发拦截的请求
page.goto("https://example.com")
异步代码示例(Async)
test_data = '{"status": "success", "data": [1, 2, 3]}'
# 异步拦截请求并返回自定义响应
await page.route(
"**/api/fetch_data",
lambda route: route.fulfill(status=200, body=test_data, headers={"Content-Type": "application/json"})
)
# 异步导航到页面
await page.goto("https://example.com")
2. 上下文级路由(context.route())
同步代码示例(Sync)
# 拦截整个上下文内 URL 匹配 "**/api/login" 的请求
context.route(
"**/api/login",
lambda route: route.fulfill(status=200, body="accept") # 模拟登录成功响应
)
# 从上下文创建页面并导航
page = context.new_page()
page.goto("https://example.com")
异步代码示例(Async)
# 异步拦截上下文内的登录请求
await context.route(
"**/api/login",
lambda route: route.fulfill(status=200, body="accept")
)
# 异步创建页面并导航
page = await context.new_page()
await page.goto("https://example.com")
六、修改请求
通过 route.continue_() 方法,可在拦截请求后修改请求参数(如请求头、请求方法、请求体等),再继续发起请求。
示例1:删除请求头
同步代码示例(Sync)
# 定义路由处理函数:删除请求头中的 "x-secret" 字段
def handle_route(route):
# 获取原始请求头
headers = route.request.headers
# 删除敏感头信息
if "x-secret" in headers:
del headers["x-secret"]
# 继续发起请求(使用修改后的请求头)
route.continue_(headers=headers)
# 拦截所有请求(**/* 匹配所有 URL),应用处理函数
page.route("**/*", handle_route)
异步代码示例(Async)
# 异步定义路由处理函数
async def handle_route(route):
headers = route.request.headers
if "x-secret" in headers:
del headers["x-secret"]
# 异步继续发起请求
await route.continue_(headers=headers)
# 异步拦截所有请求并应用处理函数
await page.route("**/*", handle_route)
示例2:修改请求方法
同步代码示例(Sync)
# 拦截所有请求,将请求方法改为 POST
page.route("**/*", lambda route: route.continue_(method="POST"))
异步代码示例(Async)
# 异步拦截所有请求,修改请求方法为 POST
await page.route("**/*", lambda route: route.continue_(method="POST"))
七、中止请求
通过 route.abort() 方法,可拦截特定请求并直接中止(不发起实际请求),适用于屏蔽不必要的资源(如图片、广告等)。
示例1:中止图片请求(按文件后缀)
同步代码示例(Sync)
# 拦截所有后缀为 png/jpg/jpeg 的请求,直接中止(不加载图片)
page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
异步代码示例(Async)
# 异步拦截图片请求并中止
await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
示例2:按资源类型中止请求
同步代码示例(Sync)
# 拦截所有请求:若资源类型为 "image" 则中止,否则继续
page.route(
"**/*",
lambda route: route.abort() if route.request.resource_type == "image" else route.continue_()
)
异步代码示例(Async)
# 异步拦截请求,按资源类型判断是否中止
await page.route(
"**/*",
lambda route: route.abort() if route.request.resource_type == "image" else route.continue_()
)
八、修改响应
通过 route.fetch() 先获取原始响应,修改响应内容(如响应体、响应头)后,再通过 route.fulfill() 返回自定义响应给页面。
示例:修改页面标题(添加前缀)
同步代码示例(Sync)
from playwright.sync_api import Route
def handle_route(route: Route) -> None:
# 1. 获取原始响应(不直接返回给页面)
response = route.fetch()
# 2. 修改响应体:在 <title> 标签前添加前缀
body = response.text() # 获取原始响应体(文本格式)
modified_body = body.replace("<title>", "<title>My prefix:") # 修改标题
# 3. 返回自定义响应(复用原始响应的大部分参数,仅覆盖 body 和 headers)
route.fulfill(
response=response, # 复用原始响应的状态码、大部分头信息等
body=modified_body, # 覆盖响应体(使用修改后的内容)
headers={**response.headers, "content-type": "text/html"} # 强制设置 Content-Type 为 html
)
# 拦截 URL 匹配 "**/title.html" 的请求,应用响应修改逻辑
page.route("**/title.html", handle_route)
异步代码示例(Async)
from playwright.async_api import Route
async def handle_route(route: Route) -> None:
# 1. 异步获取原始响应
response = await route.fetch()
# 2. 异步获取响应体并修改
body = await response.text()
modified_body = body.replace("<title>", "<title>My prefix:")
# 3. 异步返回自定义响应
await route.fulfill(
response=response,
body=modified_body,
headers={**response.headers, "content-type": "text/html"}
)
# 异步拦截请求并应用响应修改逻辑
await page.route("**/title.html", handle_route)
九、Glob URL 模式
Playwright 的网络拦截方法(如 page.route()、page.expect_response())使用简化的 Glob 模式匹配 URL,支持以下通配符规则:
| 通配符 | 功能描述 |
|---|---|
* | 匹配除 / 外的任意字符(单层级) |
** | 匹配包括 / 在内的任意字符(多层级,如跨目录) |
? | 仅匹配字面量 ?(不匹配其他字符,匹配任意字符需用 *) |
{} | 匹配括号内用逗号分隔的选项列表(如 {png,jpg} 匹配 png 或 jpg) |
\ | 转义特殊字符(如 \* 匹配字面量 *,注意在代码中需写为 \\*) |
示例与说明
| Glob 模式 | 匹配结果 | 不匹配结果 |
|---|---|---|
https://example.com/*.js | https://example.com/file.js(单层级 JS 文件) | https://example.com/path/file.js(多层级) |
https://example.com/?page=1 | https://example.com/?page=1(含指定查询参数) | https://example.com(无查询参数) |
**/*.js | https://example.com/file.js、https://example.com/path/file.js(任意层级 JS 文件) | - |
**/*.{png,jpg,jpeg} | 所有后缀为 png、jpg、jpeg 的文件(如 https://a.com/img.png) | https://a.com/img.gif(其他后缀) |
重要注意事项
- Glob 模式必须匹配完整 URL(包括协议、域名、路径),而非部分片段;
- 复杂匹配场景(如动态参数、多条件组合)建议使用 正则表达式(RegExp) 替代 Glob 模式;
- 匹配时需注意 URL 的大小写(Glob 模式默认区分大小写)。
十、WebSockets
Playwright 原生支持 WebSocket 的检查、模拟和修改,具体模拟方法可参考 API 模拟指南。
监控 WebSocket 事件
通过 page.on("websocket") 订阅 WebSocket 创建事件,可进一步监控 WebSocket 的发送帧(framesent)、接收帧(framereceived) 和关闭(close) 事件。
同步代码示例(Sync)
# 定义 WebSocket 处理函数
def on_web_socket(ws):
# WebSocket 连接打开时打印 URL
print(f"WebSocket opened: {ws.url}")
# 监控发送帧事件:打印发送的 payload
ws.on("framesent", lambda payload: print(f"Sent frame: {payload}"))
# 监控接收帧事件:打印接收的 payload
ws.on("framereceived", lambda payload: print(f"Received frame: {payload}"))
# 监控关闭事件
ws.on("close", lambda: print("WebSocket closed"))
# 订阅 WebSocket 创建事件
page.on("websocket", on_web_socket)
异步代码示例(Async)
# 定义 WebSocket 处理函数(异步场景下逻辑与同步一致,无需 await)
def on_web_socket(ws):
print(f"WebSocket opened: {ws.url}")
ws.on("framesent", lambda payload: print(f"Sent frame: {payload}"))
ws.on("framereceived", lambda payload: print(f"Received frame: {payload}"))
ws.on("close", lambda: print("WebSocket closed"))
# 订阅 WebSocket 创建事件
page.on("websocket", on_web_socket)
十一、丢失的网络事件与服务工作线程(Service Workers)
若使用 browser_context.route() 或 page.route() 时发现网络事件丢失,可能是服务工作线程(Service Workers)接管了网络请求导致,可通过以下方式解决:
1. 禁用 Service Workers
在创建浏览器上下文时,将 service_workers 设置为 'block',阻止 Service Workers 运行:
# 同步示例:创建禁用 Service Workers 的上下文
context = browser.new_context(service_workers='block')
# 异步示例
context = await browser.new_context(service_workers='block')
2. 避免与第三方模拟工具冲突
若使用 Mock Service Worker (MSW) 等第三方模拟工具,其会通过 Service Workers 接管请求,导致 Playwright 的 route() 无法捕获事件。建议:
- 优先使用 Playwright 内置的
browser_context.route()或page.route()进行响应模拟; - 若需同时使用第三方工具,需确保其不影响 Playwright 的网络拦截逻辑。
3. 监听 Service Workers 发起的请求
若需监控 Service Workers 本身发起的请求,可参考 Playwright 的 实验性功能文档(需注意实验性功能可能存在兼容性问题)。