🎯 一款可定制、具备反检测功能的云浏览器,由自主研发的 Chromium驱动,专为网页爬虫AI 代理设计。👉立即试用
返回博客

网络爬虫:浏览器 TLS 指纹伪装

Alex Johnson
Alex Johnson

Senior Web Scraping Engineer

23-Jul-2026

TL;DR:

  • rnet是一个异步HTTP客户端,它模仿真实浏览器的TLS指纹,因此在服务器分析握手时看到的是Chrome,而不是Python默认。它是用Rust构建的,并暴露给Python使用。
  • rnet不执行JavaScript。 完美的指纹使请求被接受;它并不会执行构建页面内容的脚本。
  • 本指南中的指纹是真实存在的。 一次模拟Chrome的真实运行生成了一个浏览器形状的JA3和一个t13d1516h2的JA4(基于HTTP/2)——但在一个脚本生成的页面上仍然找不到任何引用块。
  • rnet也是调用Scrapeless的客户端。 同样的异步客户端将URL通过POST请求发送到Scrapeless通用抓取API,获取渲染后的HTML,并提取了所有10位作者的信息。
  • 两个不同的问题,清晰分隔。 rnet处理TLS指纹层;Scrapeless处理渲染。两者并不相互替代。
  • 便于从抓取端开始。app.scrapeless.com创建您的Scrapeless API密钥。

rnet是什么,以及它不是

rnet是一个基于Rust网络栈构建的Python异步HTTP客户端,其显著特点是TLS和HTTP指纹的伪装。当客户端打开TLS连接时,其握手的确切形状——密码顺序、扩展、曲线——形成一个服务器可以分析的指纹;一个标准的Python客户端有一个没有任何浏览器产生的指纹,一些网站在没有发送一字节HTML之前就会拒绝它。rnet重现选择的浏览器的握手,因此连接看起来像Chrome或Firefox,并且它通过异步API支持HTTP/2。

它不是一个渲染器。一个匹配的指纹可以让你通过握手级别的分析;但它与JavaScript无关。页面仍然通过脚本构建其内容,而rnet与任何HTTP客户端一样,返回服务器发送的标记——而在一个脚本生成的页面上是没有内容的。因此,rnet抓取器将两个往往混淆的问题分开:指纹层,rnet在其中发挥作用;以及渲染层,这需要浏览器。本指南使用Scrapeless通用抓取API进行渲染,rnet负责调用。有关更全面的介绍,请参阅Python网页抓取教程

安装

rnet是本指南的整个工具链。它编写的版本是rnet 2.4.2:

bash Copy
pip install "rnet==2.4.2"

将您的密钥保存在环境变量中,绝不要放在源代码中:

bash Copy
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"

模拟浏览器,观察它的限制

将rnet指向具有浏览器伪装设置的指纹识别端点,握手将显示为该浏览器的样子。JA3和JA4是TLS握手的标准摘要——根据TLS 1.3标准协商出的指纹——rnet生成浏览器形状的值。然而,同样的客户端在一个脚本生成的页面上却找不到任何内容,因为指纹与填充页面的客户端渲染无关:

python Copy
# fingerprint.py — 一个真实浏览器握手及其限制
import asyncio
import json
import re

import rnet

FINGERPRINT_URL = "https://tls.peet.ws/api/all"
PAGE_URL = "https://quotes.toscrape.com/js/"


async def main() -> None:
    client = rnet.Client(impersonate=rnet.Impersonate.Chrome131)

    resp = await client.get(FINGERPRINT_URL)
    fp = json.loads(await resp.text())
    ja3 = fp.get("tls", {}).get("ja3_hash", "")
    ja4 = fp.get("tls", {}).get("ja4", "")
    print("ja3是32-hex:", bool(re.fullmatch(r"[0-9a-f]{32}", ja3)))
    print("ja4前缀:", ja4.split("_")[0])
    print("HTTP版本:", fp.get("http_version"))

    page_resp = await client.get(PAGE_URL)
    page = await page_resp.text()
    print("JS页面引用块数量:", page.count('class="quote"'))


asyncio.run(main())

握手是浏览器形状的;页面仍然是空的:

text Copy
ja3是32-hex: True
ja4前缀: t13d1516h2
HTTP版本: h2
JS页面引用块数量: 0

JA3哈希根据设计在每次运行中变化——浏览器会随机化其中一个扩展,根据GREASE机制,rnet也会重现这一点——因此抓取器检查的是形状,而不是固定值。JA4前缀t13d1516h2是稳定的部分:TLS 1.3、16个密码套件、16个扩展、HTTP/2。这些都无法渲染页面。

使用Scrapeless进行渲染,由rnet调用

保持相同的异步客户端并更改目标:将URL POST到Scrapeless通用抓取API,该API在服务器端呈现并返回完成的HTML。rnet像处理其他请求一样处理此请求,因此一个客户端同时支持与指纹敏感的抓取和渲染相应请求:

python Copy
# rendered.py — rnet 调用渲染 API,然后提取
import asyncio
import json
import os
import re

import rnet


async def main() -> None:
    client = rnet.Client(impersonate=rnet.Impersonate.Chrome131)
    resp = await client.post(
        "https://api.scrapeless.com/api/v2/unlocker/request",
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
        json={"actor": "unlocker.webunlocker", "input": {"url": "https://quotes.toscrape.com/js/", "js_render": True}},
    )
    html = json.loads(await resp.text())["data"]

    authors = re.findall(r'<small class="author">(.*?)</small>', html)
    print("渲染的引用块:", html.count('class="quote"'))
    print("第一位作者:", authors[0])


asyncio.run(main())

现在内容已经存在:

text Copy
渲染的引用块: 10
第一位作者: 阿尔伯特·爱因斯坦

这就是整个抓取器,它在整个过程中保持在rnet中:携带浏览器指纹的异步客户端也发出渲染调用。通用抓取API负责渲染;rnet负责HTTP请求。

在免费计划中获取您的API密钥:app.scrapeless.com

高级模式

  • 将模拟与当前浏览器匹配。 rnet.Impersonate公开了特定的浏览器版本;选择一个较新的版本,因为几年前的版本指纹本身就是一个信号。客户端的API在目标变化时不会改变。
  • 重用一个客户端。 一个rnet.Client会池化连接;只需创建一次并在异步任务组中共享,而不是每个请求,这正是异步传输的优势所在。
  • 检查形状,而不是值。 因为GREASE会在每个连接中随机化JA3,因此需要验证32位十六进制模式和JA4前缀,如第一个脚本所示——绝不使用硬编码的哈希。
  • 当形状增大时添加解析器。 正则表达式将依赖项保持在一个;对于嵌套记录,将渲染的HTML传递给选择器库并在其中选择字段。

故障排除

  • 一个网站仍然阻止请求。 TLS指纹是众多信号之一。如果浏览器样式的握手不足以通过,阻止是在传输层之上——IP声誉、头部或挑战——请求应遵循Scrapeless路径,该路径处理这些问题。
  • 从您可以在浏览器中看到的页面获取零个块。 内容是JavaScript渲染的;指纹获取了请求的接受,但脚本从未运行。通过js_render将该URL路由到Scrapeless调用。
  • JA3哈希在每次运行时发生变化。 这是正确的——GREASE随机化了它,就像真实的浏览器一样。相应地验证模式和JA4前缀。
  • 响应上的await错误。 rnet是异步的:请求和读取.text()都需要await,而且所有内容都在asyncio.run中运行,如示例所示。

结论

rnet赢得其作为使握手看起来正确的层的地位:一个异步、Rust支持的客户端,携带真实浏览器的TLS指纹,并且还发出渲染调用。它不能解决的问题是页面本身——指纹脚本的零块结果确认了这一点——而一个由rnet发送的Scrapeless POST弥补了这一空白。将指纹层和渲染层连接到单个客户端,演示页面的十位作者通过服务器以Chrome身份读取的连接返回。

创建一个免费的Scrapeless账户以获取API密钥,开发者文档涵盖了unlocker.webunlocker参数。查看Srapeless定价以计划定期作业。

常见问题

问:rnet可以单独抓取JavaScript渲染的页面吗?

不可以。rnet是一个具有指纹模拟的HTTP客户端;它在TLS层接受请求,但不运行任何JavaScript。在脚本生成的页面上,抓取返回缺少内容的标记——指南中的零块结果。将这些页面通过Scrapeless通用抓取API路由,该API对其进行渲染,rnet发出请求。

问:TLS指纹模拟实际上有什么作用?
它塑造了 rnet 的 TLS 握手,以匹配所选择的浏览器,因此服务器在分析 JA3 或 JA4 指纹时看到的是 Chrome 或 Firefox,而不是通用的 Python 客户端。这避免了握手级别的过滤;但无法解决基于 IP 的封锁、挑战页面或需要渲染 JavaScript 的问题。

问:rnet 与 curl_cffi 有何不同?

两者都模拟浏览器指纹。rnet 基于 Rust,并采用异步优先;而 curl_cffi 封装了 curl-impersonate,并且是同步的。根据你的爬虫是否是异步的以及需要哪个模拟目标来选择 — 无论哪种方式,两层模式与渲染 API 是相同的。

问:为什么 JA3 哈希在每次运行时都会改变?

因为真实的浏览器通过 GREASE 机制随机化一个 TLS 扩展,而 rnet reproduces 这种行为。固定的 JA3 本身看起来会显得不自然。验证 32 个十六进制模式和 JA4 前缀,而不是特定的值。

问:使用 rnet 爬取数据合法吗?

HTTP 客户端并不改变收集规则。仅获取公共页面,尊重网站条款和 机器人排除协议 标准化的机器人指令,保持数量有限,并根据适用于你的法律处理任何个人数据。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录