您购买了昂贵的住宅IP,设置了轮换,插入了真实的User-Agent——但解析器仍然被送入验证码或收到空响应。问题几乎总是出在TLS指纹上:您发送HTTPS请求的库“听起来”不像真正的浏览器。Wildberries、Ozon、Cloudflare和Akamai的反机器人系统在检查您的IP地址之前就能看到这一点。
什么是TLS指纹,为什么它比IP更重要
当客户端建立HTTPS连接时,它向服务器发送一个包ClientHello——TLS握手的一部分。它加密了支持的TLS版本列表、密码套件、扩展顺序、椭圆曲线和压缩算法。这组参数对于每个“库 + 操作系统 + TLS栈版本”的组合都是独特的。
Chrome、Firefox和Safari以自己的方式形成ClientHello,这组参数几乎在每次请求中都不会改变——与容易伪造的IP或User-Agent不同。而标准的HTTP库——requests、urllib3、Java中的标准HttpClient、Node.js的内置TLS栈——形成完全不同的ClientHello,因为它们使用OpenSSL或其他库的方式与浏览器不同。
这就是为什么您可以连接一个完美“干净”的住宅IP,插入真实Chrome的最新User-Agent——但仍然会被封锁。服务器看到来自住宅的用户IP,看到“Chrome 124”的头部,但TLS握手却说:“这是一个Python脚本”。不匹配是反机器人系统的直接信号。
反机器人系统如何通过JA3/JA4识别解析器
为了将ClientHello的参数转换为紧凑的标识符,使用了JA3(及其更新版本JA4)算法。它获取TLS版本、密码列表、扩展和曲线,将它们拼接成一个字符串并通过MD5进行哈希。结果是一个短哈希,例如769,47-53-5-10...,0-23-65281...,29-23-24,0,它唯一标识客户端的“指纹”。
反机器人提供商(Cloudflare、Akamai、PerimeterX、DataDome——以及使用Wildberries和Ozon的类似产品)维护着已知JA3/JA4哈希的数据库,这些哈希与流行HTTP库关联:requests、aiohttp、Scrapy、Node fetch、Java HttpClient、Go net/http。如果哈希与已知的“脚本”签名匹配,而不是Chrome/Firefox/Safari的签名——请求在行为分析之前就被标记为可疑。
然后系统查看TLS指纹与声明的User-Agent是否匹配。如果头部写着“Windows上的Chrome 124”,而TLS指纹对应于Python标准库中的OpenSSL 1.1.1——这被称为TLS/HTTP不匹配,是检测自动化的最可靠信号之一。即使在完美的住宅IP和正确的头部下,解析器也会被这样识别。
如何检查您的TLS指纹:工具
在修复问题之前,您需要看到服务器所看到的。有几个公共服务可以显示您的JA3/JA4哈希和完整的ClientHello参数集:
- tls.peet.ws——显示JA3、JA4、密码和扩展列表,以JSON格式,方便脚本自动检查。
- ja3er.com——已知JA3哈希的数据库,绑定到特定的库和浏览器。
- browserleaks.com/tls——将您的指纹与典型浏览器进行可视化比较。
- Wireshark本地——如果您想在从脚本发送请求时查看原始ClientHello包。
实际测试很简单:在普通Chrome中打开tls.peet.ws并记录JA4哈希。然后通过您的解析器(通过requests、curl_cffi或任何其他库)通过相同的代理发送GET请求到相同的地址,并比较哈希。如果它们不同——服务器在每次请求中看到“浏览器”和“脚本”之间的差异,无论IP多么干净。
Python检查:requests、httpx、curl_cffi
我们来实际分析一下为什么Python的标准库会暴露解析器。通过requests的普通请求:
import requests
resp = requests.get("https://tls.peet.ws/api/all", proxies={
"https": "http://user:pass@proxy_host:port"
})
print(resp.json()["tls"]["ja4"])
# 结果将与真实Chrome的JA4不同,
# 因为requests使用的是Python的标准ssl模块
问题在于requests和httpx通过ssl模块使用系统的OpenSSL,而TLS的扩展顺序和集合是固定的,与Chrome/Firefox不匹配。解决方案是使用curl_cffi库,它使用带有真实浏览器TLS配置的补丁curl:
from curl_cffi import requests as cffi_requests
resp = cffi_requests.get(
"https://tls.peet.ws/api/all",
impersonate="chrome124",
proxies={"https": "http://user:pass@proxy_host:port"}
)
print(resp.json()["tls"]["ja4"])
# 哈希将与桌面上的真实Chrome 124相同
参数impersonate使curl_cffi不仅重现ClientHello,还重现HTTP/2头部的顺序(frame order),这也包含在指纹中。类似的方法被tls-client库用于Go和undetected-chromedriver用于通过真实浏览器而非HTTP客户端进行解析的人。
如果解析通过无头浏览器(Playwright、Puppeteer、Selenium)进行,TLS指纹由Chromium/Firefox引擎生成,默认情况下与真实浏览器相同。但这里出现了另一个问题——JS级别的自动化签名(webdriver标志、canvas指纹),因此对于无头场景,额外需要使用类似playwright-stealth的补丁。
TLS + HTTP/2 + 头部:为什么组合很重要
TLS指纹只是检测的一个层面。反机器人系统同时检查多个层面:
- TLS ClientHello (JA3/JA4)——密码和扩展的集合。
- HTTP/2指纹——伪头部的顺序(:method、:path、:authority)、SETTINGS帧的设置、窗口大小。
- HTTP头部——普通头部的顺序和集合(Accept-Language、Sec-Ch-Ua、Sec-Fetch-*)。
- User-Agent——必须与TLS配置版本匹配:如果UA显示“Chrome 124”,而TLS对应于Chrome 110,这也是可疑的。
常见错误是将User-Agent更新到最新版本的Chrome,而忘记在curl_cffi或其他库中更新TLS配置。这样的版本不匹配对反机器人系统来说是显而易见的,就像完全没有伪装一样。请检查impersonate的版本和User-Agent中的版本是否匹配,并在浏览器新版本发布时同步更新这两个参数。
还有一个问题是头部的顺序。浏览器以严格的顺序发送头部,而许多HTTP库按字母顺序或按添加到代码的顺序对其进行排序。即使头部集合与浏览器相同,不正确的顺序也是高级反机器人系统(如DataDome)的额外信号。
代理的角色:为什么干净的IP无济于事
住宅IP解决了一个具体问题——降低了地理位置、ASN和地址声誉的可疑性。数据中心IP通常在黑名单上,因为它们产生大量自动化流量,而住宅IP属于真实的提供商和普通用户。对于Wildberries、Ozon或Avito的解析来说,这一点至关重要:没有干净的IP,请求仅凭这一特征就会被封锁,甚至不检查TLS。
但是IP和TLS指纹是两个独立的保护层,解决不同的问题。IP告诉服务器请求来自“哪里”,而TLS指纹则告诉“用什么”发送请求。因此,干净的IP和正确的TLS配置的组合是稳定解析的最小要求。对于高请求频率和激进反机器人系统的任务,最好使用住宅代理,它们在IP声誉方面的封锁率较低,但一定要与能够正确重现真实浏览器TLS配置的库结合使用。
对于在市场上监控价格的任务,速度和请求量很重要,通常使用数据中心代理,结合通过curl_cffi的TLS伪装——这比住宅代理便宜,并且如果网站的反机器人系统不太激进,效果相当好。而对于那些网站积极检查移动网络的任务(例如,通过API解析移动版本的应用程序),使用移动代理——它们通过运营商网络的声誉提供额外的信任级别。
无检测解析器设置检查清单
在将解析器投入生产之前,将检查整合为一个统一的过程:
- 通过tls.peet.ws测量您的脚本的JA4哈希,并与相同版本的真实浏览器进行比较。
- 使用支持TLS伪装的库:curl_cffi(Python)、tls-client(Go)、CycleTLS(Node.js)。
- 将TLS配置版本(impersonate)与User-Agent中的版本同步。
- 检查HTTP头部的顺序——它必须与真实浏览器相匹配,而不是按字母顺序。
- 根据您的任务的地理位置连接干净的住宅或移动IP。
- 将IP轮换与TLS配置分开设置——不要将它们紧密绑定在一起。
- 在Chrome新版本发布时定期更新TLS配置——旧的签名比想象中更快进入反机器人数据库。
- 对于带有JS检查的场景(Cloudflare挑战),使用带有stealth补丁的无头浏览器,而不是干净的HTTP客户端。
库和工具比较
| 工具 | 浏览器的TLS指纹 | 速度 | 何时使用 |
|---|---|---|---|
| requests / httpx | 没有,返回脚本 | 高 | 没有TLS检测的网站,内部API |
| curl_cffi | 是,完全复制 | 高 | 市场、Cloudflare/Akamai反机器人 |
| tls-client (Go) | 是 | 非常高 | 高负载,大规模解析 |
| Playwright / Puppeteer | 是,真实引擎 | 低 | JS渲染、Cloudflare挑战、复杂的SPA |
| Scrapy(标准) | 没有 | 高 | 没有严格反机器人保护的网站 |
结论
TLS指纹是一个保护层,许多解析器完全忽视,花费资源寻找完美的IP和User-Agent,却忘记了TLS握手的结构会在服务器查看头部之前就暴露出自动化。解决方案是使用支持TLS伪装的库(curl_cffi、tls-client),将配置版本与User-Agent同步,并在大规模运行之前检查最终的JA4哈希。
IP仍然是一个重要因素——没有干净的地址,即使是完美的TLS指纹也无法绕过网络声誉的封锁。对于市场解析和价格监控,合理地将正确的TLS设置与住宅代理结合使用——这样的组合覆盖了两个检测层,并显著降低了长时间解析会话中的封锁率。