有两种协议控制 Chrome 的自动化:WebDriver(W3C 标准,由 Selenium 使用)和 Chrome DevTools 协议(CDP,由 Puppeteer/Playwright 使用)。每个都有不同的检测配置文件、功能和验证码处理特性。
协议架构
WebDriver (Selenium):
Script ──HTTP──▶ ChromeDriver ──DevTools──▶ Chrome
(JSON Wire) (bridge) (internal)
CDP (Puppeteer/Playwright):
Script ──WebSocket──▶ Chrome
(direct) (no middle layer)
头对头比较
| 特征 | 网络驱动程序 | CDP |
|---|---|---|
| 协议 | HTTP+JSON | WebSocket |
| 标准 | W3C标准 | Chrome 专用 |
| 检测表面 | 高 (navigator.userAgent = true) |
较低(可修补) |
| 网络拦截 | 否(需要Selenium Wire) | 内置(Fetch、Network) |
| JavaScript 评估 | executeScript(包装) |
Runtime.evaluate(直接) |
| 请求修改 | 不 | 是的 |
| 响应修改 | 不 | 是的 |
| 连接开销 | 每个命令的 HTTP | 持久WebSocket |
| 跨浏览器 | Chrome、火狐、Safari、Edge | 仅限 Chrome/Chromium |
| 隐身潜力 | 低(始终设置 webdriver 标志) | 高(可修补标志) |
检测差异
WebDriver 检测(Selenium)
// What sites check for Selenium
navigator.userAgent // true (set by ChromeDriver)
document.$cdc_asdjflasutopfhvcZLmcfl // ChromeDriver internal variable
window.callSelenium // Older Selenium versions
window._selenium // Selenium internals
document.__webdriver_evaluate // WebDriver namespace
# Even with flag removal, ChromeDriver leaves traces
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome(options=options)
# Still detectable via $cdc variables in document
CDP 检测(Puppeteer)
// Puppeteer sets webdriver flag too, but it's fully patchable
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
// No $cdc variables to worry about
// No Selenium-specific traces
});
验证码解决:WebDriver 方法
# Selenium (WebDriver) + CaptchaAI
import requests
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
API_KEY = "YOUR_API_KEY"
API_URL = "https://ocr.captchaai.com"
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com/form")
time.sleep(2)
# Extract sitekey
sitekey = driver.execute_script(
"return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
)
# Solve via CaptchaAI
resp = requests.post(f"{API_URL}/in.php", data={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": driver.current_url, "json": 1,
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(5)
resp = requests.get(f"{API_URL}/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
})
data = resp.json()
if data["request"] != "CAPCHA_NOT_READY":
break
token = data["request"]
# Inject token
driver.execute_script(f"""
document.querySelector('#g-recaptcha-response').value = '{token}';
document.querySelectorAll('[name="g-recaptcha-response"]')
.forEach(el => {{ el.value = '{token}'; }});
""")
driver.find_element(By.CSS_SELECTOR, "form").submit()
driver.quit()
验证码解决:CDP 方法
// Puppeteer (CDP) + CaptchaAI
const puppeteer = require("puppeteer-extra");
const StealthPlugin = require("puppeteer (默认配置)");
const https = require("https");
puppeteer.use(StealthPlugin());
const API_KEY = "YOUR_API_KEY";
const API_URL = "https://ocr.captchaai.com";
function solveCaptcha(siteUrl, sitekey) {
// Submit + poll (simplified)
return new Promise(async (resolve, reject) => {
const submitResp = await httpPost(`${API_URL}/in.php`, {
key: API_KEY, method: "userrecaptcha",
googlekey: sitekey, pageurl: siteUrl, json: "1",
});
const taskId = submitResp.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const result = await httpGet(
`${API_URL}/res.php?key=${API_KEY}&action=get&id=${taskId}&json=1`
);
if (result.request !== "CAPCHA_NOT_READY") {
return resolve(result.request);
}
}
reject(new Error("Timeout"));
});
}
(async () => {
const browser = await puppeteer.launch({
headless: "new",
args: ["--no-sandbox", "--window-size=1920,1080"],
});
const page = await browser.newPage();
// CDP: Enable network interception
const cdp = await page.createCDPSession();
await cdp.send("Network.enable");
// Monitor CAPTCHA-related requests
cdp.on("Network.requestWillBeSent", (params) => {
if (params.request.url.includes("recaptcha")) {
console.log(`CAPTCHA request: ${params.request.url}`);
}
});
await page.goto("https://example.com/form", { waitUntil: "networkidle0" });
const sitekey = await page.evaluate(() =>
document.querySelector("[data-sitekey]")?.getAttribute("data-sitekey")
);
if (sitekey) {
const token = await solveCaptcha(page.url(), sitekey);
// CDP: Inject via Runtime.evaluate (lower-level than page.evaluate)
await cdp.send("Runtime.evaluate", {
expression: `
document.querySelector('#g-recaptcha-response').value = '${token}';
document.querySelector('form').submit();
`,
});
}
await browser.close();
})();
验证码工作流程的功能比较
网络拦截
# WebDriver: Cannot intercept requests natively
# Requires selenium-wire (third-party)
from seleniumwire import webdriver
driver = webdriver.Chrome()
# selenium-wire proxies all traffic through a local proxy
// CDP: Built-in request interception
await cdp.send("Fetch.enable", {
patterns: [{ urlPattern: "*recaptcha*" }],
});
cdp.on("Fetch.requestPaused", async ({ requestId, request }) => {
console.log(`Intercepted: ${request.url}`);
await cdp.send("Fetch.continueRequest", { requestId });
});
响应修改
// CDP: Can modify responses (WebDriver cannot)
cdp.on("Fetch.requestPaused", async ({ requestId, request }) => {
if (request.url.includes("captcha-config")) {
// Return modified response
await cdp.send("Fetch.fulfillRequest", {
requestId,
responseCode: 200,
body: btoa(JSON.stringify({ enabled: false })),
});
} else {
await cdp.send("Fetch.continueRequest", { requestId });
}
});
Cookie 操作
# WebDriver: Standard cookie API
driver.add_cookie({"name": "session", "value": "abc123"})
cookies = driver.get_cookies()
// CDP: Full cookie control including httpOnly
await cdp.send("Network.setCookie", {
name: "session",
value: "abc123",
domain: "example.com",
httpOnly: true,
secure: true,
});
// Get all cookies including httpOnly (WebDriver can't access these)
const { cookies } = await cdp.send("Network.getAllCookies");
决策矩阵
| 设想 | 受到推崇的 | 为什么 |
|---|---|---|
| 多浏览器测试 | 网络驱动程序 | 跨浏览器支持 |
| 最大隐身性 | CDP | 下检测面 |
| 网络调试 | CDP | 内置拦截 |
| 团队不熟悉 CDP | 网络驱动程序 | 更简单的 API,更多文档 |
| 高规模刮削 | CDP | 降低每个会话的开销 |
| CI/CD 集成 | 网络驱动程序 | 更好的工具支持 |
| 验证码较多的网站 | CDP + CaptchaAI | 潜行+解决组合 |
| 简单的表格填写 | WebDriver + CaptchaAI | 更容易实施 |
混合方法:WebDriver + CDP
Selenium 4 通过 BiDi(双向)支持 CDP 命令:
from selenium import webdriver
driver = webdriver.Chrome()
# Use WebDriver for navigation
driver.get("https://example.com")
# Use CDP for QA patches
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
"""
})
# Use CDP for network events
driver.execute_cdp_cmd("Network.enable", {})
性能基准
| 公制 | 网络驱动程序(硒) | CDP(Puppeteer) | CDP(剧作家) |
|---|---|---|---|
| 会话开始 | ~2.5秒 | ~1.5秒 | ~1.2秒 |
| 页面导航 | ~500ms 开销 | ~100ms 开销 | ~80ms 开销 |
| 脚本执行 | 每次调用约 200 毫秒 | 每次调用约 50 毫秒 | 每次调用约 40 毫秒 |
| 每个会话的内存 | 〜350MB | 〜280MB | 约260MB |
| 验证码解决(CaptchaAI) | 15-30秒 | 15-30秒 | 15-30秒 |
CaptchaAI 求解时间是相同的 — 这是独立于浏览器协议的 API 调用。
故障排除
| 问题 | 网络驱动修复 | CDP修复 |
|---|---|---|
| 检测为机器人 | excludeSwitches + 选项 |
隐形插件+补丁 |
| 无法拦截请求 | 使用Selenium Wire | 使用Fetch.enable |
| 执行缓慢 | 减少 execute_script 调用 |
批量CDP命令 |
| token 提交失败 | 检查 iframe 上下文 | 在上下文中使用 Runtime.evaluate |
| 会话不稳定 | 更新 Chrome 驱动程序 | 检查 WebSocket 连接 |
常问问题
哪种协议的验证码成功率更高?
成功率是相同的——CaptchaAI 解决服务器端问题。该协议仅影响检测频率(验证码出现的频率)。
我可以混合使用 WebDriver 和 CDP 吗?
是的。 Selenium 4 公开了 execute_cdp_cmd()。使用 WebDriver 进行导航,使用 CDP 进行标准补丁。
剧作家比两者都好吗?
Playwright 在内部使用 CDP,但提供了具有自动等待和更好的错误处理功能的更高级别的 API。这是一个很好的中间立场。
我应该从 Selenium 切换到 Puppeteer 来进行验证码工作吗?
如果检测是您的主要问题,那么是的。如果您需要跨浏览器支持,请继续使用 Selenium + CaptchaAI。
相关指南
为您的验证码工作流程选择正确的自动化协议 —获取您的 CaptchaAI 密钥并解决验证码,无论协议如何。