如何在注册页识别无头浏览器和自动化
暴露 Puppeteer、Playwright 和 Selenium 注册的浏览器信号,为什么每个信号单独都能伪造,以及如何在不误伤真人的前提下组合使用。
为什么机器人现在都用真实浏览器
几年前,大多数注册机器人是直接提交表单的简单 HTTP 脚本。这类很好拦:它们不执行 JavaScript,所以页面在浏览器里计算的任何东西都传不过来。如今,大多数认真的机器人会通过 Puppeteer、Playwright 或 Selenium 驱动真实浏览器,常常是无头模式,有时借助会伪装指纹的反检测浏览器。它们会执行你的 JavaScript、渲染页面,像真人一样填写表单。
识别它们,就是找出自动化浏览器与真人使用的浏览器有何不同。下面任何一个信号单独都不是证据——有决心的操作者可以逐个修补——但它们采集成本低,而且可以叠加。
信号一:WebDriver 标志
处于 WebDriver 类自动化下的浏览器会把 navigator.webdriver 设为 true。这是 WebDriver 标准有意为之的一部分,Selenium、Puppeteer 和 Playwright 的默认配置都会暴露它。
const automated = navigator.webdriver === true;
“Stealth” 插件会删除或覆盖它,所以没有这个标志说明不了什么。但如果有,几乎就能定性:真实用户基本上从来不会有。
信号二:无头用户代理
无头 Chrome 会在用户代理中自称 HeadlessChrome,除非操作者改写了它。也要在服务器上通过请求头检查,因为浏览器在 JavaScript 里报告的用户代理可能与它实际发送的不同。
信号三:连接了 DevTools 协议客户端
Puppeteer 和 Playwright 通过 DevTools 协议控制 Chrome。当协议客户端在监听控制台事件时,打印一个对象会让浏览器把它序列化——甚至读取正常页面根本不会碰的属性。用一个记录自己是否被读取的 getter,是察觉这一点的常见办法:
function devtoolsProtocolAttached() { let read = false; const e = new Error(); Object.defineProperty(e, "stack", { get() { read = true; return ""; } }); console.debug(e); return read; }
开发者打开 DevTools 时它也会触发,所以给它中等权重,而不是当作定论。
信号四:软件渲染和奇怪的窗口
服务器上的无头浏览器没有 GPU,所以 WebGL 会退回到 SwiftShader 或 llvmpipe 之类的软件渲染器。在浏览器暴露 WEBGL_debug_renderer_info 扩展时,可以读到渲染器名称。有些无头配置还会报告宽或高为零的窗口。虚拟机和远程桌面也有这些特征,所以单独看都是弱信号。
信号五:自相矛盾的指纹
反检测浏览器的存在就是为了伪装成许多不同的真实浏览器。它们通过覆盖属性来实现,而覆盖很少做到完全一致。值得比较的有:
- 用户代理中的操作系统与
navigator.platform和 User-Agent Client Hints。 - 用户代理中的浏览器家族与只有该内核才有的特性。
- 已安装的字体与该操作系统自带的字体。
- 连续绘制两次的 canvas 得到两张不同的图——注入噪声的迹象。
Notification.permission显示 “denied”,而 Permissions API 显示 “prompt”。
每个矛盾单独都很弱,因为隐私扩展和不常见的配置也会产生其中一些。两三个同时出现就是很强的模式。
信号六:从未回来的令牌
一个简单又可靠的技巧:让注册页脚本申请一个短时效、一次性的令牌,放进表单的隐藏字段,并在服务器上验证。跳过你 JavaScript 的机器人拿不到有效令牌;重放旧令牌的机器人带来的是已用过的令牌。你不需要知道机器人如何工作,就能发现它从未真正运行过你的页面。令牌要一次性、绑定你的网站,并在几分钟内过期。
信号七:看行为,不看指纹
指纹可以伪造,行为要难得多。真会读表单、移动指针、敲键盘。脚本在页面加载后几百毫秒内就填完并提交,没有任何可信的输入事件。
可靠的测量不需要额外软件:代理在页面加载时签发访客令牌,服务器把这个时间戳和检查时刻作比较。从加载到注册不到一秒,就是脚本。如果代理还能上报可信的输入事件,那么一次没有任何指针、按键或触摸事件的即时提交,就是第二个独立的确认。
关于信号三的提醒:在当前 Chrome 中,DevTools 技巧只有在自动化客户端订阅控制台事件时才会触发,所以把它当作加分项,而不是主要检测手段。
组合使用,不要凭单个信号拦截
把每个信号当作分数中的点数,按总分做决定。一个合理的初始权重,从强到弱:
| 信号 | 权重 | 原因 |
|---|---|---|
navigator.webdriver 为 true | 高 | 真实用户几乎从不为 true |
| HeadlessChrome 用户代理 | 高 | 只有无头版本会发送 |
| 令牌缺失或被重放 | 高 | 页面脚本没运行,或一次运行对应多次注册 |
| 连接了 DevTools 协议 | 中 | 开着 DevTools 的开发者也会触发 |
| 操作系统或浏览器矛盾 | 中 | 反检测覆盖,但某些扩展也会造成 |
| 软件渲染、零尺寸窗口 | 低 | 虚拟机和远程桌面也有 |
把浏览器信号与服务器端信号结合起来——来自云 IP 段的注册、同一子网的突发、关联到同一设备的账号——几乎没有机器人能在所有方面同时看起来干净。
不该做的事
- 不要仅因缺少插件或语言就拦截。强化隐私的浏览器会去掉它们。
- 不要把检测逻辑写成名字一目了然的检查发给客户端,让操作者一搜就能一次性绕过;在浏览器里采集原始事实,在服务器上做决定。
- 不要盲目相信浏览器报告的任何内容。客户端在机器人的控制之下。验证在你的服务器上、用你的密钥完成。
Attrelix 的做法
Attrelix 代理在你的注册页采集这些浏览器事实和一个一次性访客令牌;服务器把它们与网络、邮箱和关联账号信号合成一个分数。所有权重都公开在文档中,任何单个弱信号都不能单独把注册送去复核。
小结
- 现代机器人使用真实浏览器;要找出自动化与真人的不同之处。
- WebDriver、无头用户代理和缺失的令牌是强信号;其余较弱,但可以叠加。
- 在服务器上按分数决定,绝不凭单个浏览器标志。