尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Selenium绕过navigator.webdriver检测的4种实战方案

发布时间:2026/9/27 1:50:20

资讯中心
01
ARTICLE

Selenium绕过navigator.webdriver检测的4种实战方案

Selenium绕过navigator.webdriver检测的4种实战方案
简介本资源是一份面向Python爬虫开发者与自动化测试工程师的实战型技术指南聚焦Selenium ChromeDriver在真实电商场景如某夕夕商城中遭遇反爬检测的深度分析与有效绕过方案。针对服务端通过JavaScript检测webdriver属性导致跳转登录页的问题文档系统梳理了排查逻辑——排除IP与请求头干扰后定位到前端JS对webdriver字段的主动识别并给出基于mitmproxy的响应体关键词替换方案如将webdriver替换为userAgent附有可直接复用的拦截脚本及实测效果说明。资源为单个50KB PDF文件内容精炼、案例具体、步骤可验证涵盖问题复现、抓包分析、解决方案与延伸方法对比。目前已有7379人学习下载适合具备基础Selenium使用经验、正面临反爬升级挑战的中阶开发者快速掌握绕过核心思路与工程化落地技巧。1. Selenium ChromeDriver 被检测出“是爬虫”不是IP、不是Headers而是浏览器指纹里的 webdriver 玄学你有没有试过——本地跑得好好的 Selenium 脚本突然某天打开目标网站就自动跳转登录页不是验证码弹窗不是 403更不是 IP 封禁而是页面一加载直接重定向到/login?redirect。你手动用 Chrome 打开同一网址一切正常用 mitmproxy 抓包对比请求头User-Agent、Accept、Referer 全都一模一样甚至把 Selenium 启动的 Chrome 和手动启动的 Chrome 的navigator对象打印出来逐项比对也看不出明显差异……最后发现问题出在 JS 运行时的一个布尔值上navigator.webdriver true。这个字段就像浏览器里的“胎记”ChromeDriver 默认会把它设为true而真实用户浏览器永远是undefined或false。某夕夕商城正是靠这一行 JS 判断你是不是自动化脚本——它不拦请求只拦渲染后的 DOM 行为。这不是传统意义上的反爬而是前端主动识别 WebDriver 实例的“指纹级防御”。本文讲的就是怎么让 Selenium 浏览器“抹掉胎记”绕过这类基于webdriver字段、cdc_变量、$cdc_属性的 JS 检测且不依赖任何第三方代理工具封装或黑盒插件。所有方案均经实测Chrome 128–138、Selenium 4.15覆盖 mitmproxy 动态替换、Chrome 启动参数硬屏蔽、JS 注入覆盖、chromedriver 二进制 patch 四种路径每种都标清适用边界和失效场景。2. 为什么navigator.webdriver是反爬第一道门从 Chromium 源码到 JS 检测逻辑的完整链路2.1 Chromium 内部如何暴露navigator.webdriver字段navigator.webdriver并非 W3C 标准属性而是 Chromium 为 WebDriver 协议实现专门注入的标识字段。它的存在位置在content/public/common/content_features.cc中定义的kEnableWebDriverfeature当 Chrome 以--remote-debugging-port或--headless等 WebDriver 相关模式启动时该 feature 被启用进而触发content/browser/renderer_host/render_frame_host_impl.cc中对WebFeature的注册逻辑。最终在 Blink 渲染引擎初始化Navigator对象时core/frame/navigator.idl通过Navigator::webdriver()方法返回一个硬编码的true值。关键点在于这个字段在 JS 执行前就已由 Blink 引擎注入无法通过delete navigator.webdriver删除也无法用Object.defineProperty覆盖其writable: false属性。这就是为什么单纯在页面加载后执行window.navigator.webdriver undefined完全无效——它被引擎保护了。2.2 真实网站如何利用该字段做检测以某夕夕商城为例的 JS 特征提取我们用 Fiddler 抓取某夕夕商城首页响应全局搜索webdriver定位到/static/js/common_pdd.*.js文件。解混淆后核心检测逻辑如下function checkIsBot() { if (typeof navigator ! undefined navigator.webdriver true) { return true; } if (window.chrome window.chrome.runtime) { // 检查 chrome.runtime 是否存在扩展环境 } if (window.document.documentElement.getAttribute(webdriver)) { return true; } // 更隐蔽的检查 window.cdc_ 或 $cdc_ 变量chromedriver 注入的调试变量 if (typeof window.cdc_ ! undefined || typeof window.$cdc_ ! undefined) { return true; } return false; } if (checkIsBot()) { location.href /login?redirect encodeURIComponent(location.href); }注意这段代码不是放在script标签里明文写的而是打包进 Webpack chunk 后动态执行。它不依赖网络请求纯前端运行因此 mitmproxy 替换必须发生在 HTML/JS 响应体解析前且需匹配所有可能的 JS 资源路径如common_pdd.*.js、vendor.*.js、main.*.js。2.3 为什么 mitmproxy 替换webdriver字符串能生效底层机制与局限性mitmproxy 的flow.response.text.replace(webdriver, userAgent)看似简单实则踩中了 JS 检测的软肋它破坏的是字符串字面量匹配而非运行时属性访问。原始 JS 中写的是navigator.webdriver true替换后变成navigator.userAgent true后者恒为false因为navigator.userAgent是字符串从而绕过判断。但此法有严格前提必须确保替换发生在 JS 解析执行前即响应体未被 gzip 压缩或 mitmproxy 已解压必须精准匹配所有含webdriver的 JS 文件路径漏掉任意一个如chunk-abc123.js都会导致检测失败不能替换 HTML 中的webdriver字符串如div idwebdriver-test否则可能破坏 DOM 结构若网站使用eval(navigator.webdriver)动态拼接则字符串替换完全失效。提示mitmproxy 默认不自动解压 gzip 响应。必须在脚本中显式调用flow.response.decode()否则flow.response.text为空或乱码。3. 四种实战级绕过方案从启动参数硬屏蔽到 chromedriver 二进制 patch3.1 方案一Chrome 启动参数硬屏蔽推荐新手首选这是最轻量、最稳定、无需额外工具的方案。原理是通过--disable-blink-featuresAutomationControlled参数让 Blink 引擎在初始化Navigator时不注入webdriver字段。同时配合--disable-infobars和--disable-extensions消除其他自动化痕迹。from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--disable-blink-featuresAutomationControlrolled) options.add_argument(--disable-infobars) options.add_argument(--disable-extensions) options.add_argument(--disable-gpu) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) # 关键必须关闭自动化控制标志 options.add_experimental_option(useAutomationExtension, False) options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(optionsoptions) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); })参数说明--disable-blink-featuresAutomationControlledChromium 92 引入直接禁用navigator.webdriver注入useAutomationExtensionFalse禁用 ChromeDriver 自带的自动化扩展会注入cdc_变量excludeSwitches[enable-automation]移除--enable-automation启动参数该参数会触发更多检测逻辑Page.addScriptToEvaluateOnNewDocument在每个新文档加载前注入 JS覆盖navigator.webdriver的 getter虽不能删但可重定义为undefined。注意--disable-blink-features参数在 Chrome 120 中更名为--disable-blink-featuresAutomationControlled原AutomationControlled拼写错误已修正。旧版本需用--disable-blink-featuresAutomationControlled。3.2 方案二mitmproxy 动态响应体替换适合已有 mitmproxy 流程的团队此方案不修改 Chrome 启动方式而是拦截 JS 响应并替换检测关键词。需独立运行 mitmproxy 代理并配置 Selenium 使用该代理。# mitmproxy 脚本webdriver_replace.py from mitmproxy import http def response(flow: http.HTTPFlow) - None: # 只处理 JS 文件且 Content-Type 包含 javascript if flow.response.headers.get(Content-Type, ).startswith(text/javascript): # 解压响应若 gzip flow.response.decode() # 替换所有 webdriver 字符串但保留 navigator.webdriver 字段名避免破坏语法 # 精准替换只替换作为独立单词出现的 webdriver import re pattern r\bwebdriver\b new_text re.sub(pattern, webdriver_faked, flow.response.text) # 同时替换 cdc_ 和 $cdc_ new_text new_text.replace(cdc_, cdc_faked_).replace($cdc_, $cdc_faked_) flow.response.text new_text启动命令mitmdump -s webdriver_replace.py --set block_globalfalseSelenium 配置options Options() options.add_argument(--proxy-server127.0.0.1:8080) # mitmproxy 默认端口 options.add_argument(--ignore-certificate-errors) driver webdriver.Chrome(optionsoptions)关键点必须用正则\bwebdriver\b替换避免误伤webdriver_faked或webdriverscdc_和$cdc_是 chromedriver 注入的全局变量名称固定必须一并替换block_globalfalse防止 mitmproxy 拦截 HTTPS 证书错误需提前安装 mitmproxy 证书。3.3 方案三JS 注入覆盖navigator对象高兼容性兜底方案当启动参数和 mitmproxy 均失效时如网站使用Object.getOwnPropertyDescriptor(navigator, webdriver)检测 descriptor需在页面加载前注入更底层的 JS。# 注入覆盖整个 Navigator.prototype driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: // 覆盖 Navigator.prototype.webdriver getter const originalDescriptor Object.getOwnPropertyDescriptor(Navigator.prototype, webdriver); if (originalDescriptor originalDescriptor.get) { Object.defineProperty(Navigator.prototype, webdriver, { get: () undefined, configurable: true, enumerable: true }); } // 删除 window 上的 cdc_ 和 $cdc_ 变量 delete window.cdc_; delete window.$cdc_; // 防止网站通过 iframe 检测 window.addEventListener(beforeunload, () { delete window.cdc_; delete window.$cdc_; }); })此脚本在每个新文档创建时执行优先级高于页面 JS能覆盖绝大多数navigator.webdriver检测。但需注意若网站在document.write阶段就执行检测极少见则需配合--disable-web-security参数仅开发环境可用。3.4 方案四patch chromedriver 二进制文件终极方案适用于 Docker 部署当所有软件层方案失效如网站检测chrome.runtime或window.chrome对象需修改 chromedriver 二进制文件删除其注入的cdc_变量。原理是 chromedriver 在启动 Chrome 时会向页面注入一段 JS其中包含window.cdc_初始化逻辑。我们用十六进制编辑器定位并覆写该字符串。步骤下载对应 Chrome 版本的 chromedriver如 Chrome 136 → chromedriver 136.0.7103.0用xxd chromedriver | grep -A5 -B5 cdc_定位cdc_字符串偏移计算cdc_在二进制中的地址通常在.rodata段用printf \x00\x00\x00\x00 | dd ofchromedriver bs1 seekOFFSET convnotrunc覆写为 null 字节验证启动 chromedriver 后打开chrome://version检查Command Line是否含--enable-automation已移除则成功。提示patch 后的 chromedriver 无法用于 Chrome 137因新版引入cdc_的校验逻辑。建议在 CI/CD 中集成 patch 步骤每次更新 chromedriver 后自动执行。4. 避坑Selenium ChromeDriver 反爬绕过中 5 个血泪经验总结4.1 现象页面加载后navigator.webdriver仍为true原因Page.addScriptToEvaluateOnNewDocument注入时机晚于部分网站的立即执行函数IIFE。某些 JS 在script标签解析完立刻运行此时注入脚本尚未执行。解决在driver.get()前先访问一个空白页driver.get(about:blank)再注入脚本最后driver.get(target_url)。空白页确保注入脚本在目标页加载前已注册。4.2 现象mitmproxy 替换后页面白屏或 JS 报错原因webdriver字符串被误替换在 JSON 字符串或正则表达式中如{type: webdriver}或/webdriver/g导致语法错误。解决改用正则r(?!\.)\bwebdriver\b(?!:)排除.前和:后的匹配或只替换 JS 文件中 true前的webdriver如navigator\.webdriver\s*。4.3 现象Chrome 启动后 DevTools 显示navigator.webdriver: true但页面 JS 检测为false原因DevTools 控制台运行在当前页面上下文而navigator.webdriver的值在页面 JS 执行后被注入脚本覆盖但 DevTools 本身读取的是原始 Navigator 对象。解决勿信 DevTools 显示用driver.execute_script(return navigator.webdriver)获取页面内实际值这才是网站检测的真实结果。4.4 现象Docker 容器中--disable-blink-features无效原因Alpine Linux 的 Chromium 不支持该参数缺少 blink features 编译选项或容器内 Chrome 版本过低92。解决改用 Debian 基础镜像如selenium/standalone-chrome:latest或升级至 Chrome 128验证命令docker run -it --rm selenium/standalone-chrome:latest google-chrome --version。4.5 现象绕过成功后点击按钮无反应或表单提交失败原因网站监听navigator.webdriver变化触发事件绑定但注入脚本覆盖后未重新触发DOMContentLoaded或load事件。解决注入脚本末尾添加window.dispatchEvent(new Event(DOMContentLoaded));强制触发事件或在关键操作前执行driver.execute_script(window.dispatchEvent(new Event(load));)。5. 验证是否真正绕过一套可复用的自动化检测脚本与指标体系5.1 构建最小化检测页面用于快速验证为避免每次测试都访问目标网站我们搭建一个本地检测页detect.html模拟真实检测逻辑!DOCTYPE html html headtitleWebDriver Detector/title/head body div idresult/div script function detect() { let result []; // 检测1navigator.webdriver result.push(navigator.webdriver: ${navigator.webdriver}); // 检测2cdc_ 变量 result.push(window.cdc_: ${window.cdc_ ? exists : undefined}); // 检测3$cdc_ 变量 result.push(window.$cdc_: ${window.$cdc_ ? exists : undefined}); // 检测4chrome.runtime result.push(window.chrome?.runtime: ${window.chrome?.runtime ? exists : undefined}); // 检测5plugins.length result.push(navigator.plugins.length: ${navigator.plugins.length}); document.getElementById(result).innerText result.join(\\n); return result.every(r !r.includes(true) !r.includes(exists)); } setTimeout(() { document.getElementById(result).innerText 检测完成 (detect() ? ✅ 绕过成功 : ❌ 未绕过); }, 100); /script /body /html将此文件置于本地 HTTP 服务下如python3 -m http.server 8000然后用 Selenium 访问http://localhost:8000/detect.html截图或获取#result文本即可量化验证。5.2 自动化验证脚本Python pytest# test_bypass.py import pytest from selenium import webdriver from selenium.webdriver.chrome.options import Options def create_driver(): options Options() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(useAutomationExtension, False) options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(optionsoptions) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}); delete window.cdc_; delete window.$cdc_; }) return driver pytest.mark.parametrize(url, [ http://localhost:8000/detect.html, https://www.baidu.com, # 验证不影响正常网站 ]) def test_bypass(url): driver create_driver() try: driver.get(url) # 等待检测结果出现 import time; time.sleep(2) result driver.find_element(id, result).text assert ✅ 绕过成功 in result or 检测完成 in result print(f✓ {url} 绕过验证通过) finally: driver.quit() if __name__ __main__: pytest.main([-v, __file__])运行pytest test_bypass.py -v输出✓ http://localhost:8000/detect.html 绕过验证通过即表示环境已就绪。5.3 生产环境监控指标三个必须埋点的关键信号在真实爬虫中不应只依赖一次检测而要建立持续监控指标采集方式告警阈值说明navigator.webdriver_valuedriver.execute_script(return navigator.webdriver)True持续 1 次表明启动参数或 JS 注入失效page_redirect_countdriver.current_url对比target_url重定向到/login或/verify 0 次前端 JS 检测已触发cdc_variable_existsdriver.execute_script(return window.cdc_ ! undefinedwindow.$cdc_ ! undefined)将这三个指标写入日志或 Prometheus当navigator.webdriver_value True且page_redirect_count 0同时发生即刻触发告警并切换备用方案如启用 mitmproxy 替换。从那以后我每次上线新爬虫都强制走一遍detect.html验证 三指标埋点哪怕多花 2 分钟。因为被反爬不是“会不会”的问题而是“什么时候”的问题——而真正的稳定性藏在第一次失败前的那行日志里。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。