【疑惑】:使用python的requests库发起get或post请求返回403代码错误,使用postman发起请求发现状态码<200>竟然成功了。这是什么原因?首先排除ip问题,ip有问题的话postman也访问不了。难道是headers出现了问题吗,通过对比发现也不是headers的问题。
【解疑】:其实遇到这种情况大概率是遇到了“原生模拟浏览器 TLS/JA3 指纹的验证”,浏览器和postman都有自带指纹验证,而唯独requests库没有。这就让反爬有了区分人为和爬虫的突破口。
import requests
headers = {
"authority": "api.investing.com",
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"domain-id": "cn",
"origin": "https://cn.investing.com",
"referer": "https://cn.investing.com/",
"sec-ch-ua": "\"Chromium\";v=\"112\", \"Google Chrome\";v=\"112\", \"Not:A-Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
}
url = "https://脱敏处理/historical-data"
response = requests.get(url, headers=headers)
print(response.text)
print(response)
这样一个代码,看不出来有任何加密的地方,但是请求出来的源码却是403
但是当我们使用curl_cffi访问的话,就可以轻松解决这个问题
from curl_cffi import requests
headers = {
"authority": "api.investing.com",
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"domain-id": "cn",
"origin": "https://cn.investing.com",
"referer": "https://cn.investing.com/",
"sec-ch-ua": "\"Chromium\";v=\"112\", \"Google Chrome\";v=\"112\", \"Not:A-Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
}
url = "https://cn.investing.com/indices/shanghai-composite-historical-data"
# 注意这个 impersonate 参数,指定了模拟哪个浏览器
response = requests.get(url, headers=headers, impersonate="chrome101")
print(response.text)
首先来认识一下什么是 TLS 指纹
现在绝大多数的网站都已经使用了 HTTPS,要建立 HTTPS 链接,服务器和客户端之间首先要进行 TLS 握手,在握手过程中交换双方支持的 TLS 版本,加密算法等信息。不同的客户端之间的差异 很大,而且一般这些信息还都是稳定的,所以服务端就可以根据 TLS 的握手信息来作为特征,识别 一个请求是普通的用户浏览器访问,还是来自 Python 脚本等的自动化访问。
JA3 是生成 TLS 指纹的一个常用算法。它的工作原理也很简单,大概就是把以上特征拼接并求 md5。
有证据表明,阿里云、华为云、Akamai 和 Cloudflare 都在使用 TLS 指纹技术来识别机器访问流量。 Akamai 更是直接在宣传稿中说明了在通过 TLS 指纹技术检测非法请求。
我常用的查看 tls 指纹的网站: https://tls.browserleaks.com/json
我的浏览器指纹:f9cb2f760922c313abe08b84ccabe834
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。就像上文我的那个例子一样
为了完美模拟浏览器,国外有大佬给 curl 打了一些 patch,把相应组件全部都替换成了浏览器使用 库,连版本都保持一致,这样就得到了和浏览器完全一样的指纹,curl-impersonate 的作者提出使用 环境变量 + 替换 libcurl 来在不同语言中使用 curl-impersonate,但是似乎 pycurl 没法工作。 于是乎,有国内大佬写了一个 curl(-impersonate) 的 Python binding.
相比 pycurl,有以下优点:
1、原生支持 curl-impersonate 2、pip install 直接是二进制包,无需编译,也就不会有编译错误 3、提供了一个简单的 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())
HTTP Header 指纹。通过浏览器发送的 header 的顺序和值的组合来判断是合法用户还是爬虫
DNS 指纹。参考:http://dnscookie.com(opens new window)
浏览器指纹。通过 canvas,webgl 等计算得到一个唯一指纹,Cookie 禁用时监视用户的主流技术
TCP 指纹。也是根据 TCP 的一些窗口、拥塞控制等参数嗅探、猜测用户的系统版本
指纹技术就是通过不同的设备和客户端在参数上的微妙差异来识别用户。本来按照规范, 这些值都是应该任意选取的,但是,现实世界中,服务端反而对不同值采取了区别对待。指纹技术 可以说应用到了 OSI 网络模型中所有可能的层,基于 HTTP header 顺序的指纹工作在第七层应用层, SSL/TLS 指纹工作在传输层和应用层之间,TCP 指纹在第四层传输层。而在 TCP 之下的 IP 层和物理 层,因为建立的不是端到端的链路,所以只能收集上一跳的指纹,没有任何意义。
对于爬虫来说,User-Agent 相当于自报门户。除了初学者以外,没有人会顶着 Python/3.9 requests 这样的 UA 去爬的,而指纹则是很难更改的内部特征。通过指纹技术可以防御一大批爬虫,而使用 能够模拟指纹的 http client 则轻松突破这道防线。
对于普通用户来说,各种指纹造成了极大的隐私泄露风险。即使按照 GDPR 等监管政策的要求,用户拒绝使用 Cookie 时,互联网公司依然可以通过各种指纹来定位追踪用户,乃至于区别对待。