首页 > 编程语言 >curl_cffi: 支持原生模拟浏览器 TLS/JA3 指纹的 Python 库

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

时间:2023-01-29 23:15:07浏览次数:68  
标签:TLS 浏览器 Python cffi 指纹 https curl com

原文首发于我的博客:https://yifei.me/note/2719

越来越多的网站开始使用 TLS 指纹反爬虫,而 Python 中竟然没有任何方法解决这个问题。前一阵
看到由国外大神写了一个 curl-impersonate 命令行工具,可以完美模拟主流浏览器的指纹,遂用
cffi 封装成了 Python 库 curl_cffi,这样就可以
继续愉快地写爬虫啦!

TLS 指纹

首先来回顾一下什么是 TLS 指纹。如果已经了解,可以直接跳到后边的 curl_cffi 部分。

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

JA3 是生成 TLS 指纹的一个常用算法。它的工作原理也很简单,大概就是把以上特征拼接并求 md5。

有证据表明,阿里云、华为云、Akamai 和 Cloudflare 都在使用 TLS 指纹技术来识别机器访问流量。
Akamai 更是直接在宣传稿中说明了在通过 TLS 指纹技术检测非法请求。

在真正发现 Cipher Stunting 之前,Akamai 观察到的 TLS 指纹大概有数万个。在初步发现后不久,
TLS 指纹数量激增至数百万,最近跃升至数十亿。
https://www.akamai.com/blog/security/bots-tampering-with-tls-to-avoid-detection

查看 tls 指纹的网站有:

不同网站的生成的指纹可能有差异,但是多次访问同一个网站生成的指纹是稳定的,而且能区分开
不同客户端。下文以第一个网站为例。

浏览器的指纹:53ff64ddf993ca882b70e1c82af5da49

browser ja3

httpx 的指纹:44423a0e34badcd72364f09ff481fcc9

Python 3.10.9 (main, Jan 11 2023, 15:21:40) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import httpx
>>> r = httpx.get("https://tls.browserleaks.com/json")
>>> r.json()
{'ja3_hash': '44423a0e34badcd72364f09ff481fcc9', 'ja3_text': '772,4866

curl 的指纹:0ef95c8302480557fbc3cd8a7c87973c

$ curl --version

curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/3.0.2 zlib/1.2.11 brotli/1.0.9 zstd/1.4.8 libidn2/2.3.2 libpsl/0.21.0 (+libidn2/2.3.2) libssh/0.9.6/openssl/zlib nghttp2/1.43.0 librtmp/2.3 OpenLDAP/2.5.11
Release-Date: 2022-01-05
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets zstd

$ curl https://tls.browserleaks.com/json

{"ja3_hash":"0ef95c8302480557fbc3cd8a7c87973c","ja3_text":"772,4866-4867-4865

可以看到,每个客户端的指纹都是不一致的,服务端也就可以据此防御异常流量。显然,防御等级分
两个层次。

非法指纹黑名单

这个思路很直接,把常用的爬虫工具的指纹收集起来,然后全都屏蔽了就好了。比如说:curl,
requests, golang 访问时,直接 403。当然,突破也很简单,别用默认的指纹,直接随便改一下
tls hello 包的值就行了。

比如,修改 httpx 的 TLS 协议。以 httpx 为例:

# 默认 cipher 在这里定义:https://github.com/encode/httpx/blob/master/httpx/_config.py
import ssl
import httpx

# create an ssl context
ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS)
CIPHERS = 'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH'
ssl_context.set_ciphers(CIPHERS)

r = httpx.get('https://tls.browserleaks.com/json', verify=ssl_context)
print(r.json())

# {'ja3_hash': 'cc8fc04d55d8c9c318409384eee468b6'

可以看到 JA3 指纹已经变了。

合法指纹白名单

既然指纹可以随便改,那就直接只认常用浏览器的指纹好了。这时候如果爬虫或者其他脚本再想要
突破防御,需要把每一个值都改成和浏览器都完全相同,难度还是挺大的。尤其是考虑到大多数
语言的标准库都是直接使用系统的 SSL 库,很多底层的东西直接没提供接口,所以这种防御还是非常
有效的。

例如,Python 使用了 OpenSSL,而 Chrome 则使用了 BoringSSL,这两者的细节差异很多。所以,
纯 Python 的库,比如 requests 和 httpx,再怎么改也不可能改成和 Chrome 一样的指纹,必须
使用第三方的 C 扩展库,才能够实现完美模拟浏览器指纹。

此外,还又一个小细节,可以由 TLS 指纹反推出客户端是从哪些操作系统或者软件来的,如果和
User-Agent 互相矛盾,那也说明有问题。不过实际中,我还没有遇到这种情况。

curl_cffi

为了完美模拟浏览器,国外有大佬给 curl 打了一些 patch,把相应组件全部都替换成了浏览器使用
库,连版本都保持一致,这样就得到了和浏览器完全一样的指纹,这个库是:curl-impersonate

Python 中早就有 curl 的 binding -- pycurl,但是非常难用,安装的时候总是出现编译错误;接口
也很低级,相比 requests,甚至 urllib,用起来都比较费劲。curl-impersonate 的作者提出使用
环境变量 + 替换 libcurl 来在不同语言中使用 curl-impersonate,但是似乎 pycurl 没法工作。
于是乎,我直接另起炉灶,写了一个 curl(-impersonate) 的 Python binding.

相比 pycurl,有以下优点:

  • 原生支持 curl-impersonate
  • pip install 直接是二进制包,无需编译,也就不会有编译错误
  • 提供了一个简单的 requests-like 接口

废话少说,看代码吧!

pip install curl_cffi

使用起来也很简单

from curl_cffi import requests

# 注意这个 impersonate 参数,指定了模拟哪个浏览器
r = requests.get("https://tls.browserleaks.com/json", impersonate="chrome101")

print(r.json())
# output: {'ja3_hash': '53ff64ddf993ca882b70e1c82af5da49'

我们可以看到,输出的 JA3 指纹和浏览器中的指纹一模一样

代理也支持:

>>> proxies={"http": "http://localhost:7777", "https": "http://localhost:7777"}

>>> r = requests.get("http://baidu.com", 
        proxies=proxies,
        allow_redirects=False,
        impersonate="chrome101"
    )
>>> r.text
'<html>\r\n<head><title>302 Found</title></head>\r\n<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n<hr><center>bfe/1.0.8.18</center>\r\n</body>\r\n</html>\r\n'
>>> r = requests.get("https://tls.browserleaks.com/json",
        proxies=proxies,
        impersonate="chrome101"
    )
>>> r.json()
{'ja3_hash': '53ff64ddf993ca882b70e1c82af5da49'

同样的功能,也可以用底层一点的 Curl 对象:

from curl_cffi import Curl, CurlOpt
from io import BytesIO

buffer = BytesIO()
c = Curl()
c.setopt(CurlOpt.URL, b'https://tls.browserleaks.com/json')
c.setopt(CurlOpt.WRITEDATA, buffer)

c.impersonate("chrome101")

c.perform()
c.close()
body = buffer.getvalue()
print(body.decode())

仓库在这里:https://github.com/yifeikong/curl_cffi

其他指纹技术概览

  1. HTTP Header 指纹。通过浏览器发送的 header 的顺序和值的组合来判断是合法用户还是爬虫
  2. DNS 指纹。参考:http://dnscookie.com
  3. 浏览器指纹。通过 canvas,webgl 等计算得到一个唯一指纹,Cookie 禁用时监视用户的主流技术
  4. TCP 指纹。也是根据 TCP 的一些窗口、拥塞控制等参数嗅探、猜测用户的系统版本

总结一下,指纹技术就是通过不同的设备和客户端在参数上的微妙差异来识别用户。本来按照规范,
这些值都是应该任意选取的,但是,现实世界中,服务端反而对不同值采取了区别对待。指纹技术
可以说应用到了 OSI 网络模型中所有可能的层,基于 HTTP header 顺序的指纹工作在第七层应用层,
SSL/TLS 指纹工作在传输层和应用层之间,TCP 指纹在第四层传输层。而在 TCP 之下的 IP 层和物理
层,因为建立的不是端到端的链路,所以只能收集上一跳的指纹,没有任何意义。

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

标签:TLS,浏览器,Python,cffi,指纹,https,curl,com
From: https://www.cnblogs.com/ospider/p/python-curl-cffi-tls-fingerprint.html

相关文章

  • Python 基础语法介绍(一)
    目录一、概述二、变量1)变量定义2)定义变量的规则3)变量命名规范4)变量类型转换三、注释1)单行注释2)多行注释1、单引号(''')注释2、双引号(""")注释四、运算符1)算术运算符2)关系运算......
  • Python与小熊猫Dev-C++海龟作图比较
    前言少儿编程一般都遵循如下顺序:Scratch(或者变种,例如编程猫、腾讯扣叮)-Python-C++Scratch使用国际积木化搭建思路,学习起来,学生能够很容易上手上瘾,因为它能够通过积木化编程......
  • QPython实例01-获取所有短信并生成词云
    一、QPython安装配置1.1.QPython介绍QPython是一个可以在安卓设备运行python的脚本引擎。版本有QPython3L和QPython3C,3L为官方版本,可以在应用市场搜索下载。3C版本为"......
  • 区分Python开发高级和初级工程师的五个技巧汇总
    1.引言在本文中,我们将以高级方式而不是初级方式来研究五种解决常见编码问题的方法。每一个编码问题都源于某个常见的实际问题抽象,许多问题在日常工作中反复出现多次,熟练掌......
  • python-pip
    一、pip介绍Python官网中的安装包中已经自带了pip,在安装时默认选择安装。安装完python后需要手动配置pip的环境变量,cmd命令可以查看pip是否可用:pip或者pip-h二、命令......
  • [oeasy]python0066_控制序列_光标位置设置_ESC_逃逸字符_CSI
    光标位置回忆上次内容上次讲了三引号的输出三引号中回车和引号都会被原样输出​​\​​还是需要从​​\\​​转义黑暗森林快被摸排清了还有哪个转义序列没研究过......
  • python peewee
    frompeeweeimportMySQLDatabase,ModelfrompeeweeimportCharField,IntegerField,BooleanField#引入随机数据包importrandomfromfakerimportFakerfake......
  • [oeasy]python0066_控制序列_光标位置设置_ESC_逃逸字符_CSI
    光标位置回忆上次内容上次讲了三引号的输出三引号中回车和引号都会被原样输出\还是需要从\\转义黑暗森林快被摸排清了还有哪个转义序列没研究过......
  • 【2】Python3基础之Python注释,变量
    一,注释在Python中有两种形式:单⾏注释与多行注释☆单⾏注释单⾏注释只能注释一行内容,基本语法:#注释内容☆多⾏注释多行注释可以注释多行内容,常用于代码......
  • 【3】Python基础数据类型之字符串,切片,列表,元组,字典
    1.创建字符串  2.字符串输入输出,使用input()接收用户输入,使用print()输出。  3.字符串的索引下标4.切片:指对操作的对象截取其中一部分的操作。字符串、列表、元......