一、selenium架构详解/为什么多了一层
核心问题:为什么说 Selenium "多了一层"?
让我们先对比 CDP 和 Selenium 的架构:
CDP 架构(2 层)
plain
Python 代码
↓ WebSocket(直连)
Chrome 浏览器
Selenium 架构(3 层,Selenium 3)
plain
Python 代码
↓ HTTP
chromedriver(中间层!)
↓ CDP
Chrome 浏览器
关键区别 :Selenium 多了 chromedriver 这一层!
Selenium WebDriver 完整架构(Selenium 3)
plain
┌─────────────────────────────────────────────────────────┐
│ 第 1 层:Client Library │
│ (Python, Java, C#, etc.) │
│ │
│ from selenium import webdriver │
│ driver = webdriver.Chrome() │
│ driver.get('https://example.com') │
└────────────────────┬────────────────────────────────────┘
│
│ JSON Wire Protocol over HTTP
│ (REST API 调用)
│
▼
┌─────────────────────────────────────────────────────────┐
│ 第 2 层:Browser Driver(中间层) │
│ (chromedriver, geckodriver, etc.) │
│ │
│ - 接收 HTTP 请求 │
│ - 解析 JSON 命令 │
│ - 转换为浏览器特定的命令 │
│ - 内部使用 CDP/DevTools 与浏览器通信 │
└────────────────────┬────────────────────────────────────┘
│
│ CDP (Chrome DevTools Protocol)
│ 或其他浏览器原生协议
│
▼
┌─────────────────────────────────────────────────────────┐
│ 第 3 层:Real Browser │
│ (Chrome, Firefox, Safari, etc.) │
│ │
│ - 真实的浏览器实例 │
│ - 执行 DOM 操作、JS 代码等 │
└─────────────────────────────────────────────────────────┘
详细解析每一层
第 1 层:Selenium Client Library
这是你写的 Python/Java 代码:
python
from selenium import webdriver
# 创建 driver 实例
driver = webdriver.Chrome()
# 执行操作
driver.get('https://www.baidu.com')
element = driver.find_element('id', 'kw')
element.send_keys('hello')
element.submit()
功能:
- 提供面向对象的 API
- 将你的命令转换为 HTTP 请求
- 支持多种语言(Python, Java, C#, Ruby, JavaScript)
第 2 层:Browser Driver(关键的"中间层")
这是 chromedriver.exe 、geckodriver.exe 等独立的可执行文件。
为什么需要这一层?
历史原因:
- Selenium 最初设计时,浏览器还没有标准化的自动化协议
- 每个浏览器的内部机制不同(Chrome 用 CDP,Firefox 用 Marionette)
- Selenium 需要一个"翻译层"来统一这些差异
chromedriver 的工作流程
python
# 当你执行这行代码
driver.get('https://www.baidu.com')
# 实际发生的事情:
1. Client Library → chromedriver(HTTP 请求)
http
POST http://localhost:9515/session/xxx/url HTTP/1.1
Content-Type: application/json
{
"url": "https://www.baidu.com"
}
2. chromedriver 内部(解析 + 转换)
javascript
// chromedriver 收到 JSON 请求
// 解析命令:"navigate to URL"
// 转换为 CDP 命令
// 通过 CDP 发送到 Chrome:
{
"id": 1,
"method": "Page.navigate",
"params": {"url": "https://www.baidu.com"}
}
3. chromedriver → Chrome(CDP WebSocket)
chromedriver 内部维护了一个到 Chrome 的 WebSocket 连接,使用 CDP 协议通信。
4. Chrome 执行 → 返回结果
Chrome 导航到页面后,通过 CDP 返回结果:
json
{
"id": 1,
"result": {"frameId": "xxx", "loaderId": "yyy"}
}
5. chromedriver → Client(HTTP 响应)
http
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": 0,
"value": null
}
为什么 Selenium 比 CDP 慢?
正是因为这个"中间层"!
性能对比
| 操作 | CDP (直连) | Selenium (3 层) |
|---|---|---|
| 通信路径 | Python → Chrome | Python → chromedriver → Chrome |
| 协议转换 | 0 次 | 2 次(JSON Wire ↔ CDP) |
| 网络开销 | 1 次 WebSocket | 2 次(HTTP + WebSocket) |
| 延迟 | ~5ms | ~15-30ms |
实际测试
python
import time
# CDP (Playwright)
from playwright.sync_api import sync_playwright
start = time.time()
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto('https://www.baidu.com')
print(f'CDP: {time.time() - start:.2f}s') # ~0.5s
# Selenium
from selenium import webdriver
start = time.time()
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
print(f'Selenium: {time.time() - start:.2f}s') # ~1.2s
Selenium 4 的改进
Selenium 4 移除了 JSON Wire Protocol,直接使用 W3C WebDriver Protocol:
plain
Selenium 3:
Client → JSON Wire → chromedriver → CDP → Chrome
(自定义协议)
Selenium 4:
Client → W3C WebDriver → chromedriver → CDP → Chrome
(标准化协议,更快)
改进:
- 标准化通信协议(W3C 标准)
- 更好的跨浏览器兼容性
- 稍微提升了性能(但仍然慢于直接 CDP)
chromedriver 的内部实现(C++ 源码)
chromedriver 是开源的,位于 Chromium 仓库中:
cpp
// chromium/src/chrome/test/chromedriver/server/http_handler.cc
void HttpHandler::HandleRequest(const HttpRequest& request,
HttpResponse* response) {
// 1. 解析 HTTP 请求
std::string method = request.method;
std::string path = request.path;
std::string body = request.body;
// 2. 根据 WebDriver 命令分发
if (path == "/session") {
CreateSession(body, response);
} else if (path.find("/url") != std::string::npos) {
Navigate(body, response);
}
// ... 其他命令
}
void HttpHandler::Navigate(const std::string& body,
HttpResponse* response) {
// 3. 解析 JSON
base::Value parsed = base::JSONReader::Read(body);
std::string url = parsed.FindKey("url")->GetString();
// 4. 转换为 CDP 命令
base::Value cdp_command;
cdp_command.SetKey("method", "Page.navigate");
cdp_command.SetKey("params", base::Value::Dict().Set("url", url));
// 5. 通过 DevToolsClient 发送到 Chrome
devtools_client_->SendCommand(cdp_command);
// 6. 等待响应并返回
// ...
}
关键文件:
chrome/test/chromedriver/server/- HTTP 服务器chrome/test/chromedriver/chrome/- CDP 客户端chrome/test/chromedriver/commands.cc- 命令实现
为什么不能去掉 chromedriver?
你可能会问:既然 chromedriver 内部用的是 CDP,为什么不直接用 CDP?
Selenium 的优势(尽管慢)
- 跨浏览器统一接口
- 一套代码支持 Chrome、Firefox、Safari、Edge
- 不需要关心底层协议差异
- W3C 标准
- WebDriver 是 W3C 官方标准
- 大量工具和生态支持
- 历史兼容性
- 海量现有代码和教程
- 企业级稳定性
CDP 的优势(为什么更快)
- 直连
- 没有中间层
- 减少协议转换开销
- 完整功能
- CDP 提供比 WebDriver 更多的功能
- 网络拦截、性能监控、覆盖率等
- 原生性能
- Chrome 官方协议
- 最优化的通信
总结对比
CDP(DrissionPage, Playwright, Puppeteer)
plain
┌──────────┐
│ Python │
└────┬─────┘
│ WebSocket (直连)
▼
┌──────────┐
│ Chrome │
└──────────┘
Selenium
plain
┌──────────┐
│ Python │
└────┬─────┘
│ HTTP
▼
┌──────────┐
│chromedriver│ ← 多了这一层!
└────┬─────┘
│ CDP
▼
┌──────────┐
│ Chrome │
└──────────┘
二、selenium和drissionpage 相比多了哪些风险点
Selenium 的检测风险主要源于它必须遵守 W3C WebDriver 标准 ,以及它为了实现跨浏览器控制而引入的中间层(ChromeDriver)特征。
相比 DrissionPage,Selenium 多了以下几个致命的风险监测点:
1. 致命特征:navigator.webdriver
这是最基础也是最直接的检测点。
- Selenium:
根据 W3C 协议规定,凡是通过 WebDriver 控制的浏览器,必须 将 navigator.webdriver 属性设为 true。- 风险: 网站 JS 只需要一行代码 if (navigator.webdriver) { 杀无赦 }。虽然可以通过 JS 注入去修改它,但在 Selenium 中,页面加载和 JS 注入之间存在时间差(Race Condition),在页面初始化的瞬间(Script Execution Start),这个值为 true,极易被捕捉。
- DrissionPage:
由于它直接通过 CDP 的 Page.addScriptToEvaluateOnNewDocument 在页面加载前注入 JS,它可以完美地将这个值设为 undefined,让网页完全感知不到。
2. 源代码特征:CDC 变量 (CDC_xxxxxxxx)
这是 Selenium(特指 ChromeDriver)独有的深层特征。
- Selenium:
ChromeDriver 为了在浏览器内部执行 JavaScript 并与外部通信,会在浏览器的 JS 上下文中注入特定的全局变量。最著名的就是以 cdc<fontstyle="color:rgb(26,28,30);"></font><fontstyle="color:rgb(26,28,30);">开头的变量(例如</font><fontstyle="color:rgb(26,28,30);"></font>cdc_<font style="color:rgb(26, 28, 30);"> </font><font style="color:rgb(26, 28, 30);">开头的变量(例如</font><font style="color:rgb(26, 28, 30);"> </font>cdc<fontstyle="color:rgb(26,28,30);"></font><fontstyle="color:rgb(26,28,30);">开头的变量(例如</font><fontstyle="color:rgb(26,28,30);"></font>cdc_asdjflasutopfhvcZLmcfl_)。- 风险 : 高级反爬虫(如 Akamai, Cloudflare)会扫描 window 对象下的属性。如果发现了 cdc<fontstyle="color:rgb(26,28,30);"></font><fontstyle="color:rgb(26,28,30);">或</font><fontstyle="color:rgb(26,28,30);"></font>cdc_<font style="color:rgb(26, 28, 30);"> </font><font style="color:rgb(26, 28, 30);">或</font><font style="color:rgb(26, 28, 30);"> </font>cdc<fontstyle="color:rgb(26,28,30);"></font><fontstyle="color:rgb(26,28,30);">或</font><fontstyle="color:rgb(26,28,30);"></font>wdc_ 开头的变量,直接判定为 Selenium。
- 补救困难: 除非你用 16 进制编辑器修改 chromedriver.exe 二进制文件中的字符串,否则很难去除。
- DrissionPage:
完全没有这个问题。因为它不使用 ChromeDriver,是通过 WebSocket 发送纯 CDP 指令,不需要在 JS 上下文里塞这种"联络暗号"。
3. 命令执行差异:isTrusted 事件属性
这涉及到模拟点击和按键的真实性。
- Selenium:
Selenium 的 click() 或 send_keys() 生成的事件,虽然现在的 Driver 已经做得很好,但在某些复杂的 JS 验证下,生成的 Event 对象中的 isTrusted 属性可能表现异常,或者缺少某些物理硬件关联的属性(如 pointerId, pressure 等)。 - DrissionPage:
它提供了两种模式:- 浏览器模拟: 通过 CDP 的 Input.dispatchMouseEvent,这和 Selenium 类似,有一定风险。
- 更底层的控制: 它更容易结合 Python 的系统级输入库,或者通过 CDP 构建更完美的事件链,甚至通过"接管模式"让用户手动操作过验证码,程序接管后续流程。
4. 浏览器的"纯净度"与启动参数
- Selenium:
默认情况下,Selenium 每次启动都是一个新的、干净的浏览器实例(临时 Profile)。- 风险: 没有历史记录、没有缓存、没有 Cookie、Local Storage 为空。这种"白板"浏览器在风控系统眼里非常可疑(像是刚出生的婴儿却在做复杂操作)。
- 此外,Selenium 启动时默认带有大量特征参数,如 --enable-automation, --test-type。
- DrissionPage:
- 非常擅长接管模式。你可以先手动打开一个浏览器(积累了正常的用户行为、缓存、Cookie),然后 DrissionPage 通过端口连上去。
- 优势 : 在这种情况下,你的浏览器指纹是完美的真实用户指纹,Selenium 很难做到如此丝滑的接管。
5. 通信时序(Timing Attack)
我们在上一节讨论过架构差异,这直接导致了时序特征。
- Selenium:
Python -> HTTP -> ChromeDriver -> CDP -> Chrome。- 风险 : 这种长链路导致命令执行有固定的延迟模式(RTT)。某些高阶反爬(如瑞数)会统计你的 JS 执行时间、鼠标移动轨迹的采样率。如果发现操作之间有极其稳定的"机器延迟",就会触发风控。(不确定真假,ai 给的 -)
- DrissionPage:
Python -> WebSocket -> Chrome。- 优势: 通信延迟极低,并发能力强。你可以编写更复杂的鼠标轨迹算法,以极高的频率发送 CDP 包来模拟人类的抖动和变速,这在 Selenium 中因为 HTTP 瓶颈很难做到。
6. User-Agent 和 Headless 特征
虽然两者都可以改 UA,但 Selenium 在 Headless(无头)模式下的特征暴露得更彻底。
- Selenium:
早期版本在 Headless 模式下,User-Agent 会包含 HeadlessChrome 字样。虽然现在可以改,但配合 navigator.plugins(无插件)、navigator.languages(语言单一)等特征,极易被识别。 - DrissionPage:
它封装了很多便捷的配置(如 Stealth 模式),自动帮你处理了 Headless 模式下的很多特征(自动补全插件列表、语言、WebGL 指纹噪声等),而 Selenium 原生是不管这些的,需要你自己写大量的 execute_script 去补。
总结表格
| 检测点 | Selenium (WebDriver) | DrissionPage (CDP) | 风险等级 (Selenium) |
|---|---|---|---|
| Navigator.webdriver | 强制为 true (极难修改) | 可设为 undefined | 🔴 极高 |
| JS 注入变量 ($cdc_) | 存在 (需修改二进制去除) | 不存在 | 🔴 极高 |
| 启动参数 | 默认含 --enable-automation | 可完全自定义/无自动化Flag | 🟠 高 |
| 时序特征 | HTTP 延迟大,操作僵硬 | WebSocket 低延迟,更拟人 | 🟠 高 |
| 接管现有浏览器 | 困难 (需特定配置) | 原生支持,极简 | 🟡 中 |
| 浏览器指纹 | 默认"白板"环境 | 易继承真实环境 | 🟡 中 |
结论:
Selenium 的风险在于**"它太标准了"。它是为了测试而生的,所以它诚实地告诉浏览器"我是机器人"。而 DrissionPage 是为了自动化(和爬虫)而生的,它的设计初衷就是 "控制"而非"测试"**,所以它避开了那些自我暴露的协议规范。
更多文章,敬请关注gzh:零基础爬虫第一天