---
title: "Playwright 如何按域名分流？代理规则与浏览器配置实践"
source: https://www.iowill.com/blog/2026/11/playwright-domain-proxy-routing/
date: 2026-11-06
updated: 2026-11-07
tags: [Chromium, Playwright, Python, 代理]
categories: [编程开发]
---

# Playwright 如何按域名分流？代理规则与浏览器配置实践

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

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

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

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

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

[Playwright 网络文档](https://playwright.dev/python/docs/network#http-proxy)提供浏览器级和 BrowserContext 级代理配置。一个会话始终使用同一个出口时，直接配置已经足够；需要独立会话时，再结合不同 Context 或持久化目录管理。

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

```python
# Chromium Context 配置片段；browser 由调用方提供。
context = browser.new_context(
    proxy={
        "server": "socks5://127.0.0.1:19090",
        "bypass": "<-loopback>",
    }
)
```

Chromium 默认让 `localhost`、回环及部分链路本地目标绕过代理。这里的 `<-loopback>` 用于取消这组隐式绕过规则，让访问本地目标的连接也进入案例的规则判断；它不是“绕过本地代理服务器”。这与 dyDM 的租约配置一致，尤其影响本地夹具验证。该语法针对 Chromium，不应直接推广到所有浏览器；是否代理本地资源应由应用明确决定。[Chromium 的隐式绕过规则说明](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#implicit-bypass-rules)

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

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

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

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

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

```text
浏览器
  → 本地 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](https://www.rfc-editor.org/rfc/rfc1928.html)。如果客户端交来的是 IP，当前案例不会通过反向 DNS 或 TLS SNI 恢复原始域名。

命中 HTTP 上游后，应用发送 CONNECT；上游接受隧道，再向浏览器报告 SOCKS5 连接成功。目标网站的 TLS 仍由浏览器完成，应用只转发字节，不安装解密证书。[RFC 9110 的 CONNECT 定义](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)用于支持这一协议解释，不能据此证明某个上游实际可达。

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

![浏览器、本地SOCKS5入口、规则匹配和上游或直连的连接路径](https://img.iowill.com/file/%E7%BC%96%E7%A8%8B%E5%BC%80%E5%8F%91/PlaywrightDomainRouting/body1.png)

*图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`：

```python
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 字面值，并让预览与运行时复用同一套匹配逻辑。不能把未校验数据直接交给示例函数，再把错误解释成默认直连。

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

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

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

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

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

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

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

![精确例外、后缀规则、未匹配动作和代理失败行为](https://img.iowill.com/file/%E7%BC%96%E7%A8%8B%E5%BC%80%E5%8F%91/PlaywrightDomainRouting/body2.png)

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

PAC 也需要分别检查脚本获取失败、旧脚本重新获取和返回代理不可达等条件。[Chromium 代理文档](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md)描述了这些机制及条件差异。案例中的历史 PAC 反例解释了项目取舍，但不构成“PAC 在所有环境都不可靠”的结论。

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

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

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

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

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

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

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

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

当前案例的本地服务处理 TCP，不实现 SOCKS5 UDP ASSOCIATE，不自动接管 Python 下载或整机网络。WebRTC 的限制与验证需另行进行，可先参考[已有的 WebRTC 泄漏机制文章](https://www.iowill.com/blog/2026/10/webrtc-ip-leak/)。

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

## 参考资料

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

1.  [Playwright：HTTP Proxy](https://playwright.dev/python/docs/network#http-proxy)，代理配置层次。
2.  [Playwright：BrowserContext](https://playwright.dev/python/docs/api/class-browsercontext)，会话与初始化能力。
3.  [Chromium：Proxy support in Chrome](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md)，浏览器代理和PAC行为。
4.  [RFC 1928：SOCKS5](https://www.rfc-editor.org/rfc/rfc1928.html)，目标地址、命令及协议边界。
5.  [RFC 9110：CONNECT](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)，HTTP隧道语义。
