为独立浏览器上下文动态切换代理
在每个上下文一个代理、运行时切换代理和独立浏览器实例之间做选择,并在依赖某条路由之前逐个验证每个上下文。
BotBrowser Team
会话已经积累 Cookie 和打开页面后再切换代理,可以保留有用状态,却也容易把旧请求误认为来自新路由。任务始终留在一个区域时应使用按上下文代理;运行时切换只适用于能够在继续前验证新出口的已授权工作。
TL;DR
- 上下文是把路由与已存储会话状态放在一起的最小实用单位。
BotBrowser.setBrowserContextProxy会更改一个现有上下文;已经开始的请求仍可能经由旧路由完成。- 每次更换路由后,记录并比较每个上下文的出口 IP、时区、区域设置和语言。
Contents
先确定由谁拥有路由
浏览器很少需要让它做的所有事情都走同一条网络路径。测试区域服务、把多个已授权账号分开管理,或者从两个位置比较同一个页面的团队,都需要让每项任务从各自的路由出去。浏览器启动时设置的单个代理简单且可预测,但只要浏览器保持打开,所有页面就绑定在同一个出口地址上。动态代理切换是应对这一限制的办法之一,但它只是三种办法中的一种,所以第一个决定是由哪个单位拥有路由:整个浏览器实例、一个浏览器上下文,或者一个在保持打开期间路由会变化的上下文。BotBrowser 可以为每个隔离的上下文单独设置代理,让每项已授权的任务保持各自的路由,但不能让网络或站点接受这条路由。
浏览器上下文是单个浏览器实例内部的隔离会话。它有自己的 Cookie、本地存储和缓存,与再启动一个浏览器相比,创建和丢弃的成本都很低。Playwright 把上下文描述为相互隔离的浏览器配置,Puppeteer 也通过自己的上下文 API 表达了同样的想法。既然上下文已经是存储状态的边界,它也就自然成为网络路由的边界:必须保持分离的工作,既不应共用存储,也不应共用出口地址。
下表比较这三种选择。内存成本和状态隔离通常决定取舍,而许可证等级决定你能使用哪些选项。
| 选项 | 适用场景 | 成本与隔离 | 文档中的要求 |
|---|---|---|---|
| 每个上下文一个代理,创建时设置 | 每个账号或区域在其上下文的整个生命周期内保持同一条路由 | 上下文共用一个浏览器实例,因此比独立实例占用更少内存,且每个上下文保留各自的 Cookie 和存储 | BotBrowser 的按上下文代理需要 ENT Tier3 许可证 |
| 运行时切换现有上下文 | 一个工作流需要在不同区域之间移动,或者需要在不丢弃已打开上下文的情况下更换路由 | 路由变化期间,上下文保留其页面和已存储的状态 | ENT Tier3 许可证和 BotBrowser.setBrowserContextProxy 命令 |
| 每个代理一个独立浏览器实例 | 需要最彻底的分离,或者你的等级不包含按上下文路由 | 每个实例都带有自己完整的浏览器开销和自己的用户数据目录 | 启动时的 --proxy-server 值,见代理配置指南 |
通常三个问题就能定下选择。如果路由必须在同一个上下文保持打开期间发生变化,就使用运行时切换。如果每个账号在整个生命周期内只用一条路由,就在创建上下文时指定代理,因为永不变化的路由更容易审阅,之后也更容易说明。如果你的许可证不包含按上下文路由,或者你想要最彻底的分离,就为每个代理启动一个浏览器实例,并接受额外的内存开销。
在上下文与独立实例之间做选择时,内存往往是决定因素,而它取决于页面在做什么。打开的标签页、媒体、扩展和下载都会累加,所以空白页面上的结果几乎说明不了生产任务的情况。请用真实且已授权的工作负载来测量,设置保守的上限,并为短时峰值留出余量,而不是照搬演示或其他团队配置里的容量数字。
有些团队通过自动化框架内的请求拦截来轮换路由,或者使用本地代理服务转发到不同的上游代理。这些设计可以工作,但会增加需要运行和监控的组件。BotBrowser 的文档还指出,时区对齐取决于通过 --proxy-server 设置的代理或按上下文代理,而不是仅在框架内设置的代理选项,所以当区域信号对你的任务很重要时,请把路由放在浏览器一侧。本地代理服务同样是 localhost 服务,浏览器如何探测本机端口可参阅 浏览器端口扫描防护:保护本机 localhost 服务。
创建上下文时指定代理
BotBrowser 文档中给上下文分配独立路由的方式,是在创建上下文时提供代理。在 Puppeteer 中,这是 createBrowserContext 的 proxyServer 选项,凭据嵌入在代理 URL 中。创建时没有自己代理的上下文,会继承启动配置文件的代理路由和地理身份,因此任何不应使用浏览器级路由的上下文都需要自己的代理。
const client = await browser.target().createCDPSession();
const ctx = await browser.createBrowserContext({
proxyServer: 'socks5://user:[email protected]:1080',
});
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: ctx._contextId,
botbrowserFlags: ['--bot-profile=path/to/profile.enc', '--proxy-ip=203.0.113.1'],
});
const page = await ctx.newPage();
创建之后,BotBrowser 会检测该上下文代理的出口 IP,并据此设置该上下文的时区、区域设置和语言。如果你已经知道出口 IP,可以用 --proxy-ip 传入,这样就会跳过查询。当上下文有自己的路由,同时你又声明了出口 IP 时,请在创建时同时提供两者:文档提醒,只在创建之后才应用 --proxy-ip,可能会让地理解析在地址声明可用之前就已开始。请在该上下文创建第一个页面之前发送标志更新,如上面的示例所示。
保持 auto 的设置会跟随该上下文的代理,而显式值则针对每个上下文和每项设置分别独立解析。当团队有意固定某一项,比如测试计划要求的界面语言,并让其他项跟随路由时,这很有用。请把每个固定值都当作有负责人的决定。路由变化后,旧的覆盖值看起来依然有效,也很容易被忘记。
Playwright 为新上下文记录了自己的 proxy 选项,要了解该选项在 Playwright 内部做什么,应以它的文档为准。BotBrowser 的按上下文代理文档是围绕上下文创建选项和上面展示的标志来写的,所以在 Playwright 环境中依赖自动对齐之前,请先在你自己的测试页面上确认区域值。来源列表中的 Playwright 页面是框架行为的依据。
凭据需要和路由同样的谨慎。文档中的命令和创建选项都接受带嵌入凭据的代理 URL,所以请把凭据留在那里,而不是改用单独的 page.authenticate() 调用。在任务启动时从密钥管理系统获取凭据,不要放进源码文件、工单和运行记录,并通过其负责人进行轮换。关于代理认证与凭据管理的文章更详细地介绍了这一流程。
关闭上下文会丢弃它的 Cookie、本地存储和缓存,之后的上下文从干净状态开始。当每项任务都应从零开始时,这正是你想要的行为;当账号会话需要在上下文之后继续存在时,这就成了问题。如果会话必须延续,请在关闭之前决定导出什么、存放在哪里、谁可以读取。按上下文代理指南介绍了如何把一个目的、一个上下文和一条路由放在一起。
一份简短的路由登记表可以让这些分配便于审阅。为每个上下文记录一个可读的路由标签、它服务的任务、负责人、需要时的预期区域,以及保存其凭据的密钥引用。请使用描述用途的标签,而不是把主机名复制到许多任务里,这样提供商更换端点时,不必修改每个模板。用途变化时,请创建新的分配,而不是悄悄沿用旧标签,因为依赖标签的审阅会因此不再可靠。
切换活动上下文的代理
运行时切换适用于同一个上下文之后需要换一条路由的工作流。拥有 ENT Tier3 许可证时,BotBrowser 文档提供 BotBrowser.setBrowserContextProxy CDP 命令,用来更改现有浏览器上下文的代理。该命令接收 browserContextId 和带嵌入凭据的 proxyServer URL,另外还可以选择传入 proxyIp、proxyBypassList 和 proxyBypassRgx。
const client = await browser.target().createCDPSession();
await client.send('BotBrowser.setBrowserContextProxy', {
browserContextId: ctx._contextId,
proxyServer: 'socks5://user:[email protected]:1080',
proxyIp: '198.51.100.1',
});
await page.goto(nextUrl);
该命令必须在浏览器级会话上发送。在 Puppeteer 中是 browser.target().createCDPSession(),在 Playwright 中是 browser.newBrowserCDPSession()。页面级会话不会暴露 BotBrowser 域,出现“找不到命令”的错误通常就是用错了会话。当许可证不包含该功能时,也可能出现同样的错误。
调用该命令时,BotBrowser 会更新该上下文的代理配置,除非你提供了 proxyIp,否则会重新检测出口 IP,并重新配置时区、区域设置和语言以匹配新的位置。命令在更新后的地理状态应用完成后结束,此后该上下文的请求会使用新代理。因此,在开始任何依赖新位置的导航之前,请先等待命令完成(await)。同一个上下文的代理更改、标志更新和清除代理应按顺序执行,并在每个命令完成后再发送下一个。
请为交接本身做好计划。已经加载的页面继续工作,切换时正在进行的请求会在原来的代理上完成。只有新的请求才使用新代理,所以这种更改既不是瞬时的,也不是没有重叠的。实际操作中,切换前先让待处理的导航完成,并暂停页面上的定时器或轮询,等命令完成后再重新导航。在切换期间一直保持打开的页面,不应被算作经由新路由而来。
提供 proxyIp 可以省去切换后第一次导航之前那次一次性的查询。当你已经从提供商那里拿到出口地址时就使用它,并确认它确实是该代理的真实出口地址,因为地理设置是根据你给出的值推导的。对于只有一个地址族地址的代理,文档允许用 ipv4_none 或 ipv6_none 来声明缺失的地址族。没有 proxyIp 时,切换后的第一次加载会稍慢一些,这是正常的,本身并不代表失败。
BotBrowser.clearBrowserContextProxy 命令会移除覆盖设置,让上下文回到浏览器级代理,在没有配置代理时则回到直连。直连可能恰恰是你不想要的,所以使用之前请先确认启动配置提供了什么。必须始终经过代理的工作流,应切换到另一个已批准的代理,而不是执行清除。
proxyBypassList 选项接收以分号分隔的、需要直连的主机列表,proxyBypassRgx 接收用于同样目的的 URL 的 RE2 模式。这些请求完全不使用代理。请让列表保持简短,把它记录为路由分配的一部分,并分别测试一个应当直连的主机和一个不应直连的主机。
希望每个任务使用不同出口的团队,可以在每次轮换时创建新的上下文,也可以在任务之间切换同一个上下文。新上下文从没有 Cookie 和缓存的状态开始,所以不会带入任何东西。被切换的上下文保留已存储的状态,这意味着已登录的账号会话会从新地址继续。这是否可接受由账号负责人决定,而且网站对这种变化的处理可能与你的预期不同。关于代理故障切换与用户可见的连续性的文章讨论了会话中途路由变化时用户会看到什么。
逐个验证每个上下文
验证的目标是,对每个上下文都能看到它自己的出口地址和你预期的区域值。每个上下文创建后单独检查一次,每次切换后再检查一次。不要从一个上下文推断另一个,因为每个上下文都有自己的路由和自己的地理设置。
检查地址时,请打开你控制或你所在组织批准的、能显示请求来源地址的服务页面,并把结果与代理提供商为该代理列出的出口地址比较。一致说明流量经由预期的路由离开;不一致则说明代理设置没有应用到该上下文。如果你配置了直连规则,请分别检查一个应当直连的主机和一个不应直连的主机。
检查时区、区域设置和语言时,请让你控制的测试页面显示浏览器报告的值,并与出口地址所在区域比较。对于带显式覆盖的上下文,则与覆盖值比较。关于时区、区域设置和语言的文章解释了这些值之间的关系,以及为什么不一致比任何单个值都更有参考价值。
切换之后,请等命令完成,再打开新页面或重新加载现有页面,然后再次检查。把切换前加载的页面视为过期。接着确认另一个方向:你没有切换的上下文应当仍然显示它们原来的地址。这项检查证明了对一个上下文的切换没有改变另一个上下文的路由。
请让证据保持精简。一条简短的记录就够了,包含路由标签、上下文的用途、观察到的出口地址、区域值、结果和时间。不要为了证明路由已应用而保存页面内容、账号数据或凭据。
请固定记录字段顺序,例如 contextId、routeLabel、observedIp、timezone、locale、language 和 checkedAt。这样可比较一次 BotBrowser.setBrowserContextProxy 前后的记录,同时不保存代理 URL 或凭据。
检查失败时,请按顺序排查原因。确认命令是在浏览器级会话上发送的,并且你的许可证包含该功能。确认代理能接受浏览器之外的简单工具发起的连接,这可以把提供商的问题与配置问题区分开。检查该上下文是否设置了显式的时区、区域设置或语言覆盖,因为覆盖值优先于从路由推导出的值。检查你提供的 proxyIp 值是否确实是该代理的真实出口地址。每次只改一处,并在每次更改后重复检查。
需要提前考虑的限制
代理切换只改变一个上下文的网络路径,其他都不变。这条路径的质量取决于代理提供商:出口地址的信誉、速度和稳定性,以及其位置登记得是否准确。切换到一个差的代理,上下文得到的就是一条差的路由,所以在基于它建立工作流之前,请用自己的测试来评估提供商。
共用出口地址只是多种关联信号之一。使用不同的地址可以减少账号之间的一类关联,但并不能控制网站如何通过账号数据、支付信息、行为、与设备相关的值,或者操作这些账号的人来关联身份。这里的任何内容都不保证网站会接受、忽略或不去关联你的上下文。请把按上下文路由用于已授权的工作,例如你自己的账号、区域质量检查和隐私研究,并遵守每个服务的条款。
请提前决定切换失败时怎么办。提供商的端点可能不可用,运行时命令也可能被拒绝。对于需要特定路由的工作流,请停止任务并记录失败,而不是继续沿用之前的路由,除非负责人已书面批准这种备用方案。
BotBrowser 不能提供或审核代理,不能改善代理的 IP 信誉或速度,不能把已在进行中的请求从原来的代理上移走,也不能保证网站在网络路径之外如何关联或接受身份。
实用结论
请为每项已授权工作指定一个有名称的上下文和路由分配。运行时切换时,等待 BotBrowser.setBrowserContextProxy 完成后加载新页面,将 observedIp 和区域值与预期路由比较;不一致就停止该工作。不要使用切换前已打开的页面做这项比较,因为它们的请求可能更早就完成了。
公开来源
本文属于指南: 代理配置总览:浏览器网络决策图