一、selenium架构详解/为什么多了一层
核心问题:为什么说 Selenium “多了一层”?
让我们先对比 CDP 和 Selenium 的架构:
CDP 架构(2 层)
Python 代码 ↓ WebSocket(直连) Chrome 浏览器Selenium 架构(3 层,Selenium 3)
Python 代码 ↓ HTTP chromedriver(中间层!) ↓ CDP Chrome 浏览器关键区别:Selenium 多了chromedriver这一层!
Selenium WebDriver 完整架构(Selenium 3)
┌─────────────────────────────────────────────────────────┐ │ 第 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 代码:
fromseleniumimportwebdriver# 创建 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 的工作流程
# 当你执行这行代码driver.get('https://www.baidu.com')# 实际发生的事情:1. Client Library → chromedriver(HTTP 请求)
POST http://localhost:9515/session/xxx/url HTTP/1.1 Content-Type: application/json { "url": "https://www.baidu.com" }2. chromedriver 内部(解析 + 转换)
// 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 返回结果:
{"id":1,"result":{"frameId":"xxx","loaderId":"yyy"}}5. chromedriver → Client(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 |
实际测试
importtime# CDP (Playwright)fromplaywright.sync_apiimportsync_playwright start=time.time()withsync_playwright()asp:browser=p.chromium.launch()page=browser.new_page()page.goto('https://www.baidu.com')print(f'CDP:{time.time()-start:.2f}s')# ~0.5s# Seleniumfromseleniumimportwebdriver start=time.time()driver=webdriver.Chrome()driver.get('https://www.baidu.com')print(f'Selenium:{time.time()-start:.2f}s')# ~1.2sSelenium 4 的改进
Selenium 4 移除了 JSON Wire Protocol,直接使用W3C WebDriver Protocol:
Selenium 3: Client → JSON Wire → chromedriver → CDP → Chrome (自定义协议) Selenium 4: Client → W3C WebDriver → chromedriver → CDP → Chrome (标准化协议,更快)改进:
- 标准化通信协议(W3C 标准)
- 更好的跨浏览器兼容性
- 稍微提升了性能(但仍然慢于直接 CDP)
chromedriver 的内部实现(C++ 源码)
chromedriver 是开源的,位于 Chromium 仓库中:
// chromium/src/chrome/test/chromedriver/server/http_handler.ccvoidHttpHandler::HandleRequest(constHttpRequest&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);}elseif(path.find("/url")!=std::string::npos){Navigate(body,response);}// ... 其他命令}voidHttpHandler::Navigate(conststd::string&body,HttpResponse*response){// 3. 解析 JSONbase::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 发送到 Chromedevtools_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)
┌──────────┐ │ Python │ └────┬─────┘ │ WebSocket (直连) ▼ ┌──────────┐ │ Chrome │ └──────────┘Selenium
┌──────────┐ │ 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:零基础爬虫第一天