郭立 (leeguoo)

# 反爬检测数的是你说了几次谎:真实浏览器为什么天生没破绽

做浏览器自动化的人迟早会掉进一个坑:拼命把假浏览器伪装成真人,再被一层层识破。想明白这件事,得先知道 CreepJS 这类检测压根不直接判断"你是不是机器人",它在收集你几十项信号做交叉核对,对不上一处记一条谎。这篇从我实测的一组真实指纹讲起,拆到最难盖的那条痕迹(在协议层,不在 JS 层),说清为什么打补丁是负和游戏。

2026年7月2日 · 文章 · 公开

本页目录

做浏览器自动化的人迟早会掉进一个坑:想尽办法把一个自动化浏览器伪装成真人,被网站一眼识破,再加一层伪装,再被识破。我自己绕明白这件事,是从想通"检测方到底在查什么"开始的。

先说个反直觉的结论:像 CreepJS 这种检测,它压根不直接判断"你是不是机器人"。它做的是另一件事,把你浏览器暴露出来的几十项信号收齐,然后互相对账,看哪儿对不上。对不上一处,记一条"谎"。谎越多,分越低。它内部就管这个叫 lies。

视角一换,很多事就通了。

左:假浏览器/自动化被 CreepJS 检测查出 webdriver=true、headless、指纹对不上、Runtime.enable 四面红旗而失败;右:真实 Chrome 四项全绿通过

一台真实浏览器长什么样

我让 chrome-use 连上这台 Mac 上正开着的 Chrome,读了几项最常被查的信号:

$ json
{
  "webdriver": false,
  "gpu": "ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)",
  "plugins": 5,
  "languages": "en,en-US,ja",
  "timeZone": "Asia/Tokyo"
}

看着平平无奇,但每一项都是检测方盯着的点:

navigator.webdriver 是 false。W3C 的 WebDriver 规范规定,浏览器被自动化控制时这个 getter 返回 true;Selenium、Puppeteer、Playwright 默认都是 true。我这里是 false,因为确实没有框架在驱动,是我本人在用。

GPU 那串 Apple M3,是通过 WebGL 的 WEBGL_debug_renderer_info 扩展拿到的真实显卡名。无头 Chrome 没有真实 GPU,这一项通常是 SwiftShaderllvmpipe,也就是软件渲染。软件渲染本身不违法,可它跟一个"自称是普通 Mac"的浏览器摆在一起,就成了矛盾。

plugins 有 5 个、时区 Asia/Tokyo、语言列表里有 ja,这些琐碎信号单看没意义,价值在于它们能不能对上

检测的杀招是交叉核对

一个自动化浏览器要伪装,改的通常是表层:改 UA 谎称自己是 Mac,改 navigator.webdriver,注脚本盖掉 headless 的痕迹。但你改得了 UA,改不了那一大串跟着联动的东西。

UA 说 macOS,WebGL 却报 llvmpipe(Linux 软件渲染的典型),对不上。声称时区在东京,出口 IP 却在法兰克福,对不上。手动改了屏幕分辨率,devicePixelRatio 和 Canvas 实际渲染出的尺寸又对不上。CreepJS 把这些信号两两一验,每个矛盾记一条谎。

所以伪装真正的难点从来不是"盖住某一项",是盖住这一项之后,它跟其余几十项还得自洽。改一个,漏一串。这是伪装天然的劣势。

最难盖的那条痕迹,不在 JS 层

上面这些还都是 JS 能读到的信号,理论上都能 hook 掉。但有一类痕迹在更底下。

用 Chrome DevTools Protocol 驱动浏览器时,一旦发了 Runtime.enable(不少 CDP 库默认就发),Chrome 会开始把执行上下文和 console 调用通过协议往外报。rebrowser 的检测利用的正是这点:它构造一个带 getter 的对象,console.debug 一下。只要 Runtime.enable 开着,CDP 就要把这个对象序列化传出去,序列化会触发那个 getter;getter 一响,页面当场就知道"有个开了 Runtime.enable 的调试器挂在我身上"。

这条难受在于,它不是你 JS 里的某个属性,你 hook 不掉,它是你选择的驱动方式本身留下的。rebrowser 面板里专门有 runtimeEnableLeak 这一项,不少号称隐身的方案就栽在这。

真实浏览器为什么省心

回头看开头那几项:真实 Chrome 的 webdriver 本来就是 false,GPU 本来就是 Apple M3,时区、语言、Canvas 本来就出自同一台真机、处处自洽,没有谎可数。chrome-use 连它走的是浏览器扩展加 native messaging,不开 --remote-debugging-port,中继路径默认不开 Runtime.enable,连协议层那条痕迹都没有。

这不是"伪装得好",是根本没有要伪装的东西。

还有一层容易被忽略:打补丁本身也留痕。你 hook navigator.webdriver 的那段代码,覆盖属性时会改掉它的 property descriptor、改掉 toString 的返回值,检测方反过来查"这个属性是不是被人动过手脚"就行。你补得越多,能被指认动过手脚的地方越多。真实浏览器没这个负担,因为它没打补丁。

自己拉一遍对比

别信我说的,两个浏览器各跑一遍最直接:

  • CreepJS:看它给的 trust score,以及它列出来的 lies 具体是哪几条。
  • rebrowser-bot-detector:重点看 runtimeEnableLeaknavigatorWebdriver

拿一个普通无头自动化跑一遍,再拿你自己天天用的浏览器跑一遍,把两边的 lies 列表并排一看,差在哪儿不用我解释。真要做那些"必须是真人才过得去"的活,登录态、风控敏感的操作,用一个没有谎要圆的浏览器,比用一个靠不停打补丁硬撑的假浏览器,省的事不是一星半点。

← 上一篇
一个真实 Chrome,几个 agent,还有你自己:怎么不互相点乱
下一篇 →
桌面应用其实是网页:想通这点,操作它就不再是"桌面自动化"

评论

评论发布后会立即公开,如触发规则可能被审核下架。

最多 1000 字。