curl_cffi:支持原生模拟浏览器 TLS/JA3 指纹的 Python 库

问题复现

网页能正常打开:https://zhengce.beijing.gov.cn/bs/api/v2/server/th/notice/info?id=1287842303418548224&enabled=Y

import requests
headers = {...}
url = 'https://zhengce.beijing.gov.cn/bs/api/v2/server/th/notice/info?id=1287842303418548224&enabled=Y'
response = requests.get(url,headers=headers)
print(response)

在进行网页请求的时候经常出现如下错误:
requests.exceptions.SSLError: HTTPSConnectionPool(host='zhengce.beijing.gov.cn', port=443): Max retries exceeded with url: /bs/api/v2/server/th/notice/info?id=1287842303418548224&enabled=Y (Caused by SSLError(SSLError(1, '[SSL: BAD_ECPOINT] bad ecpoint (_ssl.c:1129)')))

诸如此类情况,网页能正常打开,在使用requests 请求,总是报上述的错误。排查headers内容不全和代理不稳定以及网络等外界因素,在各个条件齐全的条件下报错依旧存在。

问题解决

from curl_cffi import requests as req1
s = req1.Session()
headers = {...}
url = 'https://zhengce.beijing.gov.cn/bs/api/v2/server/th/notice/info?id=1287842303418548224&enabled=Y'
response = s.get(url,headers=headers)
print(response)

TLS指纹

现在绝大多数的网站都已经使用了 HTTPS,要建立 HTTPS 链接,服务器和客户端之间首先要进行 TLS 握手,在握手过程中交换双方支持的 TLS 版本,加密算法等信息,建立成功后再进行数据传输。不同的客户端之间的差异 很大,而且一般这些信息还都是稳定的,所以服务端就可以根据 TLS 的握手信息来作为特征,识别 一个请求是普通的用户浏览器访问,还是来自 Python 脚本等的自动化访问。

  • TLS 握手过程概述
    TLS(Transport Layer Security)握手是建立安全的 HTTPS 连接的关键步骤。在握手开始时,客户端会向服务器发送一个 “ClientHello” 消息。这个消息包含了客户端支持的 TLS 版本(如 TLS 1.2、TLS 1.3)、加密算法套件(如 RSA、ECDHE 等加密算法和对应的哈希算法组合)、随机数等信息。服务器收到 “ClientHello” 后,会根据自身支持的配置,选择一个合适的 TLS 版本和加密算法套件,然后发送 “ServerHello” 消息作为回应,同时还会发送证书等信息用于身份验证。
  • 客户端差异作为识别特征
    不同的客户端(如各种浏览器和 Python 脚本)在 TLS 握手阶段呈现出明显的差异。例如,常见的浏览器(如 Chrome、Firefox、Safari)都有自己固定的 TLS 版本偏好和加密算法组合模式。浏览器开发者会根据安全标准和性能优化不断更新这些配置,但在一定时期内相对稳定。而 Python 脚本如果没有进行特殊处理,其 TLS 握手信息可能与浏览器有很大不同。比如,Python 的标准库urllib或简单的requests库(在没有额外配置的情况下)在 TLS 配置上可能比较简单和固定,没有像浏览器那样丰富的加密算法套件支持或者 TLS 版本选择策略。
  • JA3 是生成 TLS 指纹的一个常用算法。它的工作原理也很简单,大概就是把以上特征拼接并求 md5。不同网站的生成的指纹可能有差异,但是多次访问同一个网站生成的指纹是稳定的,而且能区分开 不同客户端。

  • 快速查看自己的TSL指纹信息
    https://tls.browserleaks.com/json
    https://tls.peet.ws/
    https://tls.peet.ws/api/all
  • 进行对比:
    网页端的TSL指纹

    使用request请求的TSL指纹

    使用curl_cffi 请求的TSL指纹

    通过对比发现,经过JA3加密之后的TSL指纹与网页端都有"akamai_hash"和"akamai_text",并且能顺利访问页面。

    curl_cffi库

  • 安装:pip install curl_cffi
  • 使用示例
  • from curl_cffi import requests
    # 注意这个 impersonate 参数,指定了模拟哪个浏览器
    r = requests.get("https://tls.browserleaks.com/json", impersonate="chrome101")
    

    其他指纹技术

  • HTTP Header 指纹。通过浏览器发送的 header 的顺序和值的组合来判断是合法用户还是爬虫
  • DNS 指纹。参考:http://dnscookie.com
  • 浏览器指纹。通过 canvas,webgl 等计算得到一个唯一指纹,Cookie 禁用时监视用户的主流技术
  • TCP 指纹。也是根据 TCP 的一些窗口、拥塞控制等参数嗅探、猜测用户的系统版本
  • 总结一下,指纹技术就是通过不同的设备和客户端在参数上的微妙差异来识别用户。本来按照规范, 这些值都是应该任意选取的,但是,现实世界中,服务端反而对不同值采取了区别对待。指纹技术 可以说应用到了 OSI 网络模型中所有可能的层,基于 HTTP header 顺序的指纹工作在第七层应用层, SSL/TLS 指纹工作在传输层和应用层之间,TCP 指纹在第四层传输层。而在 TCP 之下的 IP 层和物理 层,因为建立的不是端到端的链路,所以只能收集上一跳的指纹,没有任何意义。

    对于爬虫来说,User-Agent 相当于自报门户。除了初学者以外,没有人会顶着 Python/3.9 requests 这样的 UA 去爬的,而指纹则是很难更改的内部特征。通过指纹技术可以防御一大批爬虫,而使用 能够模拟指纹的 http client 则轻松突破这道防线。

    对于普通用户来说,各种指纹造成了极大的隐私泄露风险。即使按照 GDPR 等监管政策的要求,用户拒绝使用 Cookie 时,互联网公司依然可以通过各种指纹来定位追踪用户,乃至于区别对待。平等、 匿名、自由地使用个人数据和公开数据应该是一项基本人权。在立法赶不上技术更新的时代,我们应该用技术手段捍卫自己的权利。

    参考资料

    https://zhuanlan.zhihu.com/p/601474166?utm_id=0

    作者:TU不秃头

    物联沃分享整理
    物联沃-IOTWORD物联网 » curl_cffi:支持原生模拟浏览器 TLS/JA3 指纹的 Python 库

    发表回复