JA3 与 JA4 TLS 指纹,讲清楚
发布:2026 年 8 月 30 日 · 阅读约 7 分钟
一句话总结: 你的客户端开一条 HTTPS 连接时,发出的第一条消息——TLS ClientHello——按特定顺序列出它支持的加密套件、扩展和曲线。把这个列表哈希一下,就得到 JA3(较老,基于 MD5)或 JA4(较新,更稳健)指纹。反爬系统拿它和已知浏览器指纹比对,不一致就能在页面加载前直接拦——没有验证码,就是个静默 403。Python 的 requests 发的指纹是任何浏览器都没有的。要过,就发一个真实浏览器的指纹(curl_cffi 的 impersonate,或真实浏览器)——并走住宅 IP,让网络层也对得上。
你在发任何东西之前就发出去的那个指纹
每条 HTTPS 连接都以 TLS 握手开始,你客户端发的第一条消息就是 ClientHello。它此刻还没加密,它公告了你客户端能干什么:支持的 TLS 版本、加密套件列表、提供的扩展、椭圆曲线和签名算法——全都按特定顺序。不同软件把这个列表构造得不一样,而这个差异就是一个指纹。
JA3 和 JA4 是把这个 ClientHello 变成一段短、可比对字符串的两种方式。反爬系统存着一个真实浏览器会产生的指纹库。你的握手一到,它算出你的指纹,和库里比对。你看着像 Chrome,就过这一层;你看着像 Python 的 requests,就可能立刻被拦——在页面一个字节都还没发时,没有任何可见挑战。这就是为什么那么多爬虫撞上一个「静默 403」、光看响应还解释不了。
JA3 怎么运作
JA3 从 ClientHello 取五个字段——TLS 版本、接受的加密套件、扩展列表、椭圆曲线、曲线格式——把它们的数值按顺序拼起来,再用 MD5 哈希。输出是一段像 769,47-53-5-10-49161... 归约成的哈希。TLS 栈相同的两个客户端产生相同的 JA3;浏览器和脚本产生不同的。
JA3 影响很大但很脆。因为它哈希的是固定拼接,细小无害的变动(重排一个扩展、一个新 TLS 构建)就会改变哈希;又因为输入简单易复制,一旦你知道目标值,JA3 相对容易伪造。这个组合——容易意外弄坏、容易故意造假——正是 JA4 出现的动因。
JA4 怎么改进它
JA4 是 JA4+ 指纹套件的一部分(JA4 管 TLS,另有管 HTTP、TCP 等的同伴)。和 JA3 相比它:
- 可读——指纹编码了它看到的东西(TLS 版本、加密套件和扩展的数量、ALPN),而不是一个不透明哈希,分析者能据此推理。
- 稳定——它对浏览器合法打乱的字段排序,所以无害的重排不改变指纹,减少误判。
- 信号更足——它捕获更多握手内容,伪造要对上更多东西才能过。
实际后果:在也算 JA4 的目标上,只发一个浏览器 JA3 已经不够了。现代反爬栈(Cloudflare、Akamai、DataDome 等)越来越多地给 JA4、或 JA3 和 JA4 一起打分,并连同 IP 和 HTTP/2 指纹一起看。
库为什么被标记——以及怎么发一个真实指纹
Python 的 requests 和 httpx 建在 OpenSSL 上,它提供的加密套件与扩展列表和任何在售浏览器都不匹配。再怎么伪造请求头也改不了这点,因为指纹是握手期间就定下的,在请求头存在之前。在 OpenSSL 握手上发一个 Chrome 的 User-Agent 只会更糟:TLS 层说「Python」而请求头说「Chrome」,这个矛盾本身就是机器人信号。
脚本请求的干净解法是 curl_cffi,它用一个能复现真实浏览器 ClientHello 的 TLS 栈。你选要模仿哪个浏览器,它就发那个浏览器的 JA3/JA4:
from curl_cffi import requests
# 发一个真实的 Chrome ClientHello —— 浏览器级 JA3/JA4
proxies = {"https": "http://USER:PASS@gw.roamproxy.com:41080"}
r = requests.get("https://target.example", impersonate="chrome124", proxies=proxies)
print(r.status_code)
如果目标还跑一个 JavaScript 挑战,光靠 curl_cffi 不够——你需要一个既带真实指纹、又能跑 JS 的真实浏览器(Playwright/无头 Chromium)。见 用 browser-use 配住宅代理。
指纹和 IP 必须一致
浏览器级 TLS 指纹解的是一层。它不改变你出去的 IP,而一个完美 Chrome 指纹从机房 ASN 过来,是反爬系统专门抓的那种矛盾。这两道检查是独立的:用 curl_cffi 或真实浏览器修指纹,用住宅代理修网络,让 IP 看着像真实家庭连接。TLS 在其余检查里的位置,见如何过 Cloudflare 和如何过 Akamai Bot Manager。
用于合法的数据采集。理解 TLS 指纹是为了以合理速率采集公开可访问的数据、以及构建兼容的客户端。遵守适用的 robots.txt 和站点条款,不访问你无授权的数据,并限速到永远不影响站点真实用户。
常见问题
JA3 和 JA4 有什么区别?
两者都是 TLS ClientHello 的指纹,但 JA4 是更新、更稳健的设计。JA3 把一组固定的 ClientHello 字段拼起来用 MD5 哈希,这让它既容易伪造、又容易被简单的重排打乱。JA4(JA4+ 家族的一部分)可读、会对某些字段排序使其在无害变动下保持稳定、并捕获更多信号——所以更难假、也更难意外撞上。现代反爬栈越来越多地看 JA4,或 JA3 和 JA4 一起看。
为什么我的请求在浏览器里能过、当脚本却被拦?
因为你的浏览器和脚本发的 ClientHello 不一样。Chrome 按特定顺序提供一组特定的加密套件和扩展,产生一个众所周知的指纹;Python 的 requests(基于 OpenSSL)产生的是另一个、任何真实浏览器都没有的指纹。反爬系统在握手期间读这个指纹,把它不认识的那个拦掉——静默地,在你代码看到页面一个字节之前。
我改个 User-Agent 是不是就能解决?
不能。User-Agent 是在加密连接内、TLS 握手已经完成之后才发的 HTTP 请求头。JA3/JA4 指纹是从握手本身算出来的,在任何请求头之前。发一个 Chrome 的 User-Agent 却带着 Python 的 TLS 指纹,反而是更强的机器人信号——两者互相矛盾。你得改 TLS 层,不是改请求头。
住宅代理会改变我的 TLS 指纹吗?
不会——而这正是要理解这两层的原因。代理改的是你流量出去的 IP;它不碰你的 ClientHello,所以你的 JA3/JA4 指纹不变。IP 信誉和 TLS 指纹是两道独立的检查。你用住宅代理修 IP、用 curl_cffi 或真实浏览器修指纹;防护强的目标两样都查,所以你往往两样都需要。
一个浏览器级 TLS 指纹配上被标记的机房 IP,照样输——反爬系统把 IP 和指纹一起打分,两者必须一致。Roam 住宅 IP——轮换 $2/GB、静态 $4/IP/月,HTTP 与 SOCKS5——给网络层一个真实家庭宽带 ASN,去和你的浏览器指纹匹配。注册账号即送 300MB 试用流量来测。