数据抓取

过时 User-Agent 的代价:衡量浏览器版本漂移

爬虫常常硬编码一个浏览器版本,然后就不再更新。我们以声称长达6.5年之久的旧版Chrome身份请求了397个热门网站,拒绝率逐年攀升。

Chris Collins

2026年10月5日 · 4 分钟阅读

每个设置 User-Agent 标头的爬虫都会选定一个浏览器版本,而几乎没有一个会去更改它。这个字符串在项目启动那天就写进了配置文件,此后这个项目会持续多年地自称是那个浏览器。与此同时,真实的浏览器在不断更新:Chrome 在 2025 年 9 月底到 2026 年 9 月底之间发布了 13 个主要版本。

这种版本漂移是否重要?反机器人系统清楚真实访客使用的浏览器版本,因此一个已经多年没人使用的版本会显得格外突出。我们通过请求 397 个顶级网站的首页,分别声称使用五个不同的 Chrome 版本(从当前版本到六年半前的版本),测量了这种差异的具体程度。

关键结论

  • 声称的版本越旧,被拒绝的概率就越高,且每次都是如此。17.1% 的首页对当前 Chrome 版本发起了质询或拦截;19.9% 拒绝了一年前的版本,22.7% 拒绝了六年半前的版本。
  • 这种影响只朝一个方向发生。有 22 个网站对最旧版本予以拒绝,但对当前版本予以放行;没有一个网站拒绝当前版本却放行旧版本。
  • 对当前版本的两次相同请求在全部 397 个网站上得到了完全一致的结果,因此这些差异不是噪声。
  • 使用 Akamai 的网站反应最快:在拒绝了仅一年前版本的 11 个网站中,有 9 个受 Akamai 保护。受 Cloudflare 保护的网站大多只对最旧的版本做出反应。
  • 应保持版本是最新的,使其与请求的其余部分保持一致,并像对待任何依赖项一样定期检查它。

我们如何测量

我们从早先的反机器人技术栈查询中随机抽取了 400 个网站的首页,这些首页来自 Tranco 排名前 1000 的域名。在 2026 年 10 月 5 日,我们对每个首页请求了六次,每次间隔一秒,并按每个网站随机的顺序进行,使用标准的 Windows Chrome User-Agent 字符串,声称以下版本:

声称版本稳定版发布日期测试时的年龄
Chrome 154,请求两次2026 年 9 月 22 日当前版本
Chrome 1412025 年 9 月 30 日约 1 年
Chrome 1292024 年 9 月 17 日约 2 年
Chrome 1102023 年 2 月 7 日约 3.5 年
Chrome 802020 年 2 月 4 日约 6.5 年

对当前版本的重复请求作为对照组:如果一个网站本身存在波动,那么这两次请求之间就会出现差异。所有请求均来自同一个 Python HTTP 客户端和同一个连接;只有 User-Agent 中的版本号发生了变化。有三个网站至少对一次请求未作出响应,因而被排除,最终剩余 397 个网站。

我们发现了什么

声称版本已放行已质询已拦截质询或拦截占比
Chrome 154,首次请求329422617.1%
Chrome 154,重复请求329422617.1%
Chrome 141318433619.9%
Chrome 129315433920.7%
Chrome 110314434020.9%
Chrome 80307523822.7%

对当前版本的两次请求在每一个网站上都完全一致,这使得表格其余部分很容易解读:每一处差异都来自版本号。

仅统计那些两次都放行了当前版本的网站,拒绝旧版本的网站数量随版本年龄的增加而稳步上升:

声称版本放行当前版本但拒绝此版本的网站数
Chrome 141,1 年前11
Chrome 129,2 年前14
Chrome 110,3.5 年前15
Chrome 80,6.5 年前22

没有一个网站出现相反的情况。陈旧的版本从未带来过帮助。

谁在反应,以及如何反应

将这些网站与早先查询中检测到的防护措施进行交叉比对,可以看出不同供应商划定界限的位置各不相同:

  • 一年的时间就足以让 Akamai 做出反应。 在拒绝 Chrome 141 的 11 个网站中,有 9 个受 Akamai 保护;全部 11 个都以 HTTP 403 响应,其中 10 个是直接拦截而非质询。
  • Cloudflare 对非常旧的版本做出了反应。 在拒绝 Chrome 80 的 22 个网站中,有 10 个受 Cloudflare 保护并返回了质询;另有 9 个受 Akamai 保护。
  • 页面很少向访客说明原因。 只有两个网站针对 Chrome 80 显示了”请更新浏览器”之类的提示,而对当前版本没有显示。大多数网站只是直接拒绝。

这与机器人管理的工作方式是一致的。使用一年前浏览器的真实访客并不常见,而使用六年前浏览器的更是罕见,因此旧版本本身是一个较弱的信号,但与请求中任何其他异常之处结合起来,就会成为一个更强的信号。

代码

下面的函数可对任意 URL 运行相同的比较:它会以随机顺序分别声称每个版本请求页面,并为每次请求返回状态、结果和大小,以及页面是否告知访客其浏览器已过时。将某个版本列出两次即可得到噪声对照组。

import random
import re
import time

import requests

CHROME = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{v}.0.0.0 Safari/537.36"
OUTDATED = re.compile(r"(browser (is|you are using is) (not supported|out of date|outdated|no longer supported)"
                      r"|update your browser|upgrade your browser|unsupported browser|outdated browser)", re.I)


def classify(response):
    """served, challenged or blocked, from the status and well-known challenge markers."""
    body = response.text[:20000].lower()
    if (response.headers.get("cf-mitigated", "").lower() == "challenge"
            or response.headers.get("x-amzn-waf-action", "").lower() == "challenge"
            or "captcha-delivery.com" in body):
        return "challenged"
    if response.status_code in (401, 403, 405, 429) or response.status_code >= 500:
        return "blocked"
    return "served"


def ua_drift_check(url, versions, pause=1.0, seed=None):
    """Request the same URL claiming each Chrome version, in random order, and compare what comes back.
    A version may be listed twice, as a control for how much the site varies on its own."""
    order = list(enumerate(versions))
    random.Random(seed).shuffle(order)
    results = [None] * len(versions)
    for i, v in order:
        headers = {"User-Agent": CHROME.format(v=v), "Accept": "text/html,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9"}
        try:
            r = requests.get(url, headers=headers, timeout=20)
            results[i] = {"version": v, "status": r.status_code, "outcome": classify(r), "bytes": len(r.content),
                          "outdated_notice": bool(OUTDATED.search(r.text))}
        except requests.RequestException as e:
            results[i] = {"version": v, "error": type(e).__name__}
        time.sleep(pause)
    return results

在更改 User-Agent 前后,针对你所依赖的网站运行此代码,并保留结果:如果某个目标开始拒绝你发送的版本,你就能判断版本是否是其中的原因。

本次测量的局限性

  • 仅限首页、单一网络、单一时刻。 更深层的页面、其他国家和其他时间段的表现可能有所不同。
  • 并非真实浏览器。 我们的客户端通过 Python HTTP 库发送了 Chrome 的 User-Agent,因此每个请求本身就带有与 Chrome 不匹配的网络指纹,正如TLS 与 HTTP/2 指纹识别中所解释的那样。这项比较隔离出了版本号本身的影响;它并不能说明每个版本的真实浏览器实际表现会如何。
  • 基线并非为零。 即便是当前版本,也有 17% 的首页拒绝了来自该客户端的请求,这提醒我们:正确的 User-Agent 是必要条件,但并非充分条件。

我们也应当坦诚一个关于我们自身的发现。本周早些时候我们的反机器人查询使用的是 Chrome 129 的 User-Agent:这是一个两年前的字符串,是从一个旧脚本中复制过来的。按照本次测量的结果,这使我们损失了几个百分点的首页放行率。

保持 User-Agent 的时效性

  • 把它当作一项依赖来对待。 把浏览器版本集中放在一处,并按计划更新;Chrome 大约每四周发布一个新的主要版本。
  • 保持在当前版本的几个版本之内。 在我们的数据中,一年前的版本就已经比当前版本更常被拒绝。
  • 保持一切协调一致。 一个当前的 Chrome 版本号,如果配上过时的标头顺序、缺失的客户端提示或非浏览器的网络指纹,仍然是不一致的;设置正确的标头和 User-Agent 涵盖了完整的做法。
  • 每个会话使用一个身份。 在同一会话中切换版本比使用任何单一版本都更可疑。
  • 在 CI 中进行测试。 对关键目标按计划用当前和先前的配置进行检查,可以在版本漂移影响到你的数据之前就发现它;在 CI/CD 中运行爬虫展示了如何搭建这样的流程。
  • 关注拒绝率。 403 和质询数量的缓慢上升是版本漂移的典型症状;这应当纳入目标健康评分。

结论

浏览器版本不是一个只需设置一次的参数。在我们对 397 个顶级网站的测量中,所声称的 Chrome 版本每增加一年的漂移,就会带来更多的拒绝,而这种影响从未反过来发生,有些防护服务甚至只需版本过时一年就会做出反应。

保持版本的时效性,使请求的其余部分与之保持一致,并在对你重要的目标上测量其影响。这是一个爬虫项目能做出的最廉价的可靠性改进之一。

来源与参考

  • Chromium Dash,发布时间表,用于稳定版发布日期和当前版本。
  • Tranco,列表 Q2K34。
  • Shifter 于 2026 年 10 月 5 日使用上述代码发出的请求。

准备好开始了吗?

试用 Shifter 住宅代理,205M+ 个 IP,195+ 个国家,低至 $0.10/GB。

立即开始