curl_cffi 网络爬虫:Python 中的浏览器 TLS 指纹
Specialist in Anti-Bot Strategies
TL;DR:
- curl_cffi 是一个 Python HTTP 客户端,它模拟真实浏览器的 TLS 握手,因此服务器在指纹识别连接时看到的是 Chrome,而不是脚本客户端。
- 一个
requests.get(url, impersonate="chrome")调用会呈现一个浏览器 JA3 指纹,并协商 HTTP/2,无需浏览器进程。 - JA3 哈希每次运行都会变化,这是正确的:GREASE 在握手中插入随机值,真实 Chrome 也是如此。
- curl_cffi 不运行 JavaScript,因此在一个客户端渲染的页面上,它返回的是空壳;本指南通过 0 个来自 curl_cffi 的引用块和 10 个来自 Scrapeless 的引用块证明了这一点,其中后者进行了渲染。
- 当 TLS 指纹是障碍时,使用 curl_cffi;当页面需要渲染、解决挑战或 IP 轮换时,使用 Scrapeless。
- 开始使用 Scrapeless 免费计划,以便访问仅用 TLS 模拟无法到达的页面。
一个普通的 Python HTTP 客户端在发送一字节 HTML 之前有一个目标可以读取的标记:其 TLS 握手。ClientHello 中的密码和扩展的集合看起来不像浏览器的,如果一个服务器指纹识别该握手,可以仅凭指纹阻止请求。curl_cffi 通过借用真实浏览器的 TLS 签名来填补这一空白。
本指南展示了 curl_cffi 如何呈现浏览器指纹,解释了 JA3 哈希为何不恒定,然后指出了 curl_cffi 无法跨越的界限。当页面需要一个真实浏览器时,Scrapeless Universal Scraping API 接管。以下每个数字均来自一次实际运行。
curl_cffi 的功能
curl_cffi 是一个基于 curl-impersonate 构建的 requests 风格的 HTTP 客户端,其工作是让 TLS 握手看起来像特定浏览器。检查握手的服务器会计算一个 JA3 指纹,这是 ClientHello 的版本、密码和定义在 TLS 1.3 握手 中的扩展的摘要。一个默认的 Python 客户端会产生一个没有浏览器会产生的指纹;而 curl_cffi 产生的是一个某个命名浏览器的指纹。curl_cffi 仓库 列出了它可以模拟的浏览器版本。
curl_cffi 不做的事情是运行浏览器。没有 JavaScript 引擎,没有渲染,没有挑战解决。它是一个指纹,而不是浏览器。
安装
curl_cffi 是一个单一的 pip 安装。
bash
pip install curl_cffi
模拟浏览器指纹
传递 impersonate 和浏览器名称,curl_cffi 将构建该浏览器的 TLS 握手。下面的请求询问一个指纹识别服务报告它所看到的。
python
from curl_cffi import requests
response = requests.get("https://tls.peet.ws/api/all", impersonate="chrome", timeout=30)
fingerprint = response.json()
print("http 版本:", fingerprint["http_version"])
print("ja3 哈希:", fingerprint["tls"]["ja3_hash"])
该服务报告 HTTP/2,这是浏览器协商的,而默认客户端通常不这样做,还有一个 JA3 哈希。
text
http 版本: h2
ja3 哈希: 9a23d5cedcea13483954537602158034
上面的 ja3 哈希 值是一次运行的结果;再次运行脚本,它会改变。这不是一个错误。GREASE 机制 在 ClientHello 中插入随机的保留值,真实的 Chrome 也是如此,因此 JA3 哈希在连接之间会有所不同。保持浏览器样的是握手的结构,而这正是指纹检查实际评估的内容。
curl_cffi 的局限
浏览器 TLS 指纹可以使请求通过握手检查,但它不会渲染页面。在一个使用 JavaScript 构建内容的网站上,curl_cffi 返回的恰好是服务器发送的内容,即一个空壳,而任何数量的 TLS 模拟都无法填补它。这就是需要渲染工具的地方。
在 shell 中设置你的 Scrapeless 密钥。运行时使用真实密钥,并将占位符保持在你的源代码之外。
bash
export SCRAPELESS_API_KEY="sk_your_key_here"
下面的脚本通过两种方式获取一个 JavaScript 渲染的引用页面:使用 curl_cffi 模拟完美的 Chrome 指纹,以及使用启用渲染的 Scrapeless。
python
import json
import os
import urllib.request
from curl_cffi import requests
JS_PAGE = "https://quotes.toscrape.com/js/"
response = requests.get(JS_PAGE, impersonate="chrome", timeout=30)
print("curl_cffi 状态:", response.status_code)
print("curl_cffi 引用块:", response.text.count('class="quote"'))
def scrapeless(url: str) -> str:
payload = json.dumps(
```json
{"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": true, "headless": true}}
).encode()
request = urllib.request.Request(
"https://api.scrapeless.com/api/v2/unlocker/request",
data=payload,
headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=90) as response:
return json.loads(response.read())["data"]
html = scrapeless(JS_PAGE)
print("scrapeless 引用块:", html.count('class="quote"'))
curl_cffi 获得了一个干净的 200 状态和零个引用块,因为引用是由 JavaScript 绘制的,因此它从未运行。Scrapeless 渲染页面并且引用出现。
text
curl_cffi 状态: 200
curl_cffi 引用块: 0
scrapeless 引用块: 10
这个 200 状态是一个陷阱:请求成功,所以看起来没有问题,但内容不在那里。这两个工具解决的是不同的问题。curl_cffi 打败了 TLS 指纹检查;它不渲染,不清除挑战,也不旋转 IP。有关指纹背后的概念,请参阅关于 如何绕过 TLS 指纹识别 的指南,了解服务器测量什么以及原因。
在跨站点运行这些之前,请阅读其 robots.txt 和条款。机器人排除协议 规定了网站要求自动客户端避免的路径,尊重这一点使抓取器得以持续。
准备好访问仅凭指纹无法到达的页面了吗?创建一个免费的 Scrapeless 帐户 并进行渲染。
结论
curl_cffi 是完成一项工作的正确工具:使 Python 请求的 TLS 握手看起来像浏览器的,从而清除指纹检查而不需浏览器进程。它是精准的,其限制同样精准,因为它不运行 JavaScript 并返回一个客户端渲染页面的空壳。将其用于握手是障碍的地方,当需要渲染、清除挑战或新 IP 时,将页面交给 Scrapeless。从上面的脚本开始,将工具与面前的障碍相匹配。
从 Scrapeless 免费计划开始 以获取渲染页面,并在计划定期任务时查看 Scrapeless 定价。
常见问题解答
问:curl_cffi 实际上假装成什么?
curl_cffi 假装成浏览器的 TLS 握手及其 HTTP/2 设置,因此服务器看到的 ClientHello 会匹配命名浏览器版本,而不是默认的 Python 客户端。它不假装成浏览器的 JavaScript 引擎或渲染,仅仅是网络级的指纹。
问:为什么每次运行请求时 JA3 哈希都会变化?
因为 GREASE,它在 TLS ClientHello 中插入随机保留值。真实的 Chrome 也这样做,因此 JA3 哈希在连接之间变化,而握手的实际形状保持类似浏览器。变化的哈希是预期的,而不是模仿失败的迹象。
问:curl_cffi 能否抓取 JavaScript 渲染的页面?
不能。curl_cffi 没有 JavaScript 引擎,因此在客户端渲染页面上,它返回初始的 HTML 空壳,状态为 200,但没有渲染内容。对于那些页面,请使用像 Scrapeless 这样的渲染工具,而保持 curl_cffi 用于内容已在响应中的端点。
问:curl_cffi 和 requests 有什么不同?
requests 库发送标准的 Python TLS 握手,指纹识别服务器可以标记,而 curl_cffi 通过其 impersonate 参数发送浏览器的握手。API 其他方面都比较熟悉,因此 requests.get(url, impersonate="chrome") 接近于替换已被阻止在其指纹上的代码。
问:浏览器 TLS 指纹是否足够避免封锁?
对于 TLS 指纹检查来说是足够的,仅此而已。挑战页面、行为分析和 IP 声誉是 curl_cffi 无法触及的独立防御,因此当这些因素在起作用时,您需要渲染、处理挑战或基于指纹的代理轮换。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



