"股小仙·数据篇"系列第一篇(系列主线:把同花顺账本——证券交易流水——导出做成全自动管线,供持仓分析使用)。这篇复盘 2026 年 6 月那次反爬升级的完整对抗过程:旧方案怎么工作、升级后怎么死、三次自救为什么全败、最后为什么"承认失败"反而是最优解。所有接口路径、字段做了脱敏,方法论通用。
一、前传:Cookie 保鲜时代
升级前的方案朴素而高效——requests + 手动保鲜的 Cookie:
HEADERS={"Cookie":open("COOKIE.txt").read().strip(),# 从浏览器复制整套 Cookie"User-Agent":"Mozilla/5.0 ... Chrome/146","Referer":"https://(平台)/pc/index.html",...}resp=requests.post(API,data={"terminal":"1","userid":...,"fund_key":...})配套一个前置检查脚本check_cookie.py:先用 Cookie 调一个轻量接口,输出三种状态码——Cookie 有效(N 只持仓)/COOKIE_EXPIRED/HTTP_ERROR。工作流是:每 7 天左右 Cookie 过期,检查脚本报警,人工去浏览器登录一次、开发者工具复制 Cookie 存文件,管线复活。
这套流程跑了一年半。今天回头看,它的脆弱性写在设计里(登录态的"保鲜"依赖人工周期),但在反爬不严的年代,它是成本最低的解。
二、断崖:升级当天的现象
6 月某天,所有请求开始返回 401。不是偶发——重试、换账号、重新登录复制全套 Cookie,全部 401。
第一个反直觉的细节:从浏览器里复制"正在工作"的 Cookie,直接用 requests 发,也被拒。也就是说,拒绝的依据不再是"Cookie 对不对",而是别的什么东西。抓包对比浏览器和 requests 的请求,差异锁定在 Cookie 里一个此前没人注意的字段:v——一个几百字符的编码串,值每隔几分钟就变。
三、三次自救与三次失败
自救一:更勤快地保鲜(失败,成本判断)
既然v在变,脚本里实时从浏览器读一次 Cookie 再发呢?做了,失败。v的有效期极短(分钟级),从读取到 requests 发出之间存在窗口,且同样的v在浏览器请求里有效、在 requests 请求里无效——证明校验的不是v的字符串本身,而是v背后绑定的东西。
自救二:本地跑签名脚本生成v(失败,指纹绑定)
逆向发现v由页面里的chameleon.min.js生成(混淆过的采集脚本)。用 jsdom/Node 补环境跑它,能生成格式合法的v——依然 401。
死因:chameleon 生成v时采集了真实浏览器环境指纹(canvas、字体、时序特征),并把指纹摘要编码进v。服务端解码v,拿摘要与请求的实际环境比对——jsdom 生成的v里编着 jsdom 的"假指纹",请求本身也确实来自 jsdom,这对自洽的假指纹理论上应该过,但服务端还交叉校验了 TLS/HTTP2 层特征(requests/jsdom 的网络栈与 Chrome 差异显著)。三层咬合,破一层没用。
自救三:curl_cffi 模拟 Chrome 的 TLS 指纹(失败,登录态血缘)
用 curl_cffi 把 TLS 指纹伪装成 Chrome——这次走得更远,v校验似乎过了(错误码变了),卡在登录态:sess_tk等会话 Cookie由服务端在真实登录流程中签发并绑定上下文,手工注入的会话被风控异步标记失效。也就是说,即使网络层全伪装成功,"这个会话不是真人登出来的"依然可判。
四、复盘:三次失败其实是一次失败
把三次失败排开看,它们撞的是同一堵墙的三个面:
| 自救 | 攻击的面 | 防守方的对策 |
|---|---|---|
保鲜v | 签名时效 | v与请求环境绑定,换环境即失效 |
本地生成v | 签名算法 | v内嵌指纹 + TLS 交叉校验 |
| 伪装 TLS | 网络指纹 | 登录态与真实登录流程血缘绑定 |
这套反爬的设计哲学非常清晰:不追求识破每一种伪装,而是让"伪造完整真实环境"的成本高于"直接使用真实环境"。当你发现自己在同时补指纹、补 TLS、补会话血缘——补的每一层都在提醒你:为什么不直接用真浏览器?
五、终局:承认失败,换赛道
最终方案是 DrissionPage 接管真实 Chrome:登录态落在持久化 profile(人工登录一次,之后免登录),页面自然加载让 chameleon 生成合法签名,请求在页面环境内用 XHR 发出——三层校验全部由"真实"自然满足。(实现细节是系列第二篇的主题,本篇不展开。)
复盘这次升级最大的收获不是技术,是止损的判断框架:
- 反爬对抗是不对称战争——防守方改一行代码,进攻方可能要逆向一周;
- 判断该不该继续攻,看一个信号:你是否在"补环境"(补指纹、补 TLS、补时序)。一旦进入补环境模式,每多补一层,说明你离"真实"更远而不是更近;
- 个人数据自动化场景里,最终解几乎总是"真浏览器 + 自动化操作",而不是"协议伪装"。承认这一点的那一刻,方案就出来了。
六、遗留的工程债与它的解法
新方案也有代价:依赖桌面 Chrome、浏览器要常驻、同步速度慢一个量级。这些债的偿还方式写在了管线设计里——同步频率降到每天一次(对账本场景足够)、浏览器常驻复用(B2 篇)、登录态过期时脚本等待人工介入而不是静默失败。换赛道不是回到原始,是把人工介入压缩到"每年一两次"。
系列导航
- 篇一(本篇):反爬升级对抗复盘
- 篇二:给老脚本换心脏——页内同步 XHR 替换 requests
- 篇三:导出之后——5 张表的 schema 与幂等入库(排期中)