如果您在 Playwright 上运行解析脚本或多账户操作,并为每个代理启动一个单独的浏览器实例——您是在浪费内存和 CPU。Playwright 可以在 BrowserContext 层面分配代理,而不是整个浏览器,这从根本上改变了脚本的架构:一个浏览器进程,数十个隔离的上下文,具有不同的 IP、cookies 和指纹。在本文中——它是如何工作的,Python 和 Node.js 的代码,以及在使用住宅和移动代理时为什么这特别重要。
问题:在 browser.launch() 中使用代理——昂贵且不方便
许多 Playwright 脚本的经典方法是:为每个代理调用 chromium.launch({ proxy: {...} }),创建一个单独的浏览器进程,它运行,然后关闭,接着启动下一个新的代理。如果需要并行处理 50 个 IP 地址以解析 Wildberries 或 Ozon,这意味着同时有 50 个完整的 Chromium 进程——每个在启动时消耗 150 到 300 MB 的 RAM,不包括选项卡和渲染。
实际上,这在普通的 8 GB 内存的 VPS 上已经在 15-20 个并行浏览器时达到了瓶颈。服务器开始交换,脚本因超时而崩溃,部分代理未能及时处理。问题不在于代理,也不在于 Playwright——问题在于架构:每次启动浏览器都会拉动整个渲染引擎、GPU 进程、网络和数十个辅助线程,而实际上您只需要一个新的 IP 和干净的会话。
Playwright 优雅地解决了这个问题:代理可以在启动浏览器时不设置,而是在创建 BrowserContext 时设置——这是一个轻量级的隔离“沙盒”,位于同一浏览器进程内。一个 Chromium 进程可以保持数十个上下文,每个上下文都有自己的代理、cookies 和 localStorage,彼此之间不相交。
浏览器与 BrowserContext:有什么区别
Browser 是引擎的进程本身(Chromium、Firefox 或 WebKit),它被启动一次并长时间存在。BrowserContext 是该进程内的一个隔离环境,类似于私密窗口(incognito),具有自己的 cookies、缓存、权限,以及对我们来说重要的网络设置,包括代理。
如果与像 Dolphin Anty 或 AdsPower 这样的反检测浏览器相比——每个“配置文件”本质上也是一个独立的隔离容器,具有 cookies 和指纹。Playwright 允许以编程方式构建类似的逻辑:一个浏览器——多个上下文,每个上下文就像一个独立的“配置文件”,具有自己的 IP。
| 参数 | 在浏览器上使用代理 | 在 BrowserContext 上使用代理 |
|---|---|---|
| 每个 IP 的内存 | 150-300 MB(新进程) | 15-40 MB(新上下文) |
| IP 更换速度 | 2-5 秒(重启浏览器) | 0.1-0.3 秒(新上下文) |
| cookies 隔离 | 完全(不同进程) | 完全(上下文不共享 cookies) |
| 在 8 GB RAM 上的最大并行会话数 | ~20-25 | ~150-200 |
按上下文设置代理:Python 代码
以下是基本模式:一个浏览器启动一次,然后为每个代理创建一个单独的上下文。这是 Playwright 的同步 API,但逻辑对于异步版本是相同的。
from playwright.sync_api import sync_playwright
proxies = [
{"server": "http://proxy1.example.com:8000", "username": "user1", "password": "pass1"},
{"server": "http://proxy2.example.com:8000", "username": "user2", "password": "pass2"},
{"server": "http://proxy3.example.com:8000", "username": "user3", "password": "pass3"},
]
with sync_playwright() as p:
# 浏览器只启动一次,不使用代理
browser = p.chromium.launch(headless=True)
for proxy in proxies:
# 代理分配给上下文,而不是浏览器
context = browser.new_context(
proxy={
"server": proxy["server"],
"username": proxy["username"],
"password": proxy["password"],
},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
)
page = context.new_page()
page.goto("https://httpbin.org/ip", timeout=30000)
print(page.content())
page.close()
context.close() # 只关闭上下文,浏览器继续运行
browser.close()
关键点是 browser.launch() 在循环外调用一次,而 browser.new_context() 在代理循环内调用。对于需要用户名和密码进行身份验证的代理,Playwright 会在网络层自动插入基本身份验证头,不需要额外配置。
按上下文设置代理:Node.js 代码
使用 async/await 的 JavaScript 逻辑——通常与 Node.js 的解析框架结合使用:
const { chromium } = require('playwright');
const proxies = [
{ server: 'http://proxy1.example.com:8000', username: 'user1', password: 'pass1' },
{ server: 'http://proxy2.example.com:8000', username: 'user2', password: 'pass2' },
];
(async () => {
const browser = await chromium.launch({ headless: true });
for (const proxy of proxies) {
const context = await browser.newContext({
proxy: {
server: proxy.server,
username: proxy.username,
password: proxy.password,
},
});
const page = await context.newPage();
await page.goto('https://httpbin.org/ip', { timeout: 30000 });
console.log(await page.content());
await page.close();
await context.close();
}
await browser.close();
})();
对于并行处理数十个代理,可以通过 Promise.all 创建上下文,而不必等待前一个关闭——这就是“一个浏览器,多个上下文”方法相对于启动单独进程的主要优势。
无须重启浏览器的干净代理轮换
“干净轮换”意味着在更换 IP 时不会留下前一个会话的尾巴——无论是 cookies、缓存还是服务工作者。在重启整个浏览器时,这会自动保证,但资源消耗较高。在使用上下文时,通过关闭旧上下文并创建新上下文可以实现相同的效果——这一操作只需几分之一秒。
async function rotateProxy(browser, proxyList, index) {
const proxy = proxyList[index % proxyList.length];
const context = await browser.newContext({ proxy });
const page = await context.newPage();
return { context, page };
}
// 使用方式:在每 N 次迭代中更换 IP
for (let i = 0; i < 100; i++) {
if (i % 10 === 0) {
const { context, page } = await rotateProxy(browser, proxies, i);
// 处理新的会话 10 次请求
}
}
这种模式在解析市场时尤其有用:例如,在收集 Wildberries 或 Ozon 的价格时,反机器人保护会监控来自同一 IP 的请求数量。上下文级别的轮换允许每 5-10 次请求更换 IP,而不会在重启浏览器时损失性能。
对于此类场景,住宅代理 非常合适——它们提供真实用户的 IP,市场较少将其标记为可疑,而不是明显的服务器范围。
内存和速度比较
在一台具有 8 GB RAM 和 4 vCPU 的测试 VPS 上,对两种方法进行了测试,任务是绕过 100 页,使用不同的 IP。在第一种情况下,为每个 IP 启动了一个单独的 chromium.launch(),在第二种情况下,使用一个浏览器顺序创建 100 个上下文。
| 指标 | 100 次 browser.launch() 启动 | 1 个浏览器中的 100 个上下文 |
|---|---|---|
| 峰值内存 | ~6.8 GB | ~1.9 GB |
| 总执行时间 | 182 秒 | 97 秒 |
| 因超时崩溃的会话数 | 7 | 0 |
在本次测试中,内存节省约为 70%,总执行时间几乎减少了一半,因为每次重新启动和结束 Chromium 进程的开销消失了。在大规模情况下——每天数千个 IP——差异变得更加明显,这直接影响到您可以在一台服务器上使用多少代理而无需升级硬件。
实际案例:解析和多账户操作
解析市场。 在收集 Wildberries、Ozon 或 Avito 的价格时,同一个浏览器保持数十个上下文打开,每个上下文都有独特的 IP 和自己的 cookie 存储。这降低了因请求模式而被封锁的风险,并允许更快地收集数据,而无需在 IP 之间重启进程。
多账户操作和账户农场。 在通过 Playwright 脚本测试 Facebook Ads 或 TikTok Ads 广告活动时,每个账户逻辑上应保存在一个单独的上下文中,具有自己的代理和用户代理——这模拟了像 Multilogin 或 GoLogin 这样的反检测浏览器的行为,只是没有反检测的许可证,完全通过编程实现。
地理广告测试。 需要检查创意在不同国家如何显示的市场营销人员,可以通过不同代理的上下文在一次浏览器会话中顺序“访问”所需区域,而无需手动打开数十个单独的窗口。
对于需要模拟移动流量的场景——例如,检查网站的移动版本或 TikTok Ads 中的广告——值得考虑 移动代理,它们提供来自真实移动运营商的 IP,较少引起反欺诈系统的怀疑。
如果任务是快速稳定的解析,而不需要高度匿名性(例如,从没有严格反机器人保护的网站收集公共数据),数据中心代理 在大量请求中提供最佳的速度和成本比。
常见错误及如何避免
1. 在 launch() 中设置代理,而不是在 new_context() 中。 这是新手最常见的错误——在启动浏览器时指定的代理会应用于所有上下文,无法单独重写。始终在 new_context() 中传递代理,如果需要 IP 的灵活性。
2. 不关闭旧上下文。 如果创建数百个上下文而不调用 context.close(),浏览器的内存将线性增长,直到进程因 OOM 崩溃。始终在完成该 IP 的任务后关闭上下文。
3. 所有上下文使用相同的用户代理。 如果代理更换,而浏览器的指纹保持不变(用户代理、语言、时区),市场和社交媒体的反机器人系统仍然可能将会话关联在一起。为每个上下文更换用户代理和视口与代理一起。
4. 忽视代理的超时。 住宅和移动代理有时由于真实的网络基础设施响应比数据中心代理慢。在调用 page.goto() 时设置合理的超时(20-40 秒),否则脚本会将工作代理视为“死”。
5. 在一个浏览器中同时打开太多上下文。 尽管节省了内存,但不受控制地同时创建 500+ 个活动上下文仍然会达到进程的限制。合理的平衡是在一个浏览器进程中保持 30-80 个同时活动的上下文,具体取决于服务器的 RAM 容量。
结论
在 BrowserContext 层面分配代理,而不是整个浏览器——这是一个架构解决方案,可以节省高达 70% 的内存,加快 IP 轮换速度数十倍,并在会话之间提供干净的 cookies 隔离,而无需不断重启 Chromium。对于市场解析、在不同地区测试广告和多账户操作,这是在同一服务器上增加并行会话数量的直接方法,而无需升级硬件。
如果您正在构建一个 Playwright 脚本来收集 Wildberries、Ozon 的数据或处理 Facebook Ads 和 TikTok Ads 的广告账户,代理池的质量与代码架构同样重要。对于 IP 的可靠性和对抗反机器人系统的抵抗力至关重要的任务,建议从 住宅代理 开始——它们与上述的“一个浏览器,多个上下文”方法非常契合。