每个爬虫工程师都经历过这样的对话。昨天还能正常访问的站点,今天却返回 403。代码没变,代理没变,请求头也没变,但不知在何处,某个系统悄然判定你的流量并非来自真人。本能反应是去找那个"出了问题"的地方,而现实是:什么都没坏,是检测变强了。
现代反机器人系统不是看门人,而是概率引擎。每个请求都会在数十个相互独立的信号面上被评估,最终由一个综合分数决定你是被放行、被软性限流、被抛出一道挑战,还是被返回一个被投毒过的响应。理解这个分数(什么因素会输入其中、权重如何变化、规避的低成本突破口究竟在哪里)正是一个能撑过一个季度的爬虫与一个能撑过十年的爬虫之间的分水岭。
本文是系统层面的视角。这里没有可供伪造的请求头清单,也没有"五个值得一试的代理"。这是一张领土地图,正如我们在 Crawlbase 内部向新加入平台团队的工程师解释它的方式。
三个检测面
机器人检测发生在三个层级上,而它们彼此并不共享一套词汇。能通过第一层的指纹,可能会在第二层失手。两层都通过的会话,可能会在第十八个请求时栽在第三层。这些层级大致如下:
- 传输层指纹识别:你的 TLS 握手揭示了底层技术栈的哪些信息
- 协议层检查:你的 HTTP 行为透露了关于客户端的哪些信息
- 行为建模:你的会话随时间推移呈现出怎样的样貌
它们被独立评估,再统计性地组合在一起。我们将逐一展开。
TLS 指纹识别
第一个信号在你的 HTTP 请求被解析之前就已抵达。当你的客户端打开一个 TLS 连接时,它会发送一个 ClientHello 数据包,其中列出了它支持的密码套件、它公布的扩展、它偏好的椭圆曲线,以及所有这些项的排列顺序。这个顺序由具体实现决定:OpenSSL 产生一种指纹,BoringSSL 产生另一种,Go 的 crypto/tls 产生第三种,Chrome 技术栈产生第四种。JA4 会把相关组成部分哈希成一个单一的字符串。
由此带来的后果是:如果你通过一个住宅代理用 Python 的 requests 库发起请求,代理会提供一个非常漂亮的住宅 IP,但 TLS 指纹却向任何监听者宣告"这是 Python 3.11 上的 OpenSSL"。检测系统根本不需要看你的 IP、你的请求头或你的行为。仅凭握手本身,它就知道你是什么。
那个流行的变通做法,即给 requests 打补丁让它使用一份类 Chrome 的密码套件列表,之所以失效正是出于它之所以奏效的同一个原因:如今每个爬虫都这么做。反机器人厂商维护着"自称 Chrome 实则 Python"指纹的清单。所声称的 user-agent 与真实握手之间的不匹配,本身就是一个信号。
HTTP 画像检查
越过 TLS 层之后,HTTP 请求本身会被审问。被审问的不是内容,而是形状。真实的浏览器会以特定的顺序发送请求头。Chrome 会在 :authority 之前发送 :method;Firefox 则会把两个较低优先级的条目互换次序。HTTP/2 引入了帧排序与流优先级,而这些会因客户端而异。
下面是在检测器眼中一个 HTTP/2 指纹实际呈现的样子:
{ "akamai_hash": "1:65536;3:1000;4:6291456;6:262144|15663105|0|m,s,a,p", "h2_settings": { "HEADER_TABLE_SIZE": 65536, "INITIAL_WINDOW_SIZE": 6291456, "MAX_HEADER_LIST_SIZE": 262144 }, "header_order": [":method", ":authority", ":scheme", ":path"], "pseudo_headers": "m,a,s,p", // Chrome canonical "frame_priority": [256, 255, 254, 253, 252] }
这一团数据足以在与 user-agent 字符串无关的情况下,以很高的置信度识别出"macOS 上的 Chrome 122"。其中尤以 Akamai 哈希为甚,它是 HTTP/2 指纹识别事实上的标准,几乎被所有主流 CDN 检查。
这里的陷阱比 TLS 层那一处更为微妙。你可以整天轮换 IP,可以为每个请求更换 user-agent。但如果你的 HTTP/2 客户端无论你自称是哪种浏览器都总是协商出相同的 INITIAL_WINDOW_SIZE,那么早在你栽在指纹检查之前,你就会先栽在一致性检查上。
目标不是避开封锁。目标是让你的系统对封锁具备反脆弱性。 摘自我们的内部工程手册
行为信号
第三个面是随时间不断增长的那一个,也是迄今为止在规模化场景下最难令人信服地伪造的那一个。检测系统会建立一个关于会话样貌的模型。真实用户会四处浏览。他们会点击后退。他们会在新标签页里打开一个链接,晾着不管四十秒,然后再关掉。他们会在首次访问时请求 favicon.ico,之后不再请求。他们偶尔会加载样式表失败,然后重新请求两次。他们的请求间隔时间带有抖动,且这种抖动遵循一种可被识别的分布:在大多数测量中是对数正态分布。
爬虫,尤其是那些为吞吐量而调优的生产级爬虫,几乎不做这些事。我们请求文章。我们抽取数据。我们继续前进。我们不浏览。我们不发呆。我们不去点击我们毫无兴趣的广告。
如果你的爬虫的请求间隔的变异系数低于 0.3,那么即便指纹完美无缺,你也明显是自动化的。真实用户大致处在 0.8–1.4 之间。解决办法不是加入随机的休眠,而是把到达时间建模为一个随机过程,并从中采样。
联合概率模型
下面这一点是我们花了最久才真正内化的,也正是大多数"反检测"建议会迅速过时的原因:这三个层级并非各带阈值地被独立评估,而是被组合在一起。
一个现代的检测流水线会输出类似这样的东西:
def verdict(request, session) -> Verdict: tls_score = score_tls(request.handshake) # 0.0 – 1.0 http_score = score_http(request.profile) # 0.0 – 1.0 behavior_score = score_session(session) # 0.0 – 1.0 # Weights are tuned per-customer, per-route, per-hour. composite = ( 0.30 * tls_score + 0.25 * http_score + 0.45 * behavior_score ) if composite > 0.85: return Verdict.BLOCK if composite > 0.60: return Verdict.CHALLENGE if composite > 0.40: return Verdict.THROTTLE return Verdict.ALLOW
从这个结构可以推出三件大多数工程师会忽略的事。
第一,你不需要在每个面上都取胜。一个 TLS 签名略有出入、请求头略显古怪、但会话行为出色的爬虫,得分可能低于一个 TLS 与请求头都完美、但会话模式一目了然的爬虫。在我们检视过的几乎每一个现代系统中,行为分数都被赋予了最高的权重。
第二,阈值会移动。在 UTC 凌晨 2 点放行某段流量的那个综合分数,到上午 11 点可能就会向它抛出挑战。在公共目录页放行的那个分数,到结账 API 上可能就会拦截。把这个系统当作静态不变的,正是大多数"昨天还好好的"故障的根源。
第三,也是最具战略意义的洞见:封锁并不是最糟糕的结果。挑战会给你反馈。封锁也会给你反馈。真正让数据完整性悄无声息地死去的,是限流那一档。你拿回了响应;它们看起来无误;但价格数据正被微妙地扰动,库存数量已经陈旧,而你要等到三周之后才发现,你数据集中有 12% 已被投毒。为封锁而设计很容易。难的部分在于设计出一套能察觉自己已被悄然降级的机制。
为反脆弱场景而构建
大多数规避工程都在试图做到隐形。我们逐渐认定那是个错误的目标。隐形是一个移动的靶子,由一个资源远比你充足的对手在不断维护着。韧性才是目标:构建一套系统,使其性能在被封锁时优雅降级,在条件变化时自动恢复,并就自身数据质量给出诚实的信号。
具体而言,这在我们的基础设施中意味着几件事:
- 多层指纹多样性。不是单一的一种 Chrome 模拟,而是一个由众多模拟组成的群体,并经过恰当的采样。一个代理池不是一份 IP 清单;它是一个在(IP、TLS 技术栈、HTTP 画像、地理位置)之上的联合分布。
-
对我们自身流量的实时打分。我们把每个出站请求都当作从一个未知分布中抽取的一个样本,并测量响应的分布。如果某条路由上的
p(200)跌破基线,那条路由就会被自动隔离,先做诊断性爬取,再恢复生产流量。 - 对抗性校验。我们会周期性地抓取已知良好的内容,并核对我们拿回的与我们预期的是否相符。预期与实际观测之间的漂移,是一个远胜于 HTTP 状态码的健康信号。
反机器人厂商是为抓住中位数水平的爬虫而优化的。中位数水平的爬虫很喧闹:在数百万个请求中使用同一套指纹,没有行为建模,没有校验。一个在检测器所测量的那些维度上与人类流量在统计意义上不可区分的爬虫,并不需要做到隐形。它只需要做到平平无奇。
这些数字是什么样的
把它落到实处:在我们自己的基础设施上,于过去一个季度,对被爬取最多的前 500 个域名的一份样本,我们大致观察到了如下结果。
- 单指纹客户端(默认的 Python/Node 库)在面对一条受 Cloudflare 保护的路由时,于前 100 个请求内就达到了 47% 的封锁率。
- 指纹已匹配但没有行为建模的客户端达到了 22%。
- 指纹已匹配、且具备行为建模与按路由自适应计时的客户端达到了 3.1%。
- 同一群体在通过 Crawlbase 的智能路由层运行时,达到了 0.4%。
从第二层到第三层的改善,才是真正要紧的那一个。行为模型的作用绝非边际性的。它是一个需要时刻照看的爬虫与一个能无人值守运行数月的爬虫之间的分水岭。
核心要点
- 机器人检测是横跨三个独立面的联合概率。你不需要在每个面上都取胜。你需要的是在任何一个面上都别输得太惨。
- 在现代系统中,行为信号被赋予最高的权重。花在打磨 TLS 指纹上的数小时,回报还不如花一个下午去建模真实的会话行为。
- 限流比封锁更危险。封锁会告诉你某处出了问题。限流则会悄无声息地给你的数据集投毒。
- 为韧性而设计,而非为隐形而设计。隐形是一场你终将输掉的军备竞赛。韧性会复利增长。
- 去测量你自身的数据质量,而不只是你的状态码。200 OK 是必要的,但并不充分。响应的形状才是真正的信号。
归根结底,反机器人规避与其说是关于击败这个系统,不如说更多是关于理解这个系统实际在玩的是什么游戏。这些系统比五年前更聪明了。再过五年它们还会更聪明。能够成功的团队,是那些不再把检测当作一堵要去翻越的墙,而开始把它当作一个要去围绕设计的约束的团队,就像你会围绕网络延迟、数据库负载,或你所运营的物理宇宙中的任何其他属性去做设计一样。
网络并非混沌。它有结构。我们绘制它。你基于它构建。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
