🎭Playwright 中的代理不是页面打开后再切换的临时参数。应把 BrowserContext、代理会话、Cookie、语言与时区 视为同一个浏览身份边界。
本文使用 TypeScript 展示浏览器级和 Context 级代理配置,并给出出口验证、407、隧道失败、错地区和超时的排查方法。
安装
mkdir playwright-proxy-zh
cd playwright-proxy-zh
npm init -y
npm install playwright typescript tsx
npx playwright install chromium
环境变量:
PROXY_SERVER=http://proxy.example.com:8001
PROXY_USERNAME=customer-123-country-US
PROXY_PASSWORD=replace-me
IP_CHECK_URL=https://iprobe.io/json
浏览器级代理
import { chromium } from 'playwright';
const browser = await chromium.launch({
headless: true,
proxy: {
server: process.env.PROXY_SERVER!,
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
},
});
try {
const context = await browser.newContext();
const page = await context.newPage();
const response = await page.goto(
process.env.IP_CHECK_URL || 'https://iprobe.io/json',
{ waitUntil: 'domcontentloaded', timeout: 45_000 },
);
if (!response) throw new Error('没有收到导航响应');
console.log(response.status());
console.log(await page.locator('body').innerText());
await context.close();
} finally {
await browser.close();
}
Playwright 官方 BrowserType 文档说明代理对象支持 HTTP 和 SOCKS 服务器,并可为 HTTP 代理提供用户名和密码。参见 Playwright BrowserType↗。
Context 级代理
当一个浏览器进程需要处理多个独立市场或账号时,可以为每个 Context 配置代理:
const browser = await chromium.launch();
const usContext = await browser.newContext({
proxy: {
server: process.env.PROXY_SERVER!,
username: 'customer-123-country-US',
password: process.env.PROXY_PASSWORD,
},
locale: 'en-US',
timezoneId: 'America/New_York',
});
const deContext = await browser.newContext({
proxy: {
server: process.env.PROXY_SERVER!,
username: 'customer-123-country-DE',
password: process.env.PROXY_PASSWORD,
},
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
});
每个 Context 隔离 Cookie、本地存储和页面状态。不同代理身份不要混用同一个 Context。
先验证代理,再访问真实目标
建议按顺序执行:
- 使用 curl 验证同一组凭证。
- 使用 Playwright 访问中立 IP 检测页。
- 确认出口 IP 与请求地域。
- 使用最小脚本访问获得授权的真实目标。
- 验证业务内容,而不是只看 HTTP 200。
curl --proxy 'http://proxy.example.com:8001' \
--proxy-user 'customer-123-country-US:replace-me' \
--connect-timeout 15 --max-time 30 \
'https://iprobe.io/json'
轮换与粘性会话
代理是否轮换通常由代理服务的凭证或会话参数决定,而不是 Playwright 自动完成。
轮换适合: 独立商品页、公开列表页、一次性快照。
粘性适合: 登录、多步骤表单、购物车、分页状态、浏览器 Agent。
粘性任务应在同一个 Context 中完成,并在逻辑任务结束后关闭 Context。
地域一致性
代理 IP 只是地域的一部分。还需要检查:
- 浏览器语言
- 时区
- URL 市场参数
- Cookie
- 账号地区
- 配送地址或邮编
const context = await browser.newContext({
proxy: getProxy(),
locale: 'en-GB',
timezoneId: 'Europe/London',
});
最终应验证页面实际返回的币种、语言、库存或搜索市场。
不要默认等待 networkidle
现代页面可能持续发送分析、轮询或流式请求。更可靠的方式是:
await page.goto(targetUrl, {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.locator('[data-testid="price"]').first().waitFor({
state: 'visible',
timeout: 15_000,
});
等待代表业务数据已出现的元素,而不是等待网络完全安静。
记录网络失败与 HTTP 错误
page.on('requestfailed', request => {
console.error({
type: 'request_failed',
url: request.url(),
error: request.failure()?.errorText,
});
});
page.on('response', response => {
if (response.status() >= 400) {
console.warn({
type: 'http_error',
status: response.status(),
url: response.url(),
});
}
});
requestfailed 表示浏览器未获得正常 HTTP 响应;403、407、429 等通常需要通过 response 状态单独识别。
常见错误
| 现象 | 优先检查 |
|---|
| 407 | 用户名、密码、端口、凭证格式 |
| ERR_PROXY_CONNECTION_FAILED | 代理主机、DNS、端口、防火墙 |
| ERR_TUNNEL_CONNECTION_FAILED | 协议与端口、认证、CONNECT 路径 |
| 超时 | 代理连接、目标响应、等待条件 |
| IP 正确但地区内容错误 | Cookie、语言、时区、账号和 URL |
| 登录后流程失败 | 是否中途轮换、Context 是否重建 |
重试必须改变条件
- 407:停止重试并修复凭证
- 连接瞬时失败:有限更换路由
- 429:降低频率并退避
- 明确拒绝:停止并复核授权
- 解析失败:保存页面证据并更新解析器
不要使用无限轮换掩盖配置或政策问题。
Trace 排错
await context.tracing.start({
screenshots: true,
snapshots: true,
sources: true,
});
try {
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
} finally {
await context.tracing.stop({ path: 'trace.zip' });
}
查看:
npx playwright show-trace trace.zip
Trace 可能包含敏感 URL、页面和状态,分享前必须人工检查。
生产检查清单
- 固定 Playwright 和浏览器版本。
- 凭证存入密钥管理系统。
- curl 与 Playwright 使用完全相同的代理参数。
- 每个身份使用独立 Context。
- 出口地域与页面业务地域同时验证。
- 轮换与粘性按任务边界选择。
- 重试次数、总时间和流量有限制。
- 日志中不出现完整密码或会话参数。
- 使用
finally 关闭 Context 和浏览器。 - 目标访问已获得授权。
相关内容