Playwright 按域名分流,可以让浏览器连接一个固定代理入口,再由入口根据目标主机选择直连或不同上游。 简单场景也可以使用浏览器已有的代理和绕过配置;只有需要多组规则、明确的匹配顺序与失败策略时,才值得增加应用自己的路由层。

多个账号的浏览器目录解决 Cookie、站点存储等会话隔离;域名分流解决连接路径。目录独立不等于出口不同,指定一个代理端口也不等于页面关联的所有请求都采用预期路径。

本文结合个人桌面工具 dyDM 的私有实现,统一以提交 56e25be7f9792a4245f343408bbb061df59bbf7e 为代码阅读基线。公开原理与原创教学示例供读者复核,私有项目的历史网络测试不作为本次重测结果。本文不提供完整生产代理服务,也不接管整机流量。

这里的实现对应开发源码,不表示正式安装包已经包含全部能力。2026年11月7日复核到的提交 895a6f9 已包含可选的本地代理拒绝连接后直连,差异在失败策略一节单独说明。

浏览器原生代理配置适合哪些场景

Playwright 网络文档提供浏览器级和 BrowserContext 级代理配置。一个会话始终使用同一个出口时,直接配置已经足够;需要独立会话时,再结合不同 Context 或持久化目录管理。

以下是配置形式示意,127.0.0.1:19090 是虚构端口,需要由真实可用的代理服务提供。代码没有实现代理,也不是执行后即可联网的完整程序:

1
2
3
4
5
6
7
# Chromium Context 配置片段;browser 由调用方提供。
context = browser.new_context(
proxy={
"server": "socks5://127.0.0.1:19090",
"bypass": "<-loopback>",
}
)

Chromium 默认让 localhost、回环及部分链路本地目标绕过代理。这里的 <-loopback> 用于取消这组隐式绕过规则,让访问本地目标的连接也进入案例的规则判断;它不是“绕过本地代理服务器”。这与 dyDM 的租约配置一致,尤其影响本地夹具验证。该语法针对 Chromium,不应直接推广到所有浏览器;是否代理本地资源应由应用明确决定。Chromium 的隐式绕过规则说明

如果只有少量“这些目标绕过代理”的条件,也应先评估原生 bypass 是否已经满足要求。不要把每个项目都写一个本地服务当作 Playwright 的必要步骤。

私有案例选择增加路由层,原因是它同时需要按账号引用规则组、支持精确例外和后缀规则、保留运行版本,并在命中代理却连接失败时返回错误。这些是应用约定,不能自动由一个地址输入框表达。

context.route() 则用于请求拦截、修改、模拟或继续处理 HTTP 请求,与建立通用 TCP 代理是不同层次。这里讨论的是连接下一跳,不是通过模拟响应改变页面内容。

用一次新连接理解本地路由入口

假设浏览器请求 https://www.example.com/article,需要新建连接。应用给浏览器的地址是本地 SOCKS5 入口,用户真正配置的上游则是另一个 HTTP 代理端口:

1
2
3
4
5
浏览器
→ 本地 SOCKS5 入口 127.0.0.1:19090
→ 按 www.example.com 选择代理 A
→ HTTP 上游 127.0.0.1:18080
→ www.example.com:443

SOCKS5 请求包含目标地址与端口;本地入口从中得到 www.example.com:443,并不读取 HTTPS 中的 /article。主机名如何编码、IPv4/IPv6/域名地址类型如何区分,见 RFC 1928。如果客户端交来的是 IP,当前案例不会通过反向 DNS 或 TLS SNI 恢复原始域名。

命中 HTTP 上游后,应用发送 CONNECT;上游接受隧道,再向浏览器报告 SOCKS5 连接成功。目标网站的 TLS 仍由浏览器完成,应用只转发字节,不安装解密证书。RFC 9110 的 CONNECT 定义用于支持这一协议解释,不能据此证明某个上游实际可达。

如果新连接的目标没有命中代理规则,应用可以直接连接目标。浏览器仍经过本地入口,入口之后的动作才是直连。 这与浏览器完全绕过本地入口不同。

浏览器、本地SOCKS5入口、规则匹配和上游或直连的连接路径

图1:原创连接示意,描述一次新建 TCP 连接,不是抓包结果。

一个页面还会加载接口、图片、媒体和登录资源。这些连接使用各自的目标主机匹配规则,不能因为主页面属于 example.com,就假设另一个接口域名也会走同一个代理。连接复用还会影响新规则何时有机会参与选择。

规则先后顺序比名称更重要

规则组至少需要表达:匹配对象、启用状态、动作、优先顺序和未匹配动作。服务器与规则组采用稳定 ID,账号引用 ID,名称只用于展示;改名不应破坏绑定。

例如希望一个接口直连、其余子域走代理:

顺序 匹配条件 动作
1 精确域名 api.example.com 直连
2 example.com 本域及子域 代理 A
未匹配 没有启用规则命中 直连

精确例外必须放在宽泛后缀前。后缀判断也要遵守标签边界:a.example.com 属于该后缀,notexample.com 不属于。

下面是独立教学示例,只支持精确域名和域名后缀,输入限制为合法 ASCII 域名;不是从私有项目复制的完整匹配器。将它保存为 rules_example.py,运行 python rules_example.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def choose_action(host, rules, fallback="direct"):
host = host.strip().rstrip(".").lower()
for rule in rules:
if not rule["enabled"]:
continue
pattern = rule["pattern"]
if rule["match"] == "exact":
matched = host == pattern
elif rule["match"] == "suffix":
matched = host == pattern or host.endswith("." + pattern)
else:
raise ValueError("unsupported match type")
if matched:
return rule["action"]
return fallback


rules = [
{"enabled": True, "match": "exact",
"pattern": "api.example.com", "action": "direct"},
{"enabled": True, "match": "suffix",
"pattern": "example.com", "action": "proxy-a"},
]
assert choose_action("API.EXAMPLE.COM.", rules) == "direct"
assert choose_action("cdn.example.com", rules) == "proxy-a"
assert choose_action("notexample.com", rules) == "direct"
assert choose_action("other.test", rules, "proxy-b") == "proxy-b"

真实实现还需在保存阶段校验类型、目标引用、ID、端口及域名,处理 IDNA 和 IP 字面值,并让预览与运行时复用同一套匹配逻辑。不能把未校验数据直接交给示例函数,再把错误解释成默认直连。

未匹配直连与代理失败直连是两种策略

未匹配动作是用户事先选定的规则;命中代理后再改为直连,则是连接失败时改变了路径。

1
2
没有规则命中 → 执行该组的未匹配动作
已经命中代理,但连接或握手失败 → 返回连接错误

私有案例选择第二种情况不尝试直连。这个结论只描述应用中对应连接分支的行为,不能扩大成浏览器或系统所有网络路径都获得了相同保证。

上述流程和图2对应 56e25be 基线。后续开发版本(核对至 895a6f9)允许对单个本地代理明确开启 direct_on_local_refused:旧配置缺失该字段时保持关闭;只有代理地址为回环 IP、连接代理自身端口明确被拒绝,且尚未发送代理协议数据时,才尝试一次直连目标。远程代理、连接超时、认证或 CONNECT 拒绝、证书错误,以及连接建立后的故障,都不触发这一例外。每个新连接仍先尝试原代理,不会永久切换到直连。

回退采用系统路由,实际出口可能是本地网络,也可能受 VPN 或其他隧道影响,不能保证仍使用指定代理出口。需要固定代理出口时应保持关闭;此选项也不表示应用会自动重放已经提交的业务操作。

配置缺失和配置损坏也要区分。缺少整个配置段,可以按明确的初始约定创建默认组;已经存在却解析失败、格式版本未知或引用不存在时,应阻止相应启动并提示修复。把两者都处理成空字典,会让一个配置错误悄悄变成路径选择。

精确例外、后缀规则、未匹配动作和代理失败行为

图2:原创规则示意;图中域名、动作和服务器名称均为教学数据。

PAC 也需要分别检查脚本获取失败、旧脚本重新获取和返回代理不可达等条件。Chromium 代理文档描述了这些机制及条件差异。案例中的历史 PAC 反例解释了项目取舍,但不构成“PAC 在所有环境都不可靠”的结论。

保存新规则为什么不改变旧浏览器

案例采用启动快照:浏览器启动时读取账号最新绑定,校验规则,固定配置版本,取得对应本地入口,然后再创建浏览器。已运行的浏览器继续使用自己的版本,新启动的浏览器才采用保存值。

这样可以避免一个正在运行的任务在新旧路径间突然切换。它不是热更新的唯一设计;如果产品确实需要立即切换,应另外处理已有连接、重试、任务状态和用户确认,不能把“保存成功”直接等同于所有连接已更新。

默认组表示账号没有单独绑定时继承哪个组;未匹配动作表示当前组没有规则命中时怎么办。默认组同样可以包含代理规则,不能把“默认”当作“直连”的别名。

浏览器会话也不应因为继承规则而复制。重新登录可以继承原账号的规则来源,同时使用独立候选目录;规则归属与 Cookie 归属分别管理。

验证入口、上游和出口需要不同证据

检查 可以支持的结论 仍不能证明
浏览器访问本地专用探针并返回正确版本 到达预期入口和快照 远端代理可达
上游夹具收到连接,目标端记录对应请求 指定连接经过该上游 所有页面子资源都被覆盖
关闭上游后,目标端没有收到绕过请求 已测失败路径未转直连 系统全部流量都无法绕过
指定检测接口返回预期地址 该次接口请求的出口符合预期 WebRTC、其他应用和 DNS 路径相同

“页面返回 200”可能来自代理,也可能来自直连,不能单独证明规则有效。排查 IP 不符时,应先确认实际检测接口的域名、当前快照和命中规则,再检查上游出网与其他网络路径。

当前案例的本地服务处理 TCP,不实现 SOCKS5 UDP ASSOCIATE,不自动接管 Python 下载或整机网络。WebRTC 的限制与验证需另行进行,可先参考已有的 WebRTC 泄漏机制文章。

本地入口还增加了应用自身的资源责任:监听器、并发连接、握手和空闲超时、DNS 查询、取消和退出都需要管理。只绑定回环地址,也不等于获得本机用户隔离。本文介绍连接模型和规则约定,不声称私有实现已完成打包环境及长期压力验证。

参考资料

资料核对日期为2026年11月6日。前述规则示例为作者原创;私有案例仅提供脱敏机制描述,不以内部链接充当公开证据。

  1. Playwright:HTTP Proxy,代理配置层次。
  2. Playwright:BrowserContext,会话与初始化能力。
  3. Chromium:Proxy support in Chrome,浏览器代理和PAC行为。
  4. RFC 1928:SOCKS5,目标地址、命令及协议边界。
  5. RFC 9110:CONNECT,HTTP隧道语义。