一、 常用的公开 TLS / JA3 检测接口
你可以直接用 HTTP 客户端访问以下 API,接口会返回当前请求的 TLS 握手细节、JA3/JA4 指纹及分析结果:
Scrapfly JA3 API: [https://tools.scrapfly.io/api/fp/ja3](https://tools.scrapfly.io/api/fp/ja3)特点: 返回 JA3 Hash、JA4 指纹、加密套件 (Cipher Suites) 排序以及是否被判定为 Bot。
Peet TLS Clean API: [https://tls.peet.ws/api/clean](https://tls.peet.ws/api/clean)特点: 详细列出 TLS ClientHello 的所有细节(包括 TLS 扩展、ALPN、HTTP/2 帧顺序)。
HTTPBin Header 校验: [https://httpbin.org/headers](https://httpbin.org/headers)特点: 主要用于测试上层 HTTP Header(如 User-Agent、Sec-Ch-Ua)的还原度。
二、 Python 代码:对比 curl_cffi 与 requests 的指纹差异
通过下述脚本,分别使用 Python 的原生 requests 库和 curl_cffi 请求相同的检测 API,查看底层的指纹区别:
Python
import json
import requests
from curl_cffi import requests as cffi_requests
TARGET_API = "https://tools.scrapfly.io/api/fp/ja3"
print("=" * 60)
print("1. 测试原生 Python requests 库")
print("=" * 60)
try:
# 原生 requests 即使手动伪造 User-Agent,也无法改变底层的 OpenSSL/TLS 握手特征
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
res_req = requests.get(TARGET_API, headers=headers, timeout=10).json()
print(f"[requests] JA3 Hash : {res_req.get('ja3')}")
print(f"[requests] JA4 Fingerprint: {res_req.get('ja4')}")
print(f"[requests] 检测到的客户端 : {res_req.get('client_type', '未知')}")
print(f"[requests] Cipher 密码套件数: {len(res_req.get('ciphers', []))}")
except Exception as e:
print(f"requests 请求异常: {e}")
print("\n" + "=" * 60)
print("2. 测试 curl_cffi 库 (模拟 Chrome 120)")
print("=" * 60)
try:
# curl_cffi 通过 impersonate 参数完全覆写底层的 TLS/HTTP2 指纹
res_cffi = cffi_requests.get(
TARGET_API,
impersonate="chrome120",
timeout=10
).json()
print(f"[curl_cffi] JA3 Hash : {res_cffi.get('ja3')}")
print(f"[curl_cffi] JA4 Fingerprint: {res_cffi.get('ja4')}")
print(f"[curl_cffi] 检测到的客户端 : {res_cffi.get('client_type', '未知')}")
print(f"[curl_cffi] Cipher 密码套件数: {len(res_cffi.get('ciphers', []))}")
except Exception as e:
print(f"curl_cffi 请求异常: {e}")
三、 核心差异解析(为什么 requests 会被拦截)
运行上述脚本后,会观察到以下核心差异:
| 维度 | 原生 requests | curl_cffi (impersonate="chrome120") |
| 底层依赖 | OpenSSL (Python urllib3 封装) | libcurl-impersonate (C 语言重构) |
| JA3 Hash | 固定的 Python 特征 Hash (例如 b32309a26951...) | 与真实 Chrome 120 完全一致的 Hash |
| Cipher Suites 排序 | OpenSSL 默认的几个通用加密算法(且顺序固定) | 包含 Chrome 特有的 GREASE 随机占位符与算法顺序 |
| HTTP 版本 | 默认退回到 HTTP/1.1 协议 | 支持 HTTP/2 协商与 Chrome 特有的 SETTINGS 帧顺序 |
| WAF / 风控判定 | 直接判定为 Python Bot / 自动化脚本 | 判定为真实桌面端 Chrome 浏览器 |
为什么简单改 UA 对 requests 无效?
阿里云 WAF、Cloudflare 等风控系统会在 TLS 握手阶段(即请求数据解密之前)就读取客户端发送的 ClientHello 报文。
如果你用 requests 库发包,即使 HTTP 层的 User-Agent 声明自己是 Chrome/120,风控系统只要对比一下 TLS 握手中的 JA3 指纹/密码套件列表,发现是标准的 Python OpenSSL 特征,就会认定为 指纹冲突/伪造 UA,从而直接予以封禁或弹验证码。curl_cffi 则从 TLS 握手阶段就实现了对浏览器的完整模拟。


