Selenium 代理认证与隐身爬虫指南

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 追踪用例。