python爬虫项目
MTG卡片价格爬取
cninfo信息爬取
汽车之家数据爬取
5秒克隆你的声音-Real-Time-Voice-Cloning项目学习
js逆向案例-dy之X-Bogus/signature算法分析【转】
pyinstaller打包exe免杀和逆向浅析
【实用】从一个exe到pyc文件的实操-逆向
B站直播弹幕协议详解
快手API发布作品
Tiktok直播间弹幕获取
spider之字体css解密和RSA解密
requests报错问题
Requests被网站识别咋办?使用curl_cffi,支持原生模拟浏览器TLS/JA3指纹的 Python 库
【实用技巧】使用Byteasy进行手抓
本文档使用 MrDoc 发布
-
+
up
down
首页
Requests被网站识别咋办?使用curl_cffi,支持原生模拟浏览器TLS/JA3指纹的 Python 库
# curl_cffi: 支持原生模拟浏览器 TLS/JA3 指纹的 Python 库 现在有很多网站,已经能够通过JA3或者其他指纹信息,来识别你的请求是不是Requests发起的。这种情况下,你无论怎么改Headers还是代理,都没有任何意义。越来越多的网站开始使用 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 指纹的网站有: - https://tls.browserleaks.com/json - https://tls.peet.ws/ - https://kawayiyi.com/tls 不同网站的生成的指纹可能有差异,但是多次访问同一个网站生成的指纹是稳定的,而且能区分开 不同客户端。下文以第一个网站为例。 ### 浏览器的指纹:`8b0ae4830482b4bf5f96b2f0f282affa`  ### httpx 的指纹:`76f01df912881a05228c70b7f61bcbaa` ``` Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import httpx >>> r = httpx.get('https://tls.browserleaks.com/json') >>> print(r.json()) {'ja3_hash': '76f01df912881a05228c70b7f61bcbaa', 'ja3_text': '771,4866-4867-4865-49196-49200-49195-49199-52393-52392-159-158-52394-49327-49325-49326-49324-49188-49192-49187-49191-49162-49172-49161-49171-49315-49311-49314-49310-107-103-57-51-157-156-49313-49309-49312-49308-61-60-53-47-255,0-11-10-35-16-22-23-49-13-43-45-51-21,29-23-30-25-24,0-1-2', 'ja3n_hash': '80a9bea1db8ce3a18047816ba1ee07e5', 'ja3n_text': '771,4866-4867-4865-49196-49200-49195-49199-52393-52392-159-158-52394-49327-49325-49326-49324-49188-49192-49187-49191-49162-49172-49161-49171-49315-49311-49314-49310-107-103-57-51-157-156-49313-49309-49312-49308-61-60-53-47-255,0-10-11-13-16-21-22-23-35-43-45-49-51,29-23-30-25-24,0-1-2', 'akamai_hash': '', 'akamai_text': ''} ``` ### curl的指纹:`bd0bf25947d4a37404f0424edf4db9ad` ``` > curl --version curl 8.0.1 (Windows) libcurl/8.0.1 Schannel WinIDN Release-Date: 2023-03-20 Protocols: dict file ftp ftps http https imap imaps pop3 pop3s smtp smtps telnet tftp Features: AsynchDNS HSTS HTTPS-proxy IDN IPv6 Kerberos Largefile NTLM SPNEGO SSL SSPI threadsafe Unicode UnixSockets > curl https://tls.browserleaks.com/json { "ja3_hash": "bd0bf25947d4a37404f0424edf4db9ad", "ja3_text": "771,49196-49195-49200-49199-159-158-49188-49187-49192-49191-49162-49161-49172-49171-157-156-61-60-53-47-10,0-5-10-11-13-35-16-23-65281,29-23-24,0", "ja3n_hash": "6dc58b02765223a5015e36b7f7ad469c", "ja3n_text": "771,49196-49195-49200-49199-159-158-49188-49187-49192-49191-49162-49161-49172-49171-157-156-61-60-53-47-10,0-5-10-11-13-16-23-35-65281,29-23-24,0", "akamai_hash": "", "akamai_text": "" } ``` 可以看到,每个客户端的指纹都是不一致的,服务端也就可以据此防御异常流量。显然,防御等级分 两个层次。 ## 非法指纹黑名单 这个思路很直接,把常用的爬虫工具的指纹收集起来,然后全都屏蔽了就好了。比如说: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 指纹已经变了。 ## 合法指纹白名单 既然指纹可以随便改,那就直接只认常用浏览器的指纹好了。这时候如果爬虫或者其他脚本再想要 突破防御,**需要把每一个值都改成和浏览器都完全相同,难度还是挺大的。**尤其是考虑到大多数 语言的标准库都是直接使用系统的 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` ## curl_cffi使用 **不仅能过JA3,还能过Akamai** 之前无论使用requests还是httpx,无论是否使用代理,这里的ja3_hash并不会发生变化。并且akamai_hash和akamai_text都是空。但是现在使用`curl_cffi`的效果,下面自己看! ``` from curl_cffi import requests # 注意这个 impersonate 参数,指定了模拟哪个浏览器 r = requests.get("https://tls.browserleaks.com/json", impersonate="chrome101") print(r.json()) {'ja3_hash': 'cd08e31494f9531f560d64c695473da9', 'ja3_text': '771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513-21,29-23-24,0', 'ja3n_hash': 'aa56c057ad164ec4fdcb7a5a283be9fc', 'ja3n_text': '771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0', 'akamai_hash': '4f04edce68a7ecbe689edce7bf5f23f3', 'akamai_text': '1:65536;3:1000;4:6291456;6:262144|15663105|0|m,a,s,p'} ``` 谷歌浏览器打开:https://tls.browserleaks.com/json  我们可以看到,输出的 JA3 指纹和浏览器中还是不一样,本来指纹应该是一模一样的,这是由于**浏览器版本不同**,当前已经是chrome118了~ 代理也支持: ``` >>> 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()) ``` curl_cffi的[仓库地址](https://github.com/yifeikong/curl_cffi) ## curl_cffi已经支持异步 curl_cffi不仅完全兼容Requests的语法,而且还支持Asyncio。要使用异步写法时,代码如下: ``` import asyncio from curl_cffi.requests import AsyncSession urls = [ "https://baidu.com/", "https://cn.bing.com/", "https://www.ai20tech.com/", ] async def main(): async with AsyncSession() as s: tasks = [] for url in urls: task = s.get("https://example.com") tasks.append(task) results = await asyncio.gather(*tasks) asyncio.run(main()) ``` ## 其他指纹技术概览 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 时,互联网公司依然可以通过各种指纹来定位追踪用户,乃至于区别对待。平等、 匿名、自由地使用个人数据和公开数据应该是一项基本人权。在立法赶不上技术更新的时代,我们应该用技术手段捍卫自己的权利。
laihui126
2023年10月17日 15:10
分享文档
收藏文档
上一篇
下一篇
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
关于 MrDoc
觅道文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅道文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅道文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
下载Markdown文件
分享
链接
类型
密码
更新密码