Python 爬虫 TLS 指纹实战:requests 被挂住时怎么查(CH-004)
- 1Python 爬虫入门实战:服务端渲染页面怎么取数(CH-001)
- 2Python 爬虫接口实战:翻页取完再累加(CH-002)
- 3Python 爬虫实战:随机下发路径的资源怎么下载(CH-003)
- 4Python 爬虫 TLS 指纹实战:requests 被挂住时怎么查(CH-004)本文
- 5Python 爬虫接口签名实战:参数签名 + 会话绑定(CH-005)
- 6Python 爬虫加密实战:ROT + Base32 + 凯撒 + HMAC-MD5 组合(CH-006)
- 7Python 爬虫字体映射实战:HTML 里一个字都没有(CH-007)
- 8Python 爬虫字体加密实战:码位混淆 + 自定义字体文件(CH-008)
- 9Python 爬虫验证码实战:AES 加密报文 + 每页算术验证码(CH-009)
- 10第 10 关:RSA 传密钥 + AES 加密报文(顺带一层 JavaScript 混淆)
- 11第 11 关:无限 debugger + VM 混淆 + 签名请求头
- 12第 12 关:接口下发 JS + 字符串混淆 + 内置 MD5 + 反调试
- 13第 13 关:点选验证码 + 一次一密的轨迹报文 + 行为风控
- 14第 14 关:Cookie 签名 —— 七层复合加密,和「密钥写在前端」这件事
- 15第 15 关:蜜罐与爬虫封禁 —— 数据是假的,陷阱是真的
本文是 LearnSpider 第 4 关的解密教程。这一关的接口数据本身是明文 JSON,难点在你根本连不上:
requests会一直卡到超时,服务端什么都不返回。答案是 5 页共 50 条关键词的 CPC 之和(保留 2 位小数)。
现象:请求「不报错,也不返回」
先按常规思路来:F12 找到接口,复制成 cURL,粘到终端跑:
curl "https://spider.jsnote.top/api/challenges/CH-004/cpc?page=1" -H "Cookie: session=..."结果不是 403、不是 200,而是一直挂着,然后:
curl: (28) Operation timed out这才是这一关真正的坑:服务端把连接挂住了(黑洞)。它不告诉你为什么,你只能自己判断。
顺手验证一下:浏览器打开靶场页 /cpc 完全正常。也就是说「浏览器能拿到,脚本拿不到」—— 那问题一定出在浏览器和脚本的差异上,而不是参数、Cookie、路径。
差异在哪:TLS 握手里的密码套件顺序
HTTP 头可以伪造(UA、sec-ch-ua 随便抄),但 TLS 握手里的 ClientHello 抄不了:
| 客户端 | 首个密码套件 |
|---|---|
Python requests / httpx / urllib3(OpenSSL 默认) | TLS_AES_256_GCM_SHA384 |
| Chrome / Edge | TLS_AES_128_GCM_SHA256 |
curl | 视版本而定,多数也不是 Chrome 的顺序 |
服务端在 nginx 上把 $ssl_protocol / $ssl_ciphers / $ssl_curves 透传给了后端,后端口只看第一个套件:
- 含
AES256→ 判「不是浏览器」,把连接挂住; AES128/CHACha20→ 放行。
所以这不是「请求头问题」,也不是「代理问题」——HTTP 层根本没有机会参与。
两个容易踩的细节
- 端口也有讲究:走 443(HTTPS)才有 TLS 指纹可谈。明文兜底端口
:5787会直接返回「请通过 HTTPS(443 端口)访问本站」,因为那台机器上没有握手信息。 - 本地开发是例外:本机用 Vite 代理调试时,代理会预置三个
X-TLS-*头模拟生产环境,所以本地能跑通;你用脚本直连本地后端反而会被判「不是 HTTPS」。本地想调试就把黑洞时长设成 0(LEARNSPIDER_TLS_TARPIT_SECONDS=0),它会退回 403,而不是挂你 100 秒。
解法:让握手长得像 Chrome
最省事的是 curl_cffi:
pip install curl_cffi它的 impersonate="chrome" 会把 ClientHello(套件顺序、扩展、曲线)整套伪装成 Chrome,服务端看第一个套件就是 TLS_AES_128_GCM_SHA256,直接放行。
完整脚本
"""CH-004:答案 = 5 页 50 条关键词的 CPC 之和(保留 2 位小数)
为什么 requests 不行: requests 走 OpenSSL 默认套件顺序,ClientHello 首个套件是 TLS_AES_256_GCM_SHA384 (Chrome 是 TLS_AES_128_GCM_SHA256)。服务端按首个套件判定客户端类型, 不合规就把连接挂住(什么都不返回),所以你只能等到自己的 timeout。 curl_cffi 的 impersonate="chrome" 连 ClientHello 一起伪装,才能拿到数据。"""from curl_cffi import requests
BASE = "https://spider.jsnote.top"API_URL = f"{BASE}/api/challenges/CH-004/cpc"TOTAL_PAGES = 5
headers = { "Accept": "application/json, text/plain, */*", "Accept-Language": "zh-CN,zh;q=0.9", "Referer": f"{BASE}/cpc?challenge=CH-004", "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36" ),}
session = requests.Session(impersonate="chrome") # 关键就这一行session.headers.update(headers)session.cookies.set("session", "<把浏览器里的 session cookie 粘到这里>", domain="spider.jsnote.top")
def main(): total = 0.0 rows = 0 for page in range(1, TOTAL_PAGES + 1): response = session.get( API_URL, params={"page": page}, timeout=20, impersonate="chrome" ) response.raise_for_status() payload = response.json() items = payload.get("items", []) rows += len(items)
page_sum = sum(float(item["cpc"]) for item in items) total += page_sum print(f"第 {page} 页:{len(items)} 条,本页 CPC 合计 {page_sum:.2f}," f"累计 {total:.2f}")
print(f"\n共 {rows} 条,CPC 总和:{round(total, 2)}")
if __name__ == "__main__": main()参考实现见 参考代码/s4.py。
排查清单
- 请求「不报错也不返回」→ 先怀疑服务端黑洞,而不是自己网慢(把 timeout 设成 5 秒更快确认)。
- 浏览器能开、脚本不行 → 找两者差异:Cookie、请求头、TLS 指纹、HTTP 版本。
- 抄全套 HTTP 头没用 → 说明检查点在 TLS 层。
- 确认自己走的是 443 的 HTTPS,不是明文兜底端口。
- 用
curl_cffi的impersonate="chrome"(不是只改 UA)。 - 指纹合规了还是 401/403 → 这时候才轮到 Cookie 与参数。
- 接口一次只给一页,记得翻完 5 页(网页上只放出前 4 页)。
- 求和后保留 2 位小数(判题容差 0.05)。
常见问题
我改了 User-Agent 和 sec-ch-ua,为什么还是卡住?
因为那些是 HTTP 头。这一关看的是 TLS 握手里的东西,HTTP 层改什么都到不了判定逻辑。
为什么 curl 也不行?
不同版本的 curl 用的是本地 OpenSSL 的套件顺序,多数情况下和 Chrome 不一致。要么升级/换 curl,要么直接用 curl_cffi。
impersonate="chrome" 会不会影响别的请求?
只影响你显式声明的这个 Session。日常抓别的接口不必开,避免不必要的麻烦。
为什么判题的容差是 0.05?
CPC 是两位小数浮点数,累加会有误差。留一点容差比要求「完全相等」更合理。
最后提醒
TLS 指纹是真实存在的反爬手段(CDN、风控产品都在用)。它的意义是把「没有浏览器指纹的客户端」和「正常用户」分开 —— 逆向它属于学习范畴,滥用会直接影响服务方。
在自己的靶场里练,别拿去扫别人的站点。去挑战广场提交,然后进 CH-005:数据能拿到了,但每一条都要签名。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












