Python Selenium 反检测指南:如何绕过网站的自动化检测?
你用 Selenium 写了个脚本,本地跑得好好的。换个网站,直接被拦了。不是代码写错了,是网站知道你在用 Selenium。
如果你还没用过 Selenium,先看上一篇 👉 《Python Selenium 自动化入门》
网站怎么发现你的?
navigator.webdriver
最蠢也最有效的检测。Selenium 启动的 Chrome 会把 navigator.webdriver 设成 true,正常浏览器是 undefined。网站检测就一行:
if (navigator.webdriver) {
// 拜拜了您嘞
}
ChromeDriver 的痕迹
ChromeDriver 启动时会往浏览器里塞一些内部变量,比如 document.$cdc_ 开头的东西。正常浏览器里不会有。
浏览器指纹
正常用户的指纹是自洽的。Windows 用户的 UA 里有 "Windows NT",navigator.platform 是 "Win32",插件列表不为空,屏幕分辨率是 1920×1080 这种常见值。而 Selenium 默认启动的浏览器呢?无头模式下 navigator.plugins 是空的,platform 可能和 UA 对不上。一眼假。
行为分析
鼠标瞬移到按钮上点击,每次操作间隔一模一样,从来不滚动页面。人不会这样操作,机器会。前三种是静态检测,技术上能绕过。最后一种是行为检测,得靠模拟真实操作,难度最高。
三种方案,怎么选
方案一:stealth.min.js 注入
来自 puppeteer-extra-plugin-stealth(7.2k+ ⭐),16 个反检测模块,覆盖 navigator.webdriver、chrome.runtime、插件列表、语言、权限、窗口尺寸等几乎所有静态检测点。通过 CDP 注入:
with open("stealth.min.js", "r") as f:
stealth_js = f.read()
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": stealth_js})
覆盖全,可控性强,和原版 Selenium 完全兼容。缺点是需要自己生成 stealth.min.js 文件(下一节讲怎么生成)。
方案二:undetected-chromedriver
undetected-chromedriver 走了另一条路。它直接改 ChromeDriver 的二进制文件,把 $cdc_ 这些特征字符串抹掉。
import undetected_chromedriver as uc
driver = uc.Chrome()
两行代码,不用管什么 stealth.js。但它只处理 ChromeDriver 层的特征,浏览器指纹不管。
方案三:SeleniumBase UC 模式
SeleniumBase 在 undetected-chromedriver 基础上包了一层,反检测能力更强一些。
from seleniumbase import SB
with SB(uc=True) as sb:
sb.driver.uc_open_with_reconnect("https://example.com", 5)
但 API 换了一套,不是标准的 Selenium 写法。
对比
| stealth.js | undetected-chromedriver | SeleniumBase UC | |
|---|---|---|---|
| 覆盖检测点 | ✅ 16 个模块 | ⚠️ 仅 ChromeDriver 特征 | ✅ 较全面 |
| 自定义灵活度 | 高 | 低 | 中 |
| 和标准 Selenium 兼容 | ✅ | ⚠️ API 有差异 | ⚠️ 自己的 API |
| 适合 | 需要精细控制的项目 | 快速验证 | 中等复杂度 |
我们的 Rainyun-Qiandao 签到项目 选了方案一。因为要给每个账号生成独立指纹,这个需求只有 stealth.js + 手动注入能做到。
stealth.min.js 怎么来的?
这个文件大概 180KB,包含 16 个反检测脚本。不是手写的。stealth 插件本来是给 Puppeteer 用的,浏览器打开页面时,插件通过 page.evaluateOnNewDocument() 注入反检测脚本。而 extract-stealth-evasions 这个工具做了一件事:把 evaluateOnNewDocument 替换成一个假的,只录不跑。
// 猴子补丁:把"执行"换成"录音"
page.__proto__.evaluateOnNewDocument = function(func, args) {
scripts += '(' + func.toString() + ')(' + JSON.stringify(args) + ');\n'
}
流程:启动 Puppeteer + stealth 插件 → 替换 evaluateOnNewDocument → 打开空白页触发插件 → 16 个脚本全被假方法截获写进 scripts 变量 → 关浏览器(白开了,就为触发插件) → Terser 压缩 → 输出 stealth.min.js。一行命令生成:
npx extract-stealth-evasions
生成出来的是一段纯 JS,虽然从 Puppeteer 生态里提取的,但通过 CDP 塞给 Selenium 驱动的 Chrome 一样能跑。
指纹伪装
抹掉 navigator.webdriver 只是第一步。10 个自动化任务用同一套指纹跑,网站一样能把你关联起来。我们在签到项目里做了账号专属指纹:基于账号 ID 生成确定性的参数,同一账号每次跑指纹一致,不同账号各不相同。
user_agent = get_random_user_agent(account_id)
options.add_argument(f"--user-agent={user_agent}")
fingerprint_js = generate_fingerprint_script(current_user)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": fingerprint_js})
伪装最重要的一点是自洽。UA 写的是 Windows,navigator.platform 返回 Linux,比不伪装还可疑。要一起动的参数:
| 参数 | 要求 |
|---|---|
| User-Agent | 和 platform、版本号对得上 |
navigator.platform | 和 UA 里的系统匹配 |
| 屏幕分辨率 | 别用奇怪的数值 |
navigator.languages | 和地区一致 |
| CPU 核心数、内存 | 别填 0 |
几个容易踩的坑
stealth.js 注入了还是被检测。 大概率是在 driver.get() 之后才注入的。Page.addScriptToEvaluateOnNewDocument 要在访问目标网页之前调用,不然第一个页面加载时脚本还没生效。
无头模式更容易被检测。 确实。--headless=new 比旧版好,但不完美。如果过不去,先用有头模式跑一次,确认是无头的问题还是别的原因。
指纹做得再好,IP 不行也白搭。 同一个 IP 短时间大量请求,什么指纹都救不了你。多账号场景建议一号一 IP。
每次都重新登录容易触发风控。 把 Cookie 存下来复用,能省很多事。
这些技术在我们的开源项目里全部有落地:stealth.js 注入、账号指纹、Cookie 缓存、代理 IP 轮换 👉 Rainyun-Qiandao:雨云全自动签到
想了解验证码是怎么自动破解的? 👉 《雨云签到验证码怎么破?2026最新自动识别方案》
⚠️ 免责声明:本文介绍的反检测技术仅供学习浏览器自动化和网络安全攻防原理。请勿将其用于绕过网站安全措施进行未授权操作,使用时请遵守目标网站的服务条款。