ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

编译版Chromedriver+配套浏览器:抹除自动化特征的反检测实战

编译版Chromedriver+配套浏览器:抹除自动化特征的反检测实战 简介编译好的ChromeDriver已经抹除自动化特征并附带配套浏览器面向Windows 10环境下的爬虫工程师、自动化测试与网页采集开发者尤其适合需要长期稳定运行采集任务的场景。资源共491个文件压缩包约56MB核心包含驱动程序与浏览器主程序配套dll运行库、json配置文件、js脚本和css样式等同时包含多份png、gif图标资源和html页面目录结构与真实浏览器安装目录相近便于直接替换使用。目前已有1800人学习下载。压缩包内还提供了ChromePortable.bat启动器、多款crx扩展、local state、cookies、history等数据文件能够模拟较真实的用户浏览器环境涵盖常用浏览痕迹与站点偏好降低被目标网站识别为自动化工具的风险。压缩包内附有安装说明可快速完成浏览器与驱动的配对部署离线使用方便适合反复调试和规避检测。1. 为什么一个“抹过特征”的Chromedriver比改代码更值钱做爬虫和自动化测试的人大概率经历过这种场面脚本逻辑没问题页面结构层也定位得准可只要Chromedriver一驱动浏览器目标页面二话不说弹验证码后台日志里还挂着一行“automation detected”。这时候你会意识到问题不是出在代码而是出在浏览器会话本身——Chromedriver一启动就把“我是自动化”写在了脸上。标题里说的“编译好的Chromedriver特征已经被抹除配套浏览器”指的是驱动层和浏览器层一起处理的方案Chromedriver不再暴露自动化标识配套浏览器也是一个同版本的修改版Chromium两者互相锁定对外看起来和真人手动打开Chrome几乎一样。这套东西最常出现在数据采集、UI自动化回归以及需要长期稳定运行的定时任务场景里适合那些受够了“今天能跑、明天被封”的工程师。需要先声明一点以下所有操作都限定在你拥有测试权限的站点和本地环境里使用越界的事不在讨论范围。2. 自动化特征从哪里漏JS属性、CDP协议与行为指纹2.1 navigator.webdriver 是“机器人”的入场券但只算第一层服务端要判断一个浏览器会话是不是被驱动的最省事的办法就是读navigator.webdriver。正常情况下这个值是undefined或false但Selenium、Puppeteer这些库通过Chromedriver启动浏览器时会自动把它置为true。于是很多反爬脚本的第一行就成了if (navigator.webdriver) { location.href /block-page; }这玩意儿看起来简单但直接改是改不掉的。navigator.webdriver不是挂在window上的普通属性而是定义在Navigator.prototype上的规范属性你直接在页面里执行delete navigator.webdriver基本没用需要用Object.defineProperty去覆盖它的getter或者趁文档还没加载完就把定义替换掉。不过这只是最早、最容易被大家发现的一层。许多站点的检测逻辑早就升级了会去看navigator.webdriver这个描述符是不是原生代码会去比navigator.plugins的长度还会检查window.chrome对象是否存在。单纯把webdriver改成undefined只能骗过最基础的校验。更隐蔽的一层藏在CDP里。Chromedriver跟浏览器之间通信走的是Chrome DevTools Protocol浏览器进程里会留下自动化相关的上下文信息。这类证据跑在JS之外不是你在页面里写几行脚本就能清除的。这就是为什么“运行时打补丁”和“编译期抹特征”最后走向了两条完全不同的路线。2.2 运行时补丁和编译期抹除两条路线怎么选先说明一点我在实践中见过的主流做法其实分两种。第一种是运行时补丁。不碰Chromedriver和浏览器二进制只是在Selenium启动时加几个参数再用CDP往每个新页面注入JavaScript把navigator.webdriver、navigator.plugins、window.chrome这些可感知的字段修正一遍。这种方式的好处是驱动和浏览器都可以追官方最新版日常联调非常快坏处是能洗的只是“JS可见层”CDP痕迹和进程级特征洗不掉。遇到检测严格的站点照样露馅。第二种是编译期抹除。直接修改Chromedriver或Chromium的源码把注入自动化标识的那段逻辑在编译阶段就去掉同时让浏览器不再标记“Chrome正受到自动测试软件的控制”。这才是标题里“编译好的”三个字的含义。这个方案的工程量不小需要环境能编译Chromium而且每个大版本都要重复维护所以很多人会直接找别人的编译产物来用。这样做省事但风险也明显——第三方编译的东西没有官方签名不排除被塞进后门的可能拿到的第一件事应该是核对Hash而不是直接跑。“指纹浏览器”是另一种分支它在浏览器内核层面托管UA、Canvas、时区、字体这些指纹信息跟“驱动抹除”解决的根本不是同一个问题。做自动化的时候别把这两种东西混着选否则出了问题都不知道该查哪一层。2.3 为什么“配套浏览器”不是可选项Chromedriver和Chrome之间有一个硬性匹配关系驱动的大版本号必须和浏览器大版本号一致。Chromedriver 136 只能驱动 Chrome 136拿它去连 Chrome 135 会直接报session not created。这个约束在普通场景下就已经让人头疼在“特征抹除”场景下更麻烦。原因在于修改版Chromedriver往往是针对某个特定Chromium分支编译的它内部处理过的检测点、协议兼容逻辑都只对那个分支有效。你要是随手装一个最新版Chrome新浏览器里可能又加了新的自动化检测逻辑旧驱动没处理过照样被认出来。反过来新驱动也可能因为浏览器分支不同而失去作用。所以“编译好的驱动”和“配套浏览器”是绑定的拆开用等于白费功夫。再补一个常见误区很多老教程还在用Chrome 109配老驱动因为从某个版本开始Chrome加大了对自动化的检测力度。固守旧版确实能避开一部分新检测但代价是浏览器漏洞没人管。我的建议是别为了“稳定”死守一个过时版本而是定期把整个“驱动配套浏览器Selenium”这套矩阵一起升级升级完重新做一轮特征体检。章末给一个最基础的核对命令后面会反复用到# 查看当前 Chromedriver 的版本号 chromedriver --version # 在浏览器地址栏打开 chrome://version对比版本号 # 驱动的大版本号如 136必须等于浏览器的大版本号3. 跑通最小可用方案版本核对、体检脚本、两层运行时补丁3.1 先做版本核对这不是习惯是前提拿到“配色好的驱动配套浏览器”以后第一步别急着写爬虫脚本。先确认版本对齐这一步省不掉。操作很简单打开配套浏览器地址栏输入chrome://version记下Chrome版本号里的主版本号比如136.0.7103.xx主版本就是136然后在命令行跑一次chromedriver --version看驱动的主版本是不是也是136。chromedriver --version | grep -o ChromeDriver [0-9]* | awk {print $2}这个命令会把驱动版本号前两段提取出来方便跟浏览器版本比对。如果两边的数字对不上Selenium启动时会给你看类似这样的报错This version of ChromeDriver only supports Chrome version 136 Session not created: This version of ChromeDriver does not support the browser version youre using看到这两条不用怀疑是驱动坏了就是版本错位。解决方案只有一条换到和驱动同主版本的浏览器或者反向找对应版本的驱动。另外如果打算长期做这摊事我建议去Chrome for Testing这个官方测试渠道下载指定版本的浏览器把它和日常使用的Chrome分开装避免某天日常浏览器自动升级把整个测试矩阵搅乱。3.2 写一段不撒谎的体检脚本跑之前先知道自己漏了多少在动手“抹特征”之前先写一个体检脚本看看当前状态到底漏了多少。这一步很有必要因为很多人的印象停留在“navigator.webdriver为true”但实际漏的点远不止这个。from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() # 先不做任何处理直接启动 driver webdriver.Chrome(optionsoptions) # 打开一个空白页脚本注入检测代码 driver.get(about:blank) result driver.execute_script( return { webdriver: navigator.webdriver, pluginCount: navigator.plugins.length, languages: navigator.languages.join(,), ua: navigator.userAgent }; ) print(navigator.webdriver , result[webdriver]) print(navigator.plugins.length , result[pluginCount]) print(navigator.languages , result[languages]) print(userAgent , result[ua]) driver.quit()逻辑说明脚本先原样启动Chromedriver不附加任何处理参数让driver.get(about:blank)开启一个空白页面再执行一段JavaScript把四个关键字段一次性读出来。之所以用about:blank而不是直接打开目标网站是为了先在干净环境里确认特征避免目标站点自己的脚本干扰结果。参数说明pluginCount在自动化环境里通常是0因为自动化启动的浏览器不会加载任何插件而正常Chrome至少有若干个内置插件languages默认一般是en-US如果你的目标站点是中文环境这里就存在不一致userAgent这里先看有没有HeadlessChrome字样有的话又是一个明显的机器人标记。跑完这段你会发现没做任何处理时webdriver是trueplugins是0。这就是之后所有补丁和修改要解决的目标。3.3 两层运行时补丁启动参数 文档级注入体检完先上手最常用的运行时方案这套方案能帮你快速了解“特征抹除”的基本套路也是过渡到编译版方案前最实用的一手准备。from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() # 第一层启动参数 options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome(optionsoptions) # 第二层CDP文档级注入必须在 get 之前调用 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); }) driver.get(about:blank) result driver.execute_script( return { webdriver: navigator.webdriver, webdriverDesc: Object.getOwnPropertyDescriptor(Navigator.prototype, webdriver), pluginCount: navigator.plugins.length }; ) print(navigator.webdriver , result[webdriver]) print(descriptor , result[webdriverDesc]) print(navigator.plugins.length , result[pluginCount]) driver.quit()逻辑说明--disable-blink-featuresAutomationControlled是关闭Blink引擎里跟自动化控制相关的特性加了之后navigator.webdriver会从true变成undefined这是官方留的口子也是很多“快速去标志”方案的根基。excludeSwitches的作用是去掉浏览器顶部那句“Chrome正受到自动测试软件的控制”的提示条。execute_cdp_cmd里的那段Object.defineProperty是在每个新文档加载前执行的所以必须放在driver.get之前调用否则页面都加载完了你再改服务端早读到真值了。参数说明Page.addScriptToEvaluateOnNewDocument是CDP原生方法它的语义是“后续每一次导航我都提前注入这段脚本”属于比较优雅的运行时补丁方案。日常联调用这套组合拳能滤掉不少只查navigator.webdriver的初级检测站点。但要注意这套方案有它的边界。Object.defineProperty自定义的getter函数跟浏览器原生getter长得不一样检测方一旦去读取描述符的toString就能看出它不是原生代码。这个指纹在下一章的“配套浏览器”里才会被抹掉。4. 配套浏览器里值得调的参数UA、视口、时区与网络层4.1 三个必调参数UA、视口与语言拿到配套浏览器之后Selenium连接时的启动参数就不是随便填的了。以我自己的习惯有三个参数每次必调User-Agent、窗口尺寸、语言环境。这三个参数看起来普通但放在一起就构成了浏览器“看起来像谁”的基础画像。options Options() # 浏览器驱动路径按实际环境配置 options.binary_location /path/to/your/patched/chrome # UA 必须与配套浏览器的实际版本一致 options.add_argument(--user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36) # 视口尺寸 options.add_argument(--window-size1920,1080) # 语言环境 options.add_argument(--langzh-CN) driver webdriver.Chrome(optionsoptions)逻辑说明binary_location指向配套浏览器的主程序路径这一步很关键因为Selenium默认找的是系统安装的Chrome而不是你下载的修改版浏览器。不指定它前面做的所有“配套浏览器”准备都是白搭。参数说明UA字符串里的Chrome/136.0.0.0必须和配套浏览器的实际版本一致。常见翻车是UA写的是136浏览器实际是135一眼就能看出是在伪造。另外--user-agent里绝对不能出现HeadlessChrome字样这是最扎眼的机器人标记。如果你用无头模式不要用老的--headless要用--headlessnew新版无头模式跟完整浏览器共享UA没有那个明显标记。4.2 时区、地理位置和权限 API别只改UA很多人改完UA就觉得完事了其实静态特征是一套组合拳。页面里navigator.language、navigator.languages、navigator.onLine、Intl.DateTimeFormat输出的时区这些字段之间必须相互咬合。你用--langzh-CN把语言改成中文但时区还是UTC检测系统一对时间就知道不对劲。常见做法是配合CDP的Emulation域把时区、地理位置一起覆盖掉driver.execute_cdp_cmd(Emulation.setTimezoneOverride, { timezoneId: Asia/Shanghai }) driver.execute_cdp_cmd(Emulation.setGeolocationOverride, { latitude: 31.2304, longitude: 121.4737, accuracy: 100 }) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, languages, { get: () [zh-CN, zh, en] }); })逻辑说明第一段把JS运行时看到的时区改成东八区第二段覆盖浏览器地理位置第三段修正navigator.languages列表。这三段的执行时机都在页面加载前所以它们必须放在driver.get之前。这里有个原则能不改的字段尽量不改。每个Object.defineProperty都会在浏览器里留下自定义函数跟原生函数之间的差异改得越多被检测面越大。你只需要让关键字段之间不自相矛盾就够了没必要把整个指纹都重建一遍。4.3 网络层尽量别再加“中间层”讲一个很多文章不写、但是实际影响很大的点TLS指纹。服务端除了看JS注入的字段还会在TLS握手阶段直接提取客户端特征这就是常说的JA3/JA4指纹。普通Chromedriver驱动的是浏览器本身只要流量是从浏览器内核直接发出去的TLS指纹就跟正常Chrome一致。问题出在很多人为了让“特征更干净”或者“连接更稳定”会在本地再套一层软件去转发流量这层软件用Go或OpenSSL的TLS库重新做了握手指纹立刻变成这些库的样式比什么都明显。所以我的建议是不要为了“隐藏自己”去额外引入中间网络层也不要轻易修改系统证书、做中间人解密。默认让配套浏览器的协议栈直连目标站点反而最安全。遇到连不上的情况先检查DNS和HTTPS证书而不是动TLS链路的构造。5. 从七次翻车里整理出的避坑清单版本错位、补丁失效、杀软误报与策略封禁5.1 现象一启动就报 session not created拿到“编译好的Chromedriver”连着配套浏览器Selenium却直接抛异常日志里写着This version of ChromeDriver only supports Chrome version xxx。原因驱动在编译时被锁定到了某一个Chromium分支而你的浏览器版本和它不匹配。这跟普通环境下的版本错位一样只是第三方编译版驱动对版本更敏感有的大版本后几位不一样都会出问题。解决不要尝试去修改驱动直接把浏览器换成驱动源码对应的那个版本。确认方式就是第3章里的版本比对命令。我一般会把“驱动版本浏览器版本Selenium版本”写成一组环境变量换机器时一次性改掉而不是靠记忆。5.2 现象navigator.webdriver 已经是 undefined还是被弹验证码体检脚本里navigator.webdriver输出的是undefined但打开目标页面还是被要求验证。原因检测方可能在做“描述符等级”的校验。它读取的不是navigator.webdriver的值而是Object.getOwnPropertyDescriptor(Navigator.prototype, webdriver)的getter函数然后比对toString输出里有没有[native code]。运行时注入的自定义函数没有这个标记一对比就穿帮。另外CDP层的自动化上下文和进程参数比如启动命令行里有没有--enable-automation也是检测点。解决这种场景下运行时补丁已经不够用了。要么换标题里说的那种编译期抹除过的驱动和配套浏览器让WebDriver进程本身不带自动化上下文要么在行为层面做配合让脚本动作具备真人节奏别一进页面就瞬间点五个按钮。5.3 现象修改版浏览器启动闪退或白屏配套浏览器用Selenium拉起后进程秒退或者窗口打开了但页面一片空白。原因常见情况有三类。修改版Chromium缺少系统动态库在虚拟机或容器里GPU模块崩溃或者启动参数里残留了旧用户数据目录旧Profile里写入了不兼容的东西。解决先用最小定位法排查启动参数临时加上--no-sandbox --disable-gpu看能不能正常打开about:blank。能开说明是GPU或沙箱的问题还是闪退大概率是缺库。同时每次启动都给--user-data-dir指定一个全新的临时目录不要复用系统默认Profile。最后在Linux环境可以用ldd检查关键so是否齐全。5.4 现象杀毒软件把编译好的驱动当作木马隔离解压Chromedriver杀毒软件立刻弹窗再一看文件已经进了隔离区。原因第三方编译版驱动没有官方签名文件特征跟官方版本差异太大很容易触发启发式误报。这跟驱动本身有没有问题无关但也是很多人一上来就踩的坑。解决先别急着关杀毒。在隔离环境里核对文件的SHA256跟来源页面标注是否一致确认没有问题再把整个驱动放到可信执行目录。每次下载第三方编译版都保留一份Hash记录下次换版本时先做对照。不是所有“独家收藏”都值得信任来源不明的东西永远不要放在生产环境里跑。5.5 现象浏览器地址栏显示“由你的组织管理”自动化入口被封配套浏览器打开后设置页提示“由你的组织管理”有些策略直接禁用了远程调试功能Chromedriver连不上。原因浏览器读取到了系统或用户数据目录里的托管策略文件。第三方修改版浏览器如果被塞进了策略或者你的user-data-dir复用了被人配置过的旧目录都会出现这种情况。解决打开chrome://policy查看策略来源定位是系统级还是用户级策略清理掉不相关的项把user-data-dir换成全新目录再重启浏览器。如果修改版浏览器本身内置了策略那就换一个不带策略残留的分支不要硬着头皮去绕过策略容易被反噬。6. 能不能抗检测得用两张办法验收JS体检与场景回放6.1 用一份JS体检脚本做验收前面已经写过体检脚本但这版更全面一点检查的不只是navigator.webdriver的值还有描述符、插件数量、UA特征、window.chrome对象。const checks { webdriver: navigator.webdriver, isNativeGetter: String(Object.getOwnPropertyDescriptor( Navigator.prototype, webdriver ).get), pluginCount: navigator.plugins.length, languages: navigator.languages.join(,), headlessUA: /HeadlessChrome/.test(navigator.userAgent), hasChromeRuntime: typeof window.chrome object }; console.table(checks);跑完之后拿结果跟下面这张对比表对一遍检测项未处理的Chromedriver运行时补丁编译版驱动配套浏览器navigator.webdriver一般是trueundefinedundefined描述符getter是否原生原生自定义函数易穿帮保持原生navigator.plugins0改为非0正常UA里含HeadlessChrome可能可能没有window.chrome对象可能缺失自己补存在核心看第三行isNativeGetter。它输出的应该是function get webdriver() { [native code] }里面带[native code]说明是浏览器原生的不带就是被脚本覆盖过。覆盖过的在严格检测面前依然会露馅这一项过不了后面都不用继续。6.2 用场景回放验证让操作节奏像真人JS体检通过只是第一步真正要看的是整套会话在真实操作下稳不稳。做法是打开授权测试页面用ActionChains模拟一次完整操作鼠标移动、悬停、点击、随机停顿、滚动页面观察有没有触发验证码。import time import random from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) # 先移动鼠标到页面中间带随机停顿 actions.move_by_offset(320, 240).pause(random.uniform(0.3, 0.8)) # 模拟悬停 actions.move_by_offset(40, 20).pause(random.uniform(0.5, 1.0)) # 点击 actions.click().perform() time.sleep(random.uniform(1.5, 3.0)) print(页面标题:, driver.title)逻辑说明这段脚本故意把动作拆成两段位移每段动作之间加随机停顿模拟真人的鼠标轨迹。真人操作不会是一条匀速直线停顿、回看、急停都是正常现象。如果你的脚本一进页面就三毫秒内完成所有点击就算指纹再干净行为侧也会把你标记成机器人。6.3 把验收动作固定成日常习惯聊到最后讲一个我自己的教训。有一段时间我特别依赖某个编译版驱动总觉得“都抹过特征了还能有什么事”。结果某次跑定时采集发现连着一个星期每天凌晨那批任务全被风控拦下来而白天手动跑同样的脚本完全正常。后来查日志才发现浏览器在前一天自动升级到了新版本驱动还是旧分支两边已经悄悄错位了两天。从那以后我把“版本核对JS体检场景回放”打包成一条冒烟用例每次换驱动、换浏览器、甚至换Selenium版本都先跑一遍这条用例过了才放行到正式任务里。这比依赖某个“独家收藏”版本要靠谱得多。真正能让方案长期稳定下来的不是某一个神奇的驱动而是一套能持续证明“这套环境确实是干净”的验收流程。希望帮到你。本文还有配套的精品资源点击获取
返回列表