基于实战经验重构 JSVMP 分析框架: - SKILL.md: 新增路径选择决策树(路径 A 算法追踪 vs 路径 B 环境伪装) - SKILL.md: 路径 B「环境伪装六步法」完整方法论(采集→对比→分级→补丁→验证→端到端) - SKILL.md: 新增场景 10「JSVMP + jsdom 环境伪装」、解法模式 E - SKILL.md: 标注三板斧适用边界与局限,经验法则扩充至 22 条 - references/jsdom-env-patches.md: 新增 jsdom 环境补丁知识库(Function.toString 三层防御 / plugins 完整模拟 / DOM 布局属性 / 30+ API 存根) - cases/: 新增经验库系统(模板 + 首个 JSVMP 实战案例含 58 项环境检测差异全表) - references/jsvmp-analysis.md: JSVMP 专项分析详细指南 - references/troubleshooting.md: 常见问题排查指南 - templates/python-request/: Python 项目模板 - 各 references 文档增强、Python 技术栈支持、MCP v1.1 适配(44→52 工具) Made-with: Cursor
8.7 KiB
8.7 KiB
错误排查指南
请求失败排查流程
排查顺序(必须按序执行)
当协议脚本请求返回非预期结果时,禁止盲目大改,按以下 6 步逐项排查:
步骤 ① Cookie/Session 失效
│
├─ 排查方法:
│ · 浏览器 F12 → Application → Cookies 获取最新 Cookie
│ · 对比脚本使用的 Cookie 与浏览器当前 Cookie
│ · 检查是否有 HttpOnly Cookie 漏掉(Network 面板看完整 Cookie)
│
├─ MCP 辅助:
│ · get_cookies → 获取 Camoufox 中的 Cookie
│ · evaluate_js(expression="document.cookie") → 获取 JS 可见 Cookie
│
└─ 常见原因:Cookie 过期、登录态失效、缺少 HttpOnly Cookie
步骤 ② 前置请求遗漏
│
├─ 排查方法:
│ · Network 面板查看主请求之前的所有请求
│ · 重点关注:/api/init、/token、/config 等路径
│ · 检查前置请求的响应中是否有 Token、Session ID
│
├─ MCP 辅助:
│ · start_network_capture → 触发操作 → list_network_requests
│ · 按时间排序,找到主请求之前的接口
│
└─ 常见原因:漏掉预热接口、Token 获取接口、验证码初始化接口
步骤 ③ 时间戳绑定
│
├─ 排查方法:
│ · 对比浏览器请求的时间戳与脚本生成的时间戳
│ · 检查精度:秒(10位)vs 毫秒(13位)
│ · 检查是否有时间窗口限制(如签名有效期 30 秒)
│
├─ 常见坑:
│ · Python time.time() 返回浮点数,需要 int()
│ · Node.js Date.now() 返回毫秒
│ · 服务端可能校验请求时间与服务器时间的差值
│
└─ 修复方法:确保签名计算和请求发送使用同一个时间戳值
步骤 ④ Header 缺失或错误
│
├─ 排查方法:
│ · 逐项对比浏览器 Request Headers 与脚本 Headers
│ · 重点检查:Content-Type、Referer、Origin、Accept
│ · 检查自定义 Header(X-开头的、大小写敏感的)
│
├─ 常见坑:
│ · Header 顺序可能影响(极少数站点)
│ · sec-ch-ua 等 Client Hints Header
│ · Accept-Encoding 不支持 br 但发了 br
│
└─ 修复方法:完整复制浏览器 Headers,逐个删减确认必要项
步骤 ⑤ 环境校验
│
├─ 排查方法:
│ · Hook navigator/screen/canvas 等环境 API
│ · 检查签名计算是否包含环境指纹
│ · 对比有无环境值时的请求结果
│
├─ MCP 辅助:
│ · get_fingerprint_info → 查看 Camoufox 指纹
│ · search_code(keyword="navigator|screen|canvas") → 搜索环境检测代码
│
└─ 关键原则:先验证是否真正参与服务端校验,再决定是否补全
步骤 ⑥ 频率限制
│
├─ 排查方法:
│ · 降低请求频率重试(间隔 3-5 秒)
│ · 检查响应中的限流相关字段(rate limit、retry-after)
│ · 检查是否返回了 429 状态码
│
└─ 修复方法:增加请求间隔、使用代理 IP、添加随机延迟
签名值不一致排查
逐步对比法
当计算出的签名值与浏览器实际值不匹配时:
对比链路(脚本值 vs 浏览器值):
环节 1:原始输入参数
├─ 参数名是否完全一致(大小写、下划线)
├─ 参数值是否完全一致
└─ 是否有隐藏参数(空值但参与签名的参数)
环节 2:参数排序与拼接
├─ 参数排序规则(字典序、自定义顺序、原始顺序)
├─ 拼接分隔符(& / 空 / 其他)
├─ 是否包含 key=value 中的 key 名称
├─ 空值参数是否参与拼接
└─ URL 编码是否在拼接前/后执行
环节 3:时间戳
├─ 精度:秒 (10位) vs 毫秒 (13位)
├─ 类型:字符串 vs 数字
└─ 时区:UTC vs 本地时间
环节 4:随机串
├─ 长度是否匹配
├─ 字符集:hex / alphanumeric / 自定义
└─ 生成方法:Math.random vs crypto.randomBytes
环节 5:密钥/盐值
├─ 密钥值是否正确(注意空格、换行、编码)
├─ 密钥是否是硬编码还是动态获取
└─ 是否有 IV / Salt(AES 加密场景)
环节 6:中间摘要
├─ 多次哈希时逐层对比中间值
└─ 编码格式:hex(小写) vs hex(大写) vs base64
环节 7:最终输出
├─ 编码方式:hex / base64 / 自定义
├─ 大小写:hex 小写 vs 大写
└─ 是否有额外处理(截断、拼接前缀等)
快速定位技巧
技巧 1:二分法
如果有中间值可对比,先对比中间值,
确定偏差发生在前半段还是后半段
技巧 2:固定输入法
在浏览器和脚本中使用完全相同的:
- 时间戳(硬编码一个固定值)
- 随机串(硬编码一个固定值)
- 页码(相同页码)
然后对比签名结果,排除动态因素干扰
技巧 3:打印拼接字符串
在签名函数的哈希/加密调用之前,
打印完整的输入字符串,逐字符对比
技巧 4:MCP 辅助对比
- set_breakpoint_via_hook(target_function="签名函数")
- get_breakpoint_data → 获取浏览器端的真实入参和返回值
- 与脚本端逐项对比
常见错误与解决方案
HTTP 状态码
| 状态码 | 常见原因 | 排查方向 |
|---|---|---|
| 403 Forbidden | Cookie 失效 / Header 缺失 / IP 被封 / TLS 指纹 | 步骤 ①④⑤⑥ |
| 412 Precondition Failed | 签名校验失败 / 缺少前置请求 | 签名对比 + 步骤 ② |
| 429 Too Many Requests | 频率限制 | 步骤 ⑥ |
| 500 Internal Server Error | 参数格式错误 / 数据类型不匹配 | 检查请求 Body 格式 |
| 200 但数据为空 | 签名正确但参数错误 / 页码越界 / 缺权限 | 检查业务参数 |
加密相关
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| MD5 结果不一致 | 编码问题(UTF-8 vs ASCII) | 确认输入字符串的编码方式 |
| AES 解密失败 | 模式错误(CBC vs ECB)/ Padding 错误 / IV 错误 | 逐一确认模式、Padding、IV |
| Base64 结果多余字符 | URL-safe Base64 vs 标准 Base64 | 检查是否需要 +/ → -_ 替换 |
| HMAC 结果不一致 | 密钥编码问题 / 算法类型错误 | 确认密钥是字符串还是 hex bytes |
| RSA 加密失败 | 公钥格式错误 / PKCS1 vs OAEP | 检查公钥格式和填充方案 |
环境相关
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| Node.js vm 沙箱报错 | 缺少 DOM API(document/window) | 参考 environment-patch.md 补环境 |
| Python execjs 报错 | Node.js 未安装 / JS 代码有语法错误 | 检查 Node.js 环境、验证 JS 代码 |
| WASM 加载失败 | 缺少 imports / 内存不足 | 检查 WASM imports 并补全 |
| Cookie 设置不生效 | domain 不匹配 / path 不匹配 | 确认 Cookie 的 domain 和 path |
Python 特有问题
| 问题 | 原因 | 解决方法 |
|---|---|---|
requests 请求被拒 |
TLS 指纹被识别 | 换用 curl_cffi(支持浏览器指纹模拟) |
hashlib.md5() 报错 |
Python 3.9+ FIPS 模式限制 | 使用 hashlib.md5(usedforsecurity=False) |
execjs 执行慢 |
每次创建新的 JS 运行时 | 编译后复用 context:ctx = execjs.compile(js_code) |
pycryptodome 与 pycrypto 冲突 |
同时安装了两个库 | pip uninstall pycrypto && pip install pycryptodome |
| 中文编码问题 | 签名包含中文字符 | 使用 urllib.parse.quote() 或确认 UTF-8 编码 |
httpx HTTP/2 连接失败 |
缺少 h2 依赖 | pip install httpx[http2] |
Node.js 特有问题
| 问题 | 原因 | 解决方法 |
|---|---|---|
crypto 模块不可用 |
使用了浏览器打包版本 | 确认运行环境是 Node.js 而非浏览器 |
vm 沙箱超时 |
JS 代码有死循环 | 设置 timeout 选项 |
axios 被 WAF 拦截 |
默认 User-Agent | 自定义完整浏览器 UA |
| HTTP/2 请求失败 | 证书验证失败 | rejectUnauthorized: false(调试用) |
排查工具速查
MCP 工具
| 排查场景 | MCP 工具 | 用法 |
|---|---|---|
| 对比请求差异 | start_network_capture + get_network_request |
在 Camoufox 中捕获真实请求,与脚本请求对比 |
| 获取真实签名值 | set_breakpoint_via_hook + get_breakpoint_data |
在签名函数设伪断点,捕获真实入参和返回值 |
| 验证环境检测 | get_fingerprint_info + check_detection |
确认哪些环境项参与校验 |
| 追踪调用链 | get_request_initiator |
从请求直接定位到签名函数 |
| 实时对比 | evaluate_js |
在浏览器中执行还原后的签名函数,与脚本输出对比 |