Selenium 代理认证:为什么这么难?
如果你曾尝试在 Selenium 中使用 用户名:密码 格式的代理,大概率撞过一堵墙——标准 Selenium 的 Proxy 对象只支持 host:port,不支持认证。浏览器弹出原生的认证弹窗,Selenium 无法自动填写,脚本直接卡死。
这对需要 Selenium residential proxies 的工程师来说是致命的:住宅代理几乎都要求认证,而 Selenium 原生不支持。于是你被迫寻找各种 hack——Chrome 扩展注入、AutoIt 脚本点击弹窗、甚至手动输入密码。这些方案脆弱、难维护,且在无头模式下几乎全部失效。
本文从代码出发,逐一拆解 Selenium 代理认证的六种实战方案,覆盖 Chrome、Firefox、隐身策略、代理池轮换、容器化扩展,最后对比 Playwright 帮助你做出技术选型。
方案一:Chrome + selenium-wire 拦截认证
selenium-wire 是 Selenium 的扩展库,它在 WebDriver 和浏览器之间插入一个本地代理,自动处理上游代理的 Proxy-Authorization 头。这是目前 Chrome 下最干净的认证方案。
安装与基本用法
pip install selenium-wire selenium
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options
opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--disable-blink-features=AutomationControlled")
# selenium-wire 代理配置:直接写 user:pass
proxy_config = {
"proxy": {
"http": "http://user-country-US:PASSWORD@gate.proxyhat.com:8080",
"https": "http://user-country-US:PASSWORD@gate.proxyhat.com:8080",
"no_proxy": "localhost,127.0.0.1",
}
}
driver = webdriver.Chrome(options=opts, seleniumwire_options=proxy_config)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()
关键点:selenium-wire 在本地起一个 MITM 代理(默认 localhost:8080),Chrome 连接本地代理,本地代理再连接上游并自动注入 Proxy-Authorization。你不需要写扩展、不需要处理弹窗。
注意事项
- selenium-wire 对 HTTPS 站点需要安装自签 CA 证书,否则浏览器会报证书错误。生产环境建议设置
verify_ssl=False或预装证书。 - 性能开销约 10-15%(额外一跳),大规模采集时需要评估。
- selenium-wire 维护节奏慢于 Selenium 本体,注意版本兼容。
方案二:Firefox + Profile 注入代理认证
Firefox 的优势在于:你可以直接把代理认证写入 Profile 的 prefs.js,浏览器启动时自动携带,无需额外扩展或中间代理。
import os
from selenium import webdriver
from selenium.webdriver.firefox.options import Options as FirefoxOptions
def create_firefox_proxy_profile(proxy_host, proxy_port, proxy_user, proxy_pass):
"""创建带代理认证的 Firefox Profile 目录"""
profile_dir = "/tmp/firefox_proxy_profile"
os.makedirs(profile_dir, exist_ok=True)
# 写入代理配置
prefs = f"""
user_pref("network.proxy.type", 1);
user_pref("network.proxy.http", "{proxy_host}");
user_pref("network.proxy.http_port", {proxy_port});
user_pref("network.proxy.ssl", "{proxy_host}");
user_pref("network.proxy.ssl_port", {proxy_port});
user_pref("network.proxy.no_proxies_on", "localhost, 127.0.0.1");
user_pref("network.proxy.share_proxy_settings", true);
"""
with open(os.path.join(profile_dir, "user.js"), "w") as f:
f.write(prefs)
return profile_dir
# 生成 Profile
profile_path = create_firefox_proxy_profile(
proxy_host="gate.proxyhat.com",
proxy_port=8080,
proxy_user="user-country-DE-city-berlin",
proxy_pass="PASSWORD"
)
# 启动 Firefox
opts = FirefoxOptions()
opts.add_argument("--headless")
opts.add_argument(f"-profile")
opts.add_argument(profile_path)
driver = webdriver.Firefox(options=opts)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()
Firefox 的认证弹窗可以通过 AutoAuth 扩展自动处理,但更推荐的做法是:将用户名密码编码进代理 URL 的方式不适用于 Firefox 原生设置,因此你仍然需要一个本地中间代理(如 squid 或 tinyproxy)做认证转发,或者使用 selenium-wire 的 Firefox 模式。
实战建议:如果团队已有 Squid 基础设施,在 Squid 中配置上游认证(
cache_peer+login=USER:PASS),Firefox 只需连接本地 Squid,认证完全透明。
方案三:Selenium Stealth — 降低自动化指纹
即使代理配置正确,反爬系统仍然可以通过浏览器指纹识别自动化工具:navigator.webdriver=true、缺失的 Chrome 插件、异常的 webRTC 行为等。Selenium stealth 和 selenium-driverless 是两个主流的指纹消除方案。
selenium-stealth
selenium-stealth 通过注入 JS 补丁来隐藏自动化特征:
from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.chrome.options import Options
opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=opts)
# 应用隐身补丁
stealth(driver,
languages=["en-US", "en"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
driver.get("https://bot.sannysoft.com")
print("WebDriver flag:", driver.execute_script("return navigator.webdriver"))
driver.quit()
selenium-driverless
selenium-driverless 走得更远——它通过 CDP(Chrome DevTools Protocol)直接与浏览器通信,绕过 WebDriver 协议,从根本上消除 navigator.webdriver 标志和 CDP 检测痕迹。
import asyncio
from selenium_driverless import webdriver as driverless_wd
from selenium_driverless.types.by import By
async def main():
opts = driverless_wd.ChromeOptions()
opts.add_argument("--headless=new")
# 代理配置通过 CDP 注入
opts.add_proxy(f"http://user-country-JP:PASSWORD@gate.proxyhat.com:8080")
driver = await driverless_wd.Chrome(options=opts)
await driver.get("https://nowsecure.nl")
content = await driver.page_source
print(content[:200])
await driver.quit()
asyncio.run(main())
Selenium stealth 的核心思路是打补丁,selenium-driverless 的核心思路是换通信协议。后者更彻底但对 Chrome 版本敏感,前者兼容性更好但补丁可能滞后于反爬更新。
方案四:轮换代理池 — 每个会话换 IP
大规模采集时,单一 IP 即使是住宅代理也会被限速或封禁。你需要一个轮换代理池,让每个 WebDriver 实例分配不同的出口 IP。
架构设计
核心思路:将代理池管理逻辑封装为中间件,WebDriver 工厂每次创建实例时从池中获取代理配置。
import itertools
import random
import string
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options
class ProxyPoolMiddleware:
"""轮换代理池中间件:每次创建会话分配新 IP"""
def __init__(self, username, password, gateway="gate.proxyhat.com", port=8080):
self.username = username
self.password = password
self.gateway = gateway
self.port = port
def get_proxy_config(self, country=None, city=None, sticky=False):
"""生成代理配置,支持国家/城市定向和粘性会话"""
user_parts = [self.username]
if country:
user_parts.append(f"country-{country}")
if city:
user_parts.append(f"city-{city}")
if sticky:
session_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
user_parts.append(f"session-{session_id}")
proxy_user = '-'.join(user_parts)
proxy_url = f"http://{proxy_user}:{self.password}@{self.gateway}:{self.port}"
return {
"proxy": {
"http": proxy_url,
"https": proxy_url,
"no_proxy": "localhost,127.0.0.1",
}
}
class WebDriverFactory:
"""WebDriver 工厂:集成代理池与隐身配置"""
def __init__(self, proxy_pool: ProxyPoolMiddleware):
self.proxy_pool = proxy_pool
def create_session(self, country=None, headless=True):
opts = Options()
if headless:
opts.add_argument("--headless=new")
opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_argument("--window-size=1920,1080")
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
opts.add_experimental_option("useAutomationExtension", False)
proxy_config = self.proxy_pool.get_proxy_config(country=country)
driver = webdriver.Chrome(
options=opts,
seleniumwire_options=proxy_config
)
return driver
# 使用示例
pool = ProxyPoolMiddleware(username="user", password="YOUR_PASSWORD")
factory = WebDriverFactory(pool)
# 每次创建新会话自动获得新 IP
driver1 = factory.create_session(country="US")
driver1.get("https://httpbin.org/ip")
# 同一批次中另一个会话使用不同 IP
driver2 = factory.create_session(country="DE")
driver2.get("https://httpbin.org/ip")
print(driver1.find_element("tag name", "body").text)
print(driver2.find_element("tag name", "body").text)
driver1.quit()
driver2.quit()
这个模式的关键优势:
- 每会话独立 IP:通过 ProxyHat 的
session-标记实现粘性会话,不传则每次请求自动轮换。 - 国家/城市定向:通过
country-和city-标记精确控制出口地理位置。 - 工厂模式解耦:代理逻辑与业务逻辑分离,方便替换代理供应商或切换轮换策略。
方案五:Selenium Grid + Docker 容器化并行采集
单机 Selenium 的吞吐量有限。当你需要同时运行 50-100 个浏览器实例时,Selenium Grid 是标准方案,Docker 容器化让部署和弹性扩缩变得简单。
Docker Compose 部署 Grid
# docker-compose.yml
version: '3.8'
services:
selenium-hub:
image: selenium/hub:4.18
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
environment:
- SE_SESSION_REQUEST_TIMEOUT=300
- SE_NODE_SESSION_TIMEOUT=300
chrome-node:
image: selenium/node-chrome:4.18
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
deploy:
replicas: 5 # 5 个 Chrome 节点,每个 4 会话 = 20 并发
firefox-node:
image: selenium/node-firefox:4.18
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
deploy:
replicas: 3
连接 Grid 并分配代理
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from concurrent.futures import ThreadPoolExecutor, as_completed
def scrape_with_grid(proxy_pool, target_url, country=None):
"""在 Selenium Grid 上执行采集任务"""
opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
# 通过 Chrome 扩展注入代理认证(Grid 不支持 selenium-wire)
proxy_cfg = proxy_pool.get_proxy_config(country=country)
# Grid 场景下建议使用不需要认证的白名单 IP 或本地 Squid 转发
# 此处简化为直连模式
driver = webdriver.Remote(
command_executor="http://localhost:4444/wd/hub",
options=opts
)
try:
driver.get(target_url)
return driver.title
finally:
driver.quit()
# 并行执行
targets = ["https://example.com/page1", "https://example.com/page2"] * 10
pool = ProxyPoolMiddleware(username="user", password="YOUR_PASSWORD")
with ThreadPoolExecutor(max_workers=20) as executor:
futures = {
executor.submit(scrape_with_grid, pool, url, country="US"): url
for url in targets
}
for future in as_completed(futures):
print(f"{futures[future]}: {future.result()}")
Grid + 代理的注意事项:
- Grid 节点中
selenium-wire不可用(它需要本地 Chrome 二进制),改用 Squid 本地转发或 Chrome 扩展注入认证。 - 每个容器建议限制
SE_NODE_MAX_SESSIONS为 4-6,避免内存溢出。 - 使用
--disable-dev-shm-usage和 tmpfs 或/dev/shm挂载防止 Chrome 共享内存不足。 - 考虑 Kubernetes HPA 根据 Grid 队列长度自动扩缩节点。
Playwright vs Selenium:何时切换?
Playwright 是微软推出的新一代浏览器自动化框架,在代理支持和隐身方面有原生优势。以下是详细对比:
| 维度 | Selenium | Playwright |
|---|---|---|
| 代理认证 | 原生不支持,需 selenium-wire / 扩展 / Squid | 原生支持 username:password |
| 隐身能力 | 需 selenium-stealth / driverless 补丁 | 内置 stealth 模式,指纹更少 |
| 并发模型 | Grid + 多进程,资源开销大 | 单进程多 BrowserContext,轻量 |
| 等待机制 | 显式等待 + 隐式等待,易写错 | 自动等待,API 更简洁 |
| 生态成熟度 | 20 年积累,IDE / 云服务 / 教程极丰富 | 5 年,生态快速成长但仍偏小 |
| 语言支持 | Java / Python / C# / JS / Ruby / PHP | Python / JS / Java / C# |
| 遗留系统兼容 | 大量企业测试套件依赖 Selenium | 迁移成本存在 |
| CDP 访问 | 有限(Chrome only) | 原生 CDP Session 支持 |
何时坚持 Selenium
- 团队已有大量 Selenium 测试套件,迁移成本高于收益。
- 需要 Java / Ruby / PHP 等非 Playwright 主力语言。
- 依赖 Selenium 生态的商业工具(Sauce Labs、BrowserStack、Katalon)。
- QA 团队对 Selenium IDE 录制回放有依赖。
何时切换 Playwright
- 新项目,无遗留包袱——Playwright 的代理认证和隐身是开箱即用的。
- 大规模采集场景,
BrowserContext比 Grid 节点轻 10 倍以上。 - 需要拦截/修改网络请求(等价于 selenium-wire,但原生支持)。
- 对反爬对抗要求高,Playwright 的
--disable-blink-features=AutomationControlled更彻底。
务实建议:不必非此即彼。新采集模块用 Playwright,遗留 QA 套件保留 Selenium,通过统一的代理池中间件(如本文的
ProxyPoolMiddleware)共享代理基础设施。
性能与可靠性最佳实践
并发与资源管理
- 单个 Chrome 实例约占 200-400MB 内存,8 核 16GB 机器建议最多 20 并发。
- 使用
ThreadPoolExecutor或asyncio管理会话生命周期,确保driver.quit()在 finally 块中执行。 - 设置合理的页面加载超时(
driver.set_page_load_timeout(30)),避免卡死。
代理健康检查
- 采集前先请求
httpbin.org/ip验证代理可用性和出口 IP。 - 记录每个代理的成功率和平均延迟,自动剔除低质量节点。
- 住宅代理的成功率通常 95%+,低于 90% 需排查目标站或代理配置。
反爬对抗策略
- 请求节奏:随机化页面停留时间(2-8 秒),避免固定间隔。
- User-Agent 轮换:使用
fake-useragent库或从真实 UA 池中随机选取。 - WebRTC 泄漏:Chrome 启动参数
--disable-webrtc防止真实 IP 泄漏。 - Canvas 指纹:selenium-stealth 的
fix_hairline=True可部分缓解。
合规与伦理
- 遵守目标站
robots.txt和服务条款,评估法律风险。 - 控制请求频率,避免对目标服务器造成 DDoS 效果。
- 涉及个人数据时,确保 GDPR / CCPA 合规。
Key Takeaways
- Selenium 原生不支持代理认证,Chrome 用 selenium-wire,Firefox 用 Profile + Squid 转发。
- selenium-stealth 打补丁、selenium-driverless 换协议——两者互补,根据反爬强度选择。
- 轮换代理池用工厂模式解耦,ProxyHat 的
country-/city-/session-标记实现精准地理定向和粘性会话。 - Selenium Grid + Docker 是并行采集的标准方案,但注意 Grid 下 selenium-wire 不可用。
- Playwright 在代理认证和隐身上原生更优,但 Selenium 的生态和遗留兼容仍是硬优势。
- 新项目优先 Playwright,遗留项目渐进迁移,共享代理池中间件。
如果你正在寻找高可用住宅代理来配合 Selenium 采集,欢迎查看 ProxyHat 定价方案,支持 195+ 国家定向、自动 IP 轮换和粘性会话,与 selenium-wire 和 Playwright 无缝集成。更多采集场景参考 网页采集用例 和 SERP 追踪用例。






