1. 项目概述:当Python爬虫遭遇“Just a moment...”
如果你写过爬虫,尤其是针对一些大型网站或安全防护比较严格的站点,大概率见过这个令人头疼的页面:一个简洁的“Just a moment...”提示,后面可能跟着“Checking your browser before accessing...”或者“Please turn JavaScript on and reload the page”。这通常意味着你的请求被Cloudflare、Akamai或其他WAF(Web应用防火墙)的“浏览器验证”或“质询”页面给拦截了。对于爬虫开发者来说,这堵墙就像一扇紧闭的大门,告诉你“非人类勿入”。传统的解决方案,比如加个User-Agent、设置请求头、使用代理IP,甚至上Selenium模拟浏览器,有时候都无济于事。问题的核心,往往不在于你的请求头伪装得不够像,而在于一个更深层、更隐蔽的技术点—— TLS指纹 。
简单来说,当你通过Python的 requests 、 httpx 或 aiohttp 库发起一个HTTPS请求时,客户端(你的爬虫程序)和服务器(目标网站)在建立加密连接前,会进行一次“TLS握手”。在这个过程中,客户端会告诉服务器:“嗨,我支持这些加密套件、这些TLS版本、这些扩展功能。”这个“自我介绍”的格式和内容,就是TLS指纹。像 requests 这样的标准库,其底层(如 urllib3 )使用的TLS指纹特征非常明显,安全系统可以轻易识别出这不是一个常见的浏览器(如Chrome、Firefox),而是一个脚本或程序,从而触发防护机制,给你展示那个“Just a moment...”的页面。
因此,这个项目的目标非常明确: 教会你如何用Python,通过修改或模拟一个“浏览器级别”的TLS指纹,来绕过这类基于客户端指纹识别的防护,让你的爬虫请求看起来更像一个真实的浏览器,从而成功获取数据。 这不仅适用于绕过Cloudflare的5秒盾,对于其他依赖TLS/JA3指纹进行风控的API接口、移动端应用后台等场景,同样具有参考价值。无论你是数据采集工程师、安全研究员,还是对网络协议底层感兴趣开发者,掌握这套方法都能让你在应对复杂反爬策略时多一件利器。
2. 核心原理:TLS指纹是什么,以及它如何“出卖”了你的爬虫
要解决问题,必须先理解问题。我们得深入看看TLS握手过程中,到底是什么信息构成了这个独一无二的“指纹”。
2.1 TLS握手与ClientHello报文
当你的Python脚本使用 requests.get(‘https://example.com‘) 时,底层大致会发生以下步骤:
- TCP三次握手 :建立基础的网络连接。
- TLS握手 :在TCP连接之上,建立加密的HTTPS连接。握手的第一步,就是客户端发送一个
ClientHello消息给服务器。
这个 ClientHello 消息包含了大量信息,正是这些信息的组合,形成了TLS指纹。主要构成部分包括:
- TLS版本 :例如
TLSv1.2或TLSv1.3。 - 加密套件(Cipher Suites) :一个客户端所支持的所有加密算法组合的列表,按优先级排列。例如
TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256等等。 这个列表的顺序和内容,是指纹中最具辨识度的部分之一。 不同浏览器、不同版本、不同操作系统,其支持的加密套件列表和顺序都有差异。 - 扩展(Extensions) :TLS协议的扩展功能,例如:
-
server_name(SNI):指示你要访问的域名。 -
extended_master_secret:增强密钥安全性。 -
renegotiation_info:重协商信息。 -
supported_groups(椭圆曲线):支持的椭圆曲线列表,如x25519,secp256r1。 -
ec_point_formats:椭圆曲线点格式。 -
session_ticket:会话票据。 -
application_layer_protocol_negotiation(ALPN):应用层协议协商,如h2(HTTP/2),http/1.1。 -
signature_algorithms:支持的签名算法。 -
key_share(TLS 1.3):密钥分享。 -
supported_versions(TLS 1.3) :支持的TLS版本列表。 -
padding:填充数据。
-
- 压缩方法 :通常为空或特定值。
- 随机数(Random) :一个28字节的随机值,每次连接都不同,不影响指纹本身。
2.2 JA3指纹:TLS指纹的“哈希化”标准
直接比对完整的 ClientHello 报文太麻烦了。因此,安全社区衍生出了一个名为 JA3 的指纹方法。JA3将 ClientHello 中的特定字段拼接成一个字符串,然后计算其MD5哈希值,得到一个32位的十六进制字符串,这就是JA3指纹。
JA3字符串的生成规则大致是(以JA3为例,JA3S是针对服务器响应的): TLS版本, 加密套件, 扩展列表, 椭圆曲线, 椭圆曲线点格式
例如,一个常见的Chrome浏览器的JA3字符串可能类似: 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
将这个字符串进行MD5哈希,就得到了类似 a0a1b2c3d4e5f67890a1b2c3d4e5f678 的JA3指纹。安全系统维护了一个庞大的指纹库,将已知的浏览器/客户端指纹(如Chrome 120 on Windows, Firefox 115 on macOS)标记为“可信”,而将Python requests 、 curl 、 nmap 等工具的指纹标记为“可疑”或“已知爬虫”,从而进行拦截。
实操心得 :你可以通过在线工具(如
https://tls.browserleaks.com)访问,查看自己真实浏览器的完整TLS指纹和JA3值。同时,用一段简单的Pythonrequests代码访问同一个工具,对比两者的输出,你会立刻明白差异所在。这是理解问题最直观的方式。
2.3 Python标准库的“默认指纹”为什么会被识别
以最常用的 requests 库(底层是 urllib3 )为例,它在编译时链接了系统或自带的OpenSSL库。其 ClientHello 中:
- 加密套件列表 :是OpenSSL库默认提供的一套,顺序固定,且与任何现代浏览器的列表都不同。
- 扩展列表 :可能缺少某些浏览器常见的扩展,或者扩展的顺序不同。
- TLS版本 :可能只声明支持较老的版本,或者支持列表的顺序不对。
这种独特的、非浏览器的特征组合,就是一张“爬虫身份证”。WAF只需要检查JA3指纹是否在其“白名单”(常见浏览器指纹库)内,不在的话,直接返回质询页面,简单粗暴又有效。
3. 解决方案选型与工具链搭建
知道了原理,我们就可以针对性地解决问题。目标是将我们Python爬虫的TLS指纹,修改成与目标浏览器(例如Chrome)一致。这里有几种主流方案,各有优劣。


3730

被折叠的 条评论
为什么被折叠?



