默认headless模式易被识别因暴露navigator.webdriver为true等指纹特征;需注入JS覆盖webdriver、补全plugins等属性,并匹配UA与Chrome版本,配合真实行为模拟。

单纯加 --headless 参数几乎一定会被识别,现代反爬系统(如 Cloudflare、Akamai、PerimeterX)能轻易检测出未配置的无头 Chrome 实例。
为什么默认 headless 会被识别?
标准 Selenium + ChromeDriver 启动的无头模式会暴露大量指纹特征:缺失 window.chrome、navigator.webdriver 为 true、缺少真实用户代理的 GPU/平台字段、plugins 和 mimeTypes 为空等。这些在 navigator 对象和 WebDriver 属性中直接可读。
常见错误现象包括:document.documentElement.outerHTML 中出现 headless 字符串、get_browser_fingerprint() 类工具直接返回“HeadlessChrome”、请求被 403 或跳转到验证页。
- 不要只依赖
options.add_argument('--headless=new') - 必须覆盖
webdriver属性并注入真实浏览器特征 - 启用
--disable-blink-features=AutomationControlled是基础,但远远不够
关键补丁:绕过 navigator.webdriver 和 chrome 检测
目标网站通常用 if (navigator.webdriver) 或 if (window.chrome && window.chrome.runtime) 做第一道判断。需在页面加载前注入 JS 覆盖这些属性。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 使用
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {...})注入初始化脚本 - 将
navigator.webdriver设为undefined(不能设为false,部分检测会查类型) - 删除
chrome对象上的runtime和loadTimes属性 - 补全
navigator.plugins和navigator.mimeTypes(哪怕只伪造 2–3 个)
示例片段:
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
window.chrome = {runtime: {}};
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3].map(() => ({length: 1, filename: ''}))
});
'''
})
User-Agent 和启动参数必须匹配真实环境
设置 User-Agent 字符串只是第一步;如果 UA 是 Chrome 124,但 --version 返回 120,或 screen.availHeight 是 768 却声明 deviceScaleFactor=2,就会触发设备指纹矛盾检测。
实操建议:
- 用最新稳定版 Chrome(非 Chromium),并通过
options.binary_location显式指定路径 - UA 必须与本地 Chrome 版本一致,可通过
chrome --version和http://httpbin.org/user-agent验证 - 添加
--force-device-scale-factor=1和--disable-setuid-sandbox(Linux)避免沙箱报错 - 禁用
--enable-automation(它会自动注入cdc_变量,极易被识别)
真实行为模拟比隐藏更关键
即使指纹完美,如果页面加载后立刻执行 click()、不触发 mousemove、不等待 document.readyState === 'complete' 就发请求,仍会被行为分析模型标记。
容易被忽略的点:
- 用
driver.get()后,显式等待document.readyState和document.body存在,再操作 - 避免
find_element(By.ID, ...).click()这类“瞬杀”操作;改用ActionChains(driver).move_to_element(el).click().perform() - 对输入框,用
send_keys()逐字发送(加time.sleep(0.05)模拟打字节奏),而非一次性.send_keys('xxx') - 每次会话结束后调用
driver.quit(),不要复用 driver 实例——session 复用是强异常信号
复杂点在于:没有银弹配置。同一套参数在 A 站可用,在 B 站可能因 JS 加载顺序或第三方 SDK 差异而失败。最稳的做法是先人工用真实 Chrome DevTools 录制行为,再逆向还原成 Selenium 可控步骤。


















