TLS 指纹识别 (JA3/JA4):为什么干净的 IP 仍然被封锁

您的住宅 IP 是干净的,您的请求头完美无缺,但在第一次请求时仍然收到 403。问题出在您的 TLS 握手——这是反机器人系统在任何 HTTP 交换之前读取的指纹。

这是每个抓取者最终都会遇到的难题:住宅 IP 是干净的,User-Agent 是当前的 Chrome 字符串,请求头完美无缺——而第一次请求就返回 403。无论如何轮换都无法解决。原因是封锁发生在您的请求头被读取之前。现代反机器人系统会对您的 TLS 握手进行指纹识别,而默认的 Python、Go 或 curl 客户端在 ClientHello 中就会自我暴露为机器人,这比 HTTP 低一层。这就是 JA3 和 JA4 指纹识别的作用,以及为什么 curl 在相同的 URL 上得到 403 而浏览器得到 200

TLS 指纹识别读取的内容

每个 HTTPS 连接都以 TLS 握手开始。客户端发送一个 ClientHello,按照特定顺序声明它想要的 TLS 版本、支持的密码套件、提供的扩展(SNI、ALPN 等)、椭圆曲线和点格式。这些都不会直接命名您的浏览器——但确切的组合和顺序几乎是软件栈的唯一签名。Chrome 的列表与 Firefox 的不同,二者与 Python 的 requests 或 Go 的 net/http 差异很大。读取握手,您可以在交换任何 HTTP 字节之前猜测客户端。

JA3:五个字段合成一个 MD5 哈希

JA3 由 Salesforce 的工程师于 2017 年发布,是经典的方法。它从 ClientHello 中提取五个字段——TLS 版本、密码、扩展、椭圆曲线和点格式——将它们连接起来,并通过 MD5 运行字符串。一个具体的例子:这些字段

771,4865-4866-4867,0-11-10-35-16-5-13,29-23-24,0

哈希为 e7d705a3286e19ea42f587b344ee6865——这是标准 curl 构建的指纹。反机器人供应商会保留这些哈希的数据库。JA3 匹配已知抓取库的请求会受到更严格的速率限制、挑战或直接封锁。最强大的检查是关联:如果您的 User-Agent 声称是 Chrome 120,但您的 JA3 表示 python-requests,仅这一矛盾就足以让您失败。

为什么 JA3 被 JA4 取代

JA3 有一个弱点,使其作为稳定信号失效。从 Chrome 110 和 Firefox 114 开始,浏览器在每次连接时随机化 TLS 扩展的顺序以抵抗指纹识别。这意味着现在真实的浏览器每个会话都会生成一个不同的 JA3 哈希——因此原始 JA3 在真实用户上会产生误报。行业的回应是 JA3N(一种在哈希前对扩展进行排序以取消随机化的标准化变体)和 JA4,这是 FoxIO 提出的由 JA3 的创建者构建的新方案。

JA4 更易读且更难伪造。它使用三部分 a_b_c 布局,例如 t13d1516h2_8daaf6152771_e5627efa2ab1。仅前缀就透露了很多信息:t 代表 TCP,13 代表 TLS 1.3,d 代表基于域名的 SNI,15 代表密码套件,16 代表扩展,h2 代表 HTTP/2 作为第一个 ALPN——随后是两个截断的哈希,分别是排序后的密码和扩展加签名算法。JA4 是一个家族中的一员:JA4S 指纹识别服务器,JA4H 指纹识别 HTTP 请求头,JA4X 指纹识别证书,JA4T 指纹识别原始 TCP 层。

图示显示默认客户端在 TLS 握手时被阻止,而浏览器形状的客户端通过,在发送任何 HTTP 之前
JA3/JA4 查找发生在握手时——不匹配的指纹在您的请求头被读取之前就是 403。

为什么干净的 IP 无法拯救您

这就是让只投资于代理的人困惑的部分。一个干净的住宅 IP 告诉网站流量来自真实网络。一个与浏览器不匹配的 TLS 握手则告诉它流量来自脚本。当这两个信号不一致时,握手胜出,因为它更难被意外伪造。即使您轮换一千个干净的出口,如果所有一千个都携带相同的 python-requests JA3,您仍然会在每次请求中失败。IP 和指纹是独立的轴——您必须同时正确。

具体来说,这在针对 Cloudflare 保护目标的通过率中表现出来:默认的 requests 客户端大约能通过两百分之一的请求,使用 HTTP/2 的 httpx 稍微好一些,而与浏览器匹配的客户端则进入中等偏高的通过率。每种情况下使用的都是相同的 IP 池。变量是 TLS 栈。Akamai 通过将 TLS 指纹与 HTTP/2 指纹结合起来进一步提高了门槛——SETTINGS 帧值、窗口大小和流优先级——因此即使正确的 JA3 也可能被捕获,如果 HTTP/2 层看起来像脚本。我们对 为什么 Akamai 阻止大多数代理流量 的分析深入探讨了该栈。

Cloudflare 通过率的柱状图,显示从默认 python-requests 到 httpx 和 curl_cffi 再到无头浏览器
每种情况下都是相同的干净 IP——只有 TLS 栈改变了通过率。解决方法是握手,而不是出口。

真正通过的伪装选项

您不能通过调整密码字符串将浏览器握手附加到标准客户端上——OpenSSL 和 urllib3 的选项并未暴露 JA3 读取的每个参数。有效的方法是使用一个能够说出浏览器确切 TLS 方言的客户端:

# curl_cffi: a browser-shaped handshake in two lines
from curl_cffi import requests

session = requests.Session(impersonate="chrome120")
r = session.get(
    "https://example.com",
    proxies={"https": "http://USER:PASS@gate.quantumproxies.io:8000"},
    timeout=20,
)
print(r.status_code)  # matched JA3 + a clean residential exit

注意代码片段中的代理。伪装和干净出口是互补的,而不是替代品:浏览器形状的握手让您通过 TLS 检查,而 干净的住宅 IP 使请求不被列入信誉封锁名单。将它们配对,您就能同时修复两个轴。

使用 Scraper API 跳过 TLS 军备竞赛

何时将整个问题交给他人

DIY 伪装有效,直到目标轮换其检测,添加 HTTP/2 指纹或需要 JavaScript 执行——然后您就像在维护一个浏览器仿真库作为第二份工作。一个 Scraper API 携带真实的、旋转的浏览器指纹,匹配 HTTP/2 层,按需运行 JS,并从单次调用中返回干净的 HTML、markdown 或 JSON。它将 TLS 军备竞赛变成他人的问题,而您可以专注于数据。有关网站如何标记自动化的更广泛图景,请参阅我们的 每个代理检测信号 指南。

常见问题解答

什么是 JA3 指纹?

JA3 指纹是从 TLS ClientHello 中提取的五个字段的 MD5 哈希——TLS 版本、密码套件、扩展、椭圆曲线和点格式。因为每个软件栈对这些字段的排序不同,哈希在交换任何 HTTP 数据之前就能识别客户端(Chrome、Firefox、curl、Python)。

JA3 和 JA4 有什么区别?

JA3 使用 MD5 对五个 ClientHello 字段进行哈希,当浏览器随机化扩展顺序时会失效。JA4 来自 FoxIO,在哈希前对字段进行排序,因此可以在随机化中幸存下来,添加了一个人类可读的元数据前缀,并涵盖 QUIC/HTTP/3、ALPN 和签名算法。JA4 是更可靠的现代信号;JA3N 是 JA3 的标准化权宜之计。

我能仅通过代理绕过 TLS 指纹识别吗?

不能。代理改变的是 IP,而不是握手。如果您的 TLS 指纹与已知的抓取库匹配,轮换出口也无济于事——每个请求仍然携带机器人的签名。您需要一个能够重现浏览器 TLS 栈的客户端,然后在上面使用干净的代理以获得 IP 信誉。

Python requests 有可检测的 TLS 指纹吗?

有。requests 使用 urllib3,其密码和扩展顺序独特,反机器人供应商已经将其编入目录,因此其 JA3 是已知机器人的签名。切换到带有 impersonate 配置文件的 curl_cffi 以发送与浏览器匹配的握手。

TLS 指纹识别将战斗移到了 HTTP 以下,这就是为什么请求头技巧和 IP 轮换不再单独足够。匹配握手,保持出口干净,403-on-request-one 问题就会消失。这是技术指导,而不是忽视网站条款的许可——始终在法律和目标规则范围内进行抓取。

让 Scraper API 处理 JA3、JA4 和 HTTP/2