这是每个浏览器自动化开发者都会恰好踩一次的坑:你往 Chrome 的启动参数里加了 --proxy-server=http://user:pass@host:port,浏览器正常启动了,然后每个页面都卡在一个你脚本看不见也点不到的认证弹窗上。Chrome 默默地把代理参数里的凭证给剥掉了。没有报错,没有日志,就只有一个弹在没人盯着的浏览器里的模态对话框。
这份指南是 2026 年在 Selenium 和 Playwright 里真正能用的代理认证方法的完整地图——该用哪个、该躲哪个,以及每个的确切代码。(关于这些工具里的代理选择与轮换策略,见 在 Playwright 和 Puppeteer 里使用代理——本文只讲怎么把凭证送进去。)
命令行 HTTP 客户端会从代理 URL 里解析出凭证,并自动用一个 Proxy-Authorization 头来应答代理的 407 Proxy Authentication Required。Chromium 不会:--proxy-server 只接受 scheme://host:port,当 407 回来时它会弹出一个交互式对话框。在 headless 模式下连对话框都没有——请求直接失败。
所以问题从来不是"我怎么把密码放进 URL"——而是"哪一层替我应答这个 407"。四个能用的答案,最好的在前。
Playwright 把这个问题正经地解决了:它通过 CDP 自己应答代理认证质询。凭证放进启动选项或 context 选项里:
# Python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": "http://us.jibaoproxy.com:913",
"username": "USERNAME-session-job1",
"password": "PASSWORD",
}
)
// Node.js - 以及按 context 设置,这才是它强大的地方:
const browser = await chromium.launch({
proxy: { server: 'per-context' } // 在启动时声明意图
});
const ctx = await browser.newContext({
proxy: {
server: 'http://us.jibaoproxy.com:913',
username: 'USERNAME-session-job2',
password: 'PASSWORD',
}
});
按 context 设置代理,意味着一个浏览器进程可以在 N 个不同的 sticky 会话上跑 N 个 context——这是每套正经 Playwright 爬取方案的基石。如果你用的是 Playwright,那你搞定了;直接跳到验证一节。下面这一切之所以存在,是因为 Selenium 没有等价物。
selenium-wire 用一个本地 MITM 代理包住 Selenium,由它来处理上游认证:
from seleniumwire import webdriver # pip install selenium-wire
options = {
"proxy": {
"http": "http://USERNAME:[email protected]:913",
"https": "https://USERNAME:[email protected]:913",
}
}
driver = webdriver.Chrome(seleniumwire_options=options)
driver.get("https://example.com")
上生产之前你该知道的几个注意点:
经典做法:一个动态生成的小扩展,它设置代理并通过 chrome.webRequest.onAuthRequired 应答认证。没有 MITM,没有额外进程,在纯 Selenium 里就能用:
import zipfile, json
HOST, PORT = "us.jibaoproxy.com", 913
USER, PASS = "USERNAME-session-sel1", "PASSWORD"
manifest = {
"version": "1.0.0", "manifest_version": 2, "name": "Proxy Auth",
"permissions": ["proxy", "webRequest", "webRequestBlocking", ""],
"background": {"scripts": ["background.js"]},
}
background = f"""
chrome.proxy.settings.set({{value: {{mode: "fixed_servers", rules: {{
singleProxy: {{scheme: "http", host: "{HOST}", port: {PORT}}}
}}}}, scope: "regular"}}, () => {{}});
chrome.webRequest.onAuthRequired.addListener(
() => ({{authCredentials: {{username: "{USER}", password: "{PASS}"}}}}),
{{urls: [""]}}, ["blocking"]
);
"""
with zipfile.ZipFile("proxy_auth.zip", "w") as zp:
zp.writestr("manifest.json", json.dumps(manifest))
zp.writestr("background.js", background)
from selenium import webdriver
opts = webdriver.ChromeOptions()
opts.add_extension("proxy_auth.zip")
driver = webdriver.Chrome(options=opts)
两个坑:扩展在经典 headless 模式下不加载——用 --headless=new(Chrome 109+)或者以有头方式运行;还有,Manifest V2 的 background 脚本对旁加载的自动化扩展仍然可用,但要留意 Chrome 的 MV2 弃用时间表——MV3 的等价做法需要一个 service worker 和同样的 onAuthRequired 监听器。
跑一个持有凭证的小型本地代理;让浏览器指向 localhost、完全不带认证。它对每种浏览器、每个驱动、每种语言都管用,无论 headless 与否:
# 用 pproxy (pip install pproxy) - 终端 1:
pproxy -l http://127.0.0.1:8899 \
-r "http://USERNAME:[email protected]:913"
# 你的脚本 - 终端 2:纯 Selenium,不需要认证
opts = webdriver.ChromeOptions()
opts.add_argument("--proxy-server=http://127.0.0.1:8899")
driver = webdriver.Chrome(options=opts)
这也是 Firefox/geckodriver(扩展那一招在这里不适用)以及像 Electron 应用测试这类特殊环境里最干净的答案。代价:多了一个需要照看的活动部件,而且按 context 路由时每个会话需要一个转发器端口。
| 方法 | 适用于 | Headless | 按 context 会话 | 生产裁决 |
|---|---|---|---|---|
| Playwright 原生 | Playwright | 是 | 是 | 默认首选 |
| selenium-wire | Selenium (Py) | 是 | 按驱动 | 中等规模够用 |
| 认证扩展 | Selenium(任意语言) | 仅 --headless=new | 按驱动 | 稳妥,留意 MV3 |
| 本地转发器 | 一切 | 是 | 每会话一个端口 | 万能兜底 |
静默回退到你的真实 IP,是会让账号被封的那种失败模式。在每次运行开始时断言出口 IP:
ip = driver.execute_script(
"return fetch('https://api.ipify.org').then(r => r.text())"
) if hasattr(driver, 'execute_script') else page.evaluate(
"fetch('https://api.ipify.org').then(r => r.text())"
)
assert ip != MY_REAL_IP, "代理未生效 - 在泄露之前中止"
--proxy-server 里传了凭证;Chrome 把它们丢了。改用四种方法之一。ERR_PROXY_CONNECTION_FAILED——host/port 写错了或协议不匹配。先用 curl -x 测同一行;如果 curl 能通,那问题出在你的浏览器接线上。--headless=new。浏览器不读 user:pass@ 形式的代理 URL——必须得有东西替它们应答 407。在 Playwright 里这个东西是内建的;在 Selenium 里它是 selenium-wire、一个生成的认证扩展,或者一个本地转发器。按你的技术栈从对比表里挑一个,然后在每次运行时都断言出口 IP,这样一次静默的认证失败就永远不可能在爬取中途泄露你的真实地址。
新用户注册即送500M免费流量,首次充值额外加赠,活动期间限时开放。