结论先说: 在 Playwright 里设置代理,就是传一个包含 server、username、password 的 proxy 对象:传给 chromium.launch(),整个浏览器共用一个代理;传给 browser.newContext(),每个上下文可以用不同的出口 IP。需要账号密码的代理要用 HTTP 或 HTTPS 协议,Playwright 不支持带账号密码的 SOCKS5。用 NanoIP 时,动态住宅和移动代理填 HTTPS 网关,静态 ISP 代理填 http://主机:端口。

准备工作
需要安装 Node.js、Playwright 和浏览器:
npm install playwright
npx playwright install chromium下面的示例都是 ES 模块(.mjs 文件),使用顶层 await。Playwright 的 Python 版本用同样的 proxy 字段(server、username、password),设置方法可以直接对照。
给整个浏览器设置代理
把代理传给 launch(),浏览器打开的所有页面和上下文都会使用它。保存为 launch.mjs,用 node launch.mjs 运行:
import { chromium } from 'playwright';
const browser = await chromium.launch({
proxy: {
server: 'https://gateway.nanoip.net:1111',
username: 'USERNAME',
password: 'PASSWORD',
},
});
const page = await browser.newPage();
await page.goto('https://ipinfo.io/json');
console.log(await page.textContent('body'));
await browser.close();页面会以 JSON 输出出口 IP,以及它的 country、city 和 org。账号密码有单独的字段,所以密码里有 @、: 等字符也不需要转义。
给每个上下文设置不同的代理
浏览器上下文(context)是一个相互隔离的会话,Cookie、存储和缓存各自独立,相当于一个单独的浏览器环境。把 proxy 传给 newContext(),同一个浏览器里的每个上下文就能用不同的出口 IP,比每个代理启动一个浏览器更省资源。下面的 contexts.mjs 为每个代理开一个上下文:
import { chromium } from 'playwright';
const proxies = [
{ server: 'http://HOST_1:PORT_1', username: 'USERNAME_1', password: 'PASSWORD_1' },
{ server: 'http://HOST_2:PORT_2', username: 'USERNAME_2', password: 'PASSWORD_2' },
];
const browser = await chromium.launch();
for (const proxy of proxies) {
const context = await browser.newContext({ proxy });
const page = await context.newPage();
await page.goto('https://ipinfo.io/ip');
console.log(proxy.server, '→', (await page.textContent('body')).trim());
await context.close();
}
await browser.close();每一行输出是一个代理,以及网站通过它看到的 IP。某些地址不想走代理时,在代理对象里加 bypass,用逗号分隔,例如 bypass: 'localhost,.internal.example'。
Playwright 支持哪些代理

我们在 Chromium 和 Firefox 里都试了带账号密码的 SOCKS5 代理,Playwright 在启动时就报错:
browserType.launch: Browser does not support socks5 proxy authentication所以只要代理需要登录,就用 HTTP 或 HTTPS。对网页来说没有损失,浏览器的网页流量走 HTTP 代理和走 SOCKS5 是一样的。SOCKS5 的优势在 UDP,浏览器自动化很少用到,区别见 SOCKS5和HTTP代理的区别。
正式运行前先检查代理
密码错了,Playwright 不会给出明确的报错。Chromium 会先不带账号连接代理,代理要求登录后,Playwright 再提交你填的账号;如果代理不接受,页面就一直等待。我们用错误密码测试时,page.goto() 一直等到超时才结束。所以在长时间任务之前,先用较短的超时检查一次,例如 check.mjs:
import { chromium } from 'playwright';
const proxy = { server: 'http://HOST:PORT', username: 'USERNAME', password: 'PASSWORD' };
const browser = await chromium.launch({ proxy });
const page = await browser.newPage();
try {
const response = await page.goto('https://ipinfo.io/json', { timeout: 15_000 });
const info = await response.json();
console.log(`Exit IP ${info.ip} in ${info.city}, ${info.country} (${info.org})`);
} catch (error) {
console.error('Proxy check failed:', error.message.split('\n')[0]);
} finally {
await browser.close();
}账号密码正确时,会输出一行出口 IP 和位置。密码错误时,我们得到的是:
Proxy check failed: page.goto: Timeout 15000ms exceeded.第一个页面就超时,多半是账号密码的问题,而不是网站的问题。用 curl 测同一个代理就能区分:账号密码错误时,curl 会直接报 CONNECT tunnel failed, response 407。命令行检查方法见新手入门。

在 Playwright 中使用 NanoIP 代理
动态住宅和移动代理
server 填 https://gateway.nanoip.net:1111,用户名和密码填控制台 Dynamic Routes 页面生成的路由信息。浏览器自动化建议选粘性会话:一个页面的 HTML、脚本和图片会通过多个连接加载,用轮换路由时,这些连接可能从不同的 IP 发出;粘性路由能让整个页面以及后续操作在你选择的时间内保持同一个 IP。要并行运行多个上下文,就为每个上下文生成一个粘性路由。轮换和粘性会话怎么选,见轮换会话和粘性会话的区别。
静态 ISP 代理
每个静态 ISP 代理都有自己的主机和端口。server 填 http://主机:端口,再填这个 IP 的用户名和密码;同一组账号也能用于 SOCKS5,但在 Playwright 里要用 HTTP。静态 ISP 的 IP 在整个套餐期内不变,适合每天登录同一个账号的上下文,比如多账号管理和店铺后台。
在多个地区采集公开页面,通常用动态住宅代理,每个上下文一个粘性路由,见数据采集。
常见问题
上下文创建后还能换代理吗?
不能。上下文会一直使用创建时的代理。要换代理,关闭它,再用新的代理创建一个上下文。
context.request 会走代理吗?
会。我们测试时,用 context.request 发出的接口请求经过了该上下文的代理。单独用 request.newContext() 创建的请求上下文,除非另外传入 proxy,否则不走代理。
Selenium 能用同样的方法吗?
不能直接套用。Selenium 驱动 Chrome 时,--proxy-server 参数只能填主机和端口,没有填写账号密码的地方,需要借助扩展或第三方工具来处理代理登录。需要带账号密码的代理时,Playwright 的设置更简单。