机器人检测的原理——以及 Clearcote 为何直接修改引擎
常见做法是通过脚本修改 navigator.webdriver、伪装 WebGL 厂商、覆盖 navigator.plugins。检测方照样能识别出来——原因是结构性的,而不是又漏掉了某个属性。
JavaScript 伪装会自我暴露
JS 伪装,就是让一个普通函数占据了原生函数本该在的位置。检测方不看返回值本身,而是检查返回这个值的函数是不是原生的:
| 破绽 | 为什么能抓出 JS 伪装 |
|---|---|
toString 自我暴露 | 原生方法字符串化后是 function get vendor() { [native code] };而被覆盖的函数字符串化后得到的是它自己的源码——一次 .toString() 就能抓出来。 |
属性描述符 / hasOwnProperty | getOwnPropertyDescriptor 会暴露被重新定义的属性;对被篡改的函数调用 hasOwnProperty('toString') 会返回 true,而原生函数返回的是 false。 |
错误 this 引发的 TypeError | 原生 getter 在接收者(receiver)不对时会抛出特定的 TypeError;简单的 shim 却毫无反应,而这种沉默本身就是信号。 |
| 跨 realm 重新获取 | 检测方从新建的 iframe 或 Web Worker 中取出一个未经改动的 Function.prototype.toString,拿它来检查你的 getter——这个 realm 与你在主世界(main world)中打补丁的 realm 不同。结果返回的是你的源码。露馅了。 |
Clearcote 没有这样的一层。navigator.userAgent 的 getter 就是 C++ getter:它报告 [native code],因为它本来就是原生代码,在每个 realm 中都完全相同——主框架、iframe 和 worker 皆是如此。根本不存在可供检测的 JavaScript 劫持。
机器人检测的三个层面
现代反机器人系统会在三个不同的位置,读取三类结构迥异的表面。单一工具很少能把三者全部解决:
| 层面 | 破绽 | 在哪里修复 | Clearcote |
|---|---|---|---|
| A · 驱动 / 二进制 | cdc_ ChromeDriver 变量、WebDriver 协议暴露面 | 直接驱动原始 CDP,不用 chromedriver | ✅ 普通的 Chromium 二进制——没有任何驱动残留 |
| B · CDP 副作用 | Runtime.enable 泄露、注入的 init 脚本、在主世界中执行、自动化默认视口 | 控制层 / CDP 客户端层 | ✅ 引擎消除了 Runtime.enable 泄露,SDK 默认不注入任何脚本,启动默认值也避开了自动化默认视口(你的客户端在哪里运行它自己的脚本,由客户端自己决定) |
| C · 指纹表面 | canvas、WebGL、音频、字体、navigator、TLS——横跨主框架、iframe 和 worker | 引擎(C++),因为 JS 覆盖会自我暴露 | ✅ 这正是 Clearcote |
最关键的一点:伪装与真实之间没有缝隙
由于这些控制都在引擎内部,页面看到的 JavaScript 与其底层的网络握手来自同一个真实的 Chromium。不存在“伪装的 JS 叠在真实 TLS 之上”这种缝隙可供交叉校验抓取——而这恰恰是基于注入的工具露馅的典型方式。一个 --fingerprint 种子(seed)会在 canvas、WebGL、音频、字体和硬件各方面生成一台内部自洽的机器。底层的 TLS 与 HTTP/2 握手就是引擎自己的握手;它之所以一致,是因为身份画像(persona)报告的正是引擎的真实版本——不设置 brandVersion,就能保持这一点。
如果噪声本身成了破绽,就把它关掉——canvas 和 WebGL 会返回自然值,而身份伪装保持开启(无论开关与否,渲染出的音频和 client rects 都不会被扰动,AudioContext 的采样率/延迟标量也会继续跟随身份画像;原因见此):
await launch({ fingerprint: "u1", fingerprintNoise: false }); // identity on, per-site farbling off关闭噪声后,同一台机器上的各个身份会共用同一个 canvas 哈希。canvasNoise: false 只关闭 canvas 2D 噪声,保留 WebGL 噪声(授权版构建,150 r12+)。
网络与语言区域的一致性
身份画像只是一半:时区、语言和 WebRTC 地址也必须与流量的来源地相符。使用代理时,请带上 geoip 启动——SDK 会查询代理的出口 IP,按该地区设置时区、语言列表、地理位置和 WebRTC 地址;如果无法解析出地区,则中止启动。不使用它时,时区跟随语言(en-US 即对应纽约),与代理所在的国家无关。相关选项见 Playwright & Puppeteer。
这就是 Clearcote 修改 C++ 引擎、而不是注入 JavaScript 的原因。你能控制哪些内容,见指纹参数;引擎如何让所有信号整体保持一致,见架构。
相关阅读