资讯动态

从排行榜接口入门JS逆向:还原wbi签名与w_rid参数

发布时间:2026/10/27 4:43:15 来源:尧图企业网站定制
1. 先想明白排行榜接口为什么值得拿来练手排行榜这个页面看起来最无害——数据公开、结构简单、不需要登录也能看。但恰恰是这种看起来很容易的地方最适合拿来练JS 逆向的第一课。我见过不少人一上来就啃登录加密、啃验证码折腾两周连第一个断点都没打对最后直接放弃。排行榜不一样它把逆向工程里最核心的几件事压缩成了一个可控的练习场接口定位、加密参数识别、签名函数还原、请求复现跑通一遍后面再看任何带w_rid、_sign、nonce的接口思路都是同一套。我这次的分析对象是某弹幕视频站点下文统一叫该站点的排行榜数据。你在浏览器里打开排行榜页滚动两下数据就出来了看起来是前端渲染的实际上它走的是独立的数据接口返回的是结构化 JSON比解析 DOM 舒服太多。麻烦在于这个接口不是裸调的参数里多了一个w_rid和一个wts直接复制 URL 到 Python 里跑十有八九给你返回一个错误码。所以这篇东西解决的就是一个问题怎么把前端算出来的那个签名参数在服务端语言里原样算出来。学完你能拿到什么一套可复用的定位流程、一套能直接抄的签名实现、一份我踩过的坑清单。适合谁看会点 Python、能看懂基本 JS 语法、但一遇到混淆代码就头皮发麻的同学。不需要你会 AST 还原也不需要你会补环境这一篇的门槛我尽量压到能看懂中文。说明下面的接口路径和字段名我做了简化处理方法逻辑完全一致。所有内容定位为前端加密原理的学习与研究实际使用请遵守站点公开协议控制请求频率不要用于商业分发。1.1 三条路摆在面前为什么选接口这条路拿排行榜数据明面上有三条路我把对比列出来你一看就明白为什么我不推荐另外两条。方案实现难度稳定性数据完整度主要问题解析页面 DOM低极差只有当前屏懒加载、虚拟滚动、改版即挂移动端私有接口中中全量额外签名体系参数更多网页端数据接口中好全量分组需要还原签名参数DOM 解析最大的问题是它跟页面长什么样绑定。排行榜这种页面通常有虚拟滚动你滚动到第三屏第一屏的节点就被回收了用 Selenium 一路滚下去能抓但慢得离谱而且一次改版你就得重写选择器。移动端接口倒是干净但它往往有另一套独立的签名等于换个战场重新开始。网页端接口是最划算的一次签名逻辑写对后面所有接口只要用同一套wbi体系直接复用。这也是我建议新手从这类接口入门的原因——学一次用一片。1.2 逆向之前先做功课把请求链路拆成四层很多人卡住不是因为技术不行是因为没搞清楚数据是怎么一步步来的。我习惯在动手之前先把链路画在纸上不是画流程图就是随手写几行该站点的排行榜数据流大致是四层HTML 骨架首屏返回一个几乎空的容器里面挂着几段 JS 文件引用。JS 资源其中有若干个 chunk 文件签名函数就藏在里面通常被打包工具拆成了几十上百个模块。业务接口/x/web-interface/ranking/list这类路径返回 JSON。签名参数wts时间戳和w_rid签名摘要由前端在发请求前现算。把这四层认清楚你就知道自己的目标不是破解整个网站而是找到第 4 层那个算w_rid的函数。范围一收窄心理压力立刻小一半。我第一次做的时候就是没拆层一头扎进一个两万行的混淆文件里看了三个小时才发现签名函数根本不在那个文件里。1.3 三个信号快速判断这个接口难不难不是所有接口都值得花时间。我一般用三个信号做初筛五分钟内能定生死。第一个信号看参数里有没有陌生字段。把请求参数摊开如果你认识的只有page、size、order剩下全是w_rid、wts、web_location、dm_img_str这类说明至少有一层签名而且很可能是多层。字段越莫名其妙逆向成本越高。第二个信号看 JS 有没有被重度混淆。打开源码如果变量名全是_0x4a2b字符串全塞进一个大数组控制流被拆成switch-case加死循环那就是标准的混淆产物。这种代码直接读是读不动的但你可以不读——直接搜关键词往往比读代码快十倍。第三个信号看风控响应的形态。有些接口参数错了会返回明确的错误码告诉你缺了什么这是最友好的有些直接返回空列表或者给你一个 412这种就要多用二分法去试。排行榜接口属于前者错误码还算清晰所以适合练手。三个信号都过关我就开始干活。都不过关我一般会换个数据源或者干脆去抓公开的静态榜单没必要跟自己较劲。2. 定位签名函数从网络面板一路追到源码行号定位是整个逆向里最耗时间、也最讲究方法的环节。技术含量其实不高但顺序错了就是无限循环。我的固定套路是先断点再回溯最后才搜关键词。很多人顺序反过来一上来全局搜索搜出八百个结果直接懵。2.1 XHR 断点 调用栈回溯这是最快的一条路打开开发者工具切到网络面板先清空记录然后刷新排行榜页。找到那个返回 JSON 的请求右键选择复制为 cURL先存着后面复现的时候用得上。接着切到源码面板在右侧找到XHR/提取断点添加一条断点URL 里填接口路径的关键片段比如ranking。刷新页面请求发出前会被拦下来此时调用栈Call Stack里就是完整的调用链。这里有个经验从栈底往上第二个或第三个函数看起栈顶通常是打包工具注入的包装函数没什么信息量。点开中间那几帧看参数里有没有你熟悉的东西比如wts、w_rid或者一个明显的对象字面量。找到了就在那一帧上打个普通断点重新走一遍单步进去。我最喜欢用的一招是断下来之后在控制台里直接对可疑变量求值。比如怀疑某个变量是签名结果就敲JSON.stringify(那个变量)如果打印出来是一串 32 位十六进制基本就锁定了。这一步能省掉大量阅读代码的时间。2.2 全局搜索关键词姿势比工具重要断点走不通的时候比如签名发生在 Web Worker 里或者被包进了一个异步回调就得靠搜索。关键词的选择直接决定效率我整理了一张自己常用的词表关键词命中概率说明w_rid极高签名参数名通常直接出现在拼接处wts极高时间戳一般和 w_rid 相邻md5/MD5高摘要算法调用mixinKey高密钥拼接变量名substr/slice中密钥截取常见操作sort()中参数排序签名前必做encodeURIComponent中参数规范化navigator.userAgent低常见于设备指纹不是签名搜索的时候注意两个细节。第一开启忽略大小写和正则混淆代码里的变量名大小写经常不统一。第二搜索范围限定在 Sources 里的 JS 文件不要搜 All否则 HTML 和 CSS 里的噪声会把你淹掉。搜到结果之后不要急着读先看上下文五十行。如果看到类似md5(...)加上字符串拼接基本就是它了。我踩过的坑是有些站会故意放几个假签名函数名字起得很像但根本没被调用纯属迷惑。验证方法很简单在可疑位置打个断点刷新页面断点没触发就说明是假的。2.3 打包工具拆出来的模块怎么从 chunk 里捞出来现代前端基本都是打包工具产物一个页面引十几个 chunk签名函数可能在任意一个里面。手动一个个点开看太蠢了我一般这么做先在网络面板按 JS 类型过滤把加载的 JS 文件列出来重点看体积中等几十 KB 到两百 KB的那些——太大的通常是框架本体太小的往往是边角料。然后在这些文件里逐个搜索关键词一般两三个文件内就能命中。如果命中了但代码被压缩成一行几万字符用开发者工具自带的格式化按钮那对花括号图标展开再看。格式化之后代码依然难读但至少能打断点、能看调用关系。还有一个偷懒的办法在控制台里遍历全局对象找挂在上面的函数名。签名函数如果被挂到全局很多老代码会这么干直接Object.keys(window).filter(k /sign|enc|wbi/i.test(k))就能筛出来。这一招不一定成功但试试不亏五秒钟的事。2.4 混淆代码不用全读懂读关键二十行就够这是我特别想强调的一点。混淆代码看着吓人但你的目标不是理解它而是把它的输入输出关系搬到 Python 里。所以真正需要读的往往只有二十行左右。具体怎么做断点命中之后看这个函数的入参一般是个对象里面是你的查询参数看它的返回值一个字符串。中间那坨逻辑你只需要确认三件事有没有对参数做排序一般是按键名字典序有没有对参数值做字符过滤常见是去掉!()*这类特殊符号有没有引入额外的密钥可能来自另一个接口也可能硬编码。把这三件事确认清楚剩下的就是照抄。至于那些_0x4a2b[_0x1c3d(0x2f1)]之类的写法本质就是用数组下标代替变量名和用字符串数组代替字面量你不需要还原它只需要在断点处看实际运行的值。我一般会在断点停下时在控制台里把入参和中间变量逐个打印出来对着打印结果反推逻辑。这比读代码快得多也不容易读错。实测下来一个中等复杂度的签名函数用这个方法二十分钟能搞定硬读代码可能两小时还在原地打转。3. wbi 签名机制拆解三步走完直接落成代码该站点这套签名体系我拆完之后的结论是逻辑极简坑在细节。整体就三步——取密钥、重排密钥、规范化参数后摘要。但每一步都有一个能让你卡半小时的细节我逐个说。3.1 密钥从哪来一个公开接口两个文件名密钥不是硬编码在 JS 里的那样太容易被抄而是每次会话从接口动态取。流程是先请求一个用户信息接口不登录也会返回从返回的 JSON 里拿两个图片地址{ code: 0, data: { wbi_img: { img_url: https://example-cdn.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png, sub_url: https://example-cdn.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png } } }关键操作是取 URL 最后一段的文件名去掉扩展名。上面两个地址分别得到7cd084941338484aae1ad9425b84077c和4932caff0ff746eab6f01bf08b70ac45各 32 位拼起来是 64 位。这里第一个坑就来了千万别把整个 URL 拿去拼。我第一次做的时候没注意直接把两个 URL 字符串连在一起算结果怎么都对不上排查了四十分钟才反应过来。正确写法def extract_key(url: str) - str: filename url.rsplit(/, 1)[-1] return filename.split(.)[0]第二个坑这两个 key 会变但不是每次请求都变一般按天或按会话周期更新。所以不要每次都去请求一次缓存起来配一个过期时间就行。缓存多久我一般设 30 分钟到 1 小时宁可多请求一次也别拿着过期密钥一直撞墙。3.2 64 位密钥怎么变成 32 位一张重排表拿到 64 位原始密钥之后要按一张固定的索引表重排然后取前 32 位。这就是所谓的mixinKey生成逻辑。重排表长这样64 个数字0 到 63 的一个排列MIXIN_KEY_ENC_TAB [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ]逻辑就是把 64 位密钥当成数组按表里的顺序取字符重新拼成一个 64 位字符串再切前 32 位def get_mixin_key(orig: str) - str: return .join(orig[i] for i in MIXIN_KEY_ENC_TAB)[:32]第三个坑在这儿这张表不是永远的。站点改版的时候会换表换完之后你的签名全线失效但错误码不会告诉你表过期了只会给你一个通用的鉴权失败。所以这张表一定要从实际抓到的 JS 里核对不要长期用别人文章里抄来的。核对方法很简单在断点处打印一下这个数组逐项对一遍。提示如果你的签名一直失败但逻辑看着没问题优先怀疑两件事——密钥取错了是不是把 URL 当 key 了、重排表过期了。这两个占了失败原因的七成以上。3.3 参数规范化排序、过滤、编码一个都不能少拿到 32 位mixin_key之后接下来是参数处理。这一步看着最简单但细节最多我给你列全。第一步加时间戳。把wts设成当前秒级时间戳放进参数字典里。注意是秒不是毫秒。这是第四个坑用毫秒的话签名本身能算对但服务端校验时间窗口会因为数值溢出直接拒掉。第二步按键名排序。字典序sorted()默认就行。注意排序要包含wts在内也就是先加时间戳再排序。第三步过滤特殊字符。遍历每个参数值把!、、(、)、*这五个字符删掉。这个规则是前端定的目的是避免 URL 编码歧义。你的过滤器必须和它完全一致多删一个少删一个都会导致摘要不同。第四步URL 编码并拼接。用标准的 query string 编码keyvalue之间用连接。注意编码规则要和前端一致Python 的urlencode默认行为和 JS 的encodeURIComponent在个别字符上比如空格、~有差异一般场景没问题但保险起见可以自定义quote_via。第五步拼密钥算摘要。把拼好的 query 字符串直接接上 32 位mixin_key做 MD5得到 32 位小写十六进制这就是w_rid。3.4 完整实现可以直接跑的一段代码把上面几步串起来就是一个完整的签名函数。我把依赖、会话、签名、请求全部写在一起你可以直接拿去改。import hashlib import time import urllib.parse import requests MIXIN_KEY_ENC_TAB [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ] def get_mixin_key(orig: str) - str: return .join(orig[i] for i in MIXIN_KEY_ENC_TAB)[:32] def enc_wbi(params: dict, img_key: str, sub_key: str) - dict: mixin_key get_mixin_key(img_key sub_key) params[wts] int(time.time()) params dict(sorted(params.items())) params { k: .join(ch for ch in str(v) if ch not in !()*) for k, v in params.items() } query urllib.parse.urlencode(params) params[w_rid] hashlib.md5((query mixin_key).encode()).hexdigest() return params调用的时候大概是这样session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://example.com/ranking/, }) resp session.get(https://example.com/x/web-interface/nav, timeout10) data resp.json()[data][wbi_img] img_key data[img_url].rsplit(/, 1)[-1].split(.)[0] sub_key data[sub_url].rsplit(/, 1)[-1].split(.)[0] params {rid: 0, type: all, page: 1, page_size: 20} signed enc_wbi(params, img_key, sub_key) r session.get(https://example.com/x/web-interface/ranking/list, paramssigned, timeout10) print(r.status_code, r.json().get(code))这段代码我在本地跑过多次返回code: 0就说明签名对上了。如果返回的是负数错误码先别改逻辑直接跳到第 5 章对着排查表走一遍效率更高。4. 从零跑通一次完整采集环境、会话、校验、落库签名搞定只是第一步真正跑起来还有一堆琐碎事。这一章我按实际执行顺序走一遍你照着做能少走弯路。4.1 环境与依赖越少越好我不喜欢一上来就装一堆库。这个需求实际只需要两个requests和hashlib后者是标准库不用装。如果要做持久化加一个sqlite3也够。真没必要上 scrapy那个框架适合大规模分布式你抓个排行榜用它属于杀鸡用牛刀光调试就够你喝一壶。pip install requests版本上没特别要求requests2.25 以上都行。Python 我用 3.9 到 3.12 都测过主要差别在urlencode的默认编码行为3.x 各版本之间是一致的不用担心。4.2 会话初始化三个请求头一个都不能少这里我要多说两句因为这是新手最容易忽略的地方。签名算对了但请求失败八成是请求头的问题。请求头是否必须作用缺失后果User-Agent必须标识浏览器直接返回风控页Referer强烈建议标识来源页部分接口返回空数据Cookie视接口会话状态部分接口需要buvid3等设备标识User-Agent不要用python-requests/2.x的默认值那个等于在自己脑门上写我是脚本。复制一个真实浏览器的 UA 就行。Referer填排行榜页面地址这个字段很多站会校验缺失的时候不报错只给你返回空列表极难排查。Cookie方面排行榜这类公开接口一般不强制登录但有些站会下发一个设备标识 cookie第一次请求的时候给你后续请求要带上。用requests.Session()就能自动处理不需要手动维护。注意会话对象要复用不要每次请求都新建。一是为了 cookie 连续性二是复用连接池能明显降低延迟抓几十页的时候差别很直观。4.3 参数拼接与结果校验先验证再批量签名拼参数的时候有个顺序问题wts必须在签名前加进去w_rid必须在签名后加进去不能反过来。我第一次写的时候把w_rid也放进了待签名的字典里结果就是拿一个空字符串参与摘要怎么都算不对。校验环节我建议分两级。第一级看 HTTP 状态码200 才算通412、429 直接说明触发了风控先降频再说。第二级看业务错误码返回体里的code字段0 是成功负数各有含义。拿到数据之后别急着写库先打印三条看看结构。排行榜接口返回的通常是嵌套结构外层是分页信息内层是一个列表列表里每一项包含标题、作者、播放量、弹幕数、分区、封面地址等字段。字段名可能和你预期的不一样比如播放量可能叫view也可能叫play多看两条就能对上。4.4 翻页与分区参数把 rid 搞明白排行榜一般是按分区划分的参数里有个rid分区 ID0通常代表全站总榜。不同rid对应不同分区具体映射关系可以从页面左侧的分类导航里逐个点开、看请求参数拿到比翻文档快。翻页参数一般是page和page_size注意有些站对page_size有上限常见是 20 或 50超过上限不报错直接按上限返回你会以为数据少了。我一般先用 20 试确认能翻到第 2 页、第 3 页再考虑要不要调大。这里有个细节同一页重复请求返回的数据可能不完全一致。排行榜是实时变化的你第 1 页抓完去抓第 2 页中间隔了几秒可能已经有条目移动了位置导致重复或遗漏。解决办法是给每条数据打上一个采集时间戳落库的时候用标题 作者 采集日期做去重键别指望靠页码去重。4.5 频率控制与使用边界这条线得自己画清楚技术上能跑通不代表可以随便跑。我在实际项目里给自己定了几个硬规矩你可以参考单次采集间隔不低于 2 秒批量翻页的时候用随机抖动比如 2 到 4 秒之间随机别用固定间隔。单日采集次数设上限我一般不超过 20 次完整翻页。不加并发。这类接口并发一上去风控几乎是必然的而且并发带来的收益几秒钟远小于被封的代价几天。数据只做本地分析不二次分发、不商用。这些都是我踩过坑之后定下来的。早期我图快用多线程跑了三十页跑到一半开始返回 412之后整整一天所有请求都是 412连正常访问都受影响。那次之后我就老实了单线程慢慢跑反而稳定。5. 踩坑实录那些错误码不会告诉你的真相这一章是我最想写的部分因为网上大部分教程只讲怎么做对不讲做错了会看到什么。而实际干活的时候你 90% 的时间都在跟错误打交道。5.1 常见错误码对照表先查表再改代码现象可能原因排查顺序HTTP 412请求频率过高 / UA 异常降频 → 换 UA → 加 RefererHTTP 429触发限流等待 10 分钟以上再试code: -352风控校验未通过检查 cookie、UA、签名code: -403签名参数缺失或错误检查 w_rid、wts 是否都带上code: -400参数格式不对检查 rid 是否为数字、page 是否超范围code: 0但列表为空分区无数据 / Referer 缺失换 rid 试 → 补 Referer签名本地算对但服务端不认密钥过期 / 排序不一致重新取 key → 打印排序后字典这张表是我从实际报错里攒出来的覆盖了九成以上的情况。查表的时候注意一点不要一次改多个变量。有人一看到失败就把 UA、Referer、签名逻辑全改一遍改完通了也不知道是哪个起了作用下次遇到同样问题还是不会。正确做法是一次只动一个动完验证记录结果。5.2 时间戳的坑本地时钟可能不准wts参与签名服务端一般会校验一个时间窗口比如前后 5 分钟。如果你的机器时间偏差过大签名算得再对也会被拒。我就遇到过一次本地虚拟机休眠之后时间漂了十几分钟所有请求全挂错误码还是那个通用的鉴权失败查了快一个小时才发现是系统时间的问题。后来我养成了一个习惯签名失败的时候第一件事是打印本地时间戳和服务端返回的时间做对比。如果确实有偏差又没法改系统时间可以在请求前先拿一次服务端时间有些接口的响应头里带Date算出差值在本地时间戳上做补偿。这个做法有点绕但能救急。5.3 签名对不上的五步定位法按顺序走签名失败是最常见的我整理了一个固定流程按顺序执行基本能在十分钟内定位。第一步打印最终参数字典。把排完序、过滤完、加上w_rid之后的完整字典打出来肉眼对一遍键名有没有拼错、值有没有多空格。第二步打印拼好的 query 字符串。这一步是核心因为最终的摘要就是这个字符串加密钥算出来的。你可以把这段字符串拿到浏览器的断点处和前端实际用的那个做逐字符对比。我习惯用difflib比一下差异位置一目了然。第三步打印 32 位 mixin_key。和前端断点处的值对一遍。不一样就说明密钥取错了或者重排表过期了。第四步单独验证 MD5。拿一个固定的 query 和固定的 key在 Python 和浏览器控制台各算一次看结果是否一致。这一步能排除编码问题。第五步检查编码差异。如果前四步都一致但还是失败那就是 URL 编码的细节差异。重点看空格、~、中文、以及数组类型参数的序列化方式。这五步走完还搞不定的话八成不是签名的问题而是别的参数比如某个隐藏的设备标识没带上。5.4 密钥缓存缓存太久和每次都取都不可取密钥缓存这件事两个极端都有问题。每次都取请求数翻倍容易触发限流缓存太久密钥轮换之后全是无效签名。我的做法是内存缓存 失败重取。正常情况用一个字典存着设个 30 分钟过期一旦签名请求返回鉴权类错误码立刻清缓存、重新取 key、重试一次。重试还失败就不再重试直接抛错避免陷入死循环。import time _cache {key: None, ts: 0} def get_keys(session, ttl1800): now time.time() if _cache[key] and now - _cache[ts] ttl: return _cache[key] resp session.get(https://example.com/x/web-interface/nav, timeout10) data resp.json()[data][wbi_img] keys ( data[img_url].rsplit(/, 1)[-1].split(.)[0], data[sub_url].rsplit(/, 1)[-1].split(.)[0], ) _cache[key] keys _cache[ts] now return keys这段小逻辑看着不起眼但它能让你的脚本从跑两小时挂一次变成跑一整天没事。工程上的稳健性往往就藏在这种几行代码里。6. 把玩具脚本变成能长期用的工具跑通一次和能长期用中间隔着的不是技术难度是工程习惯。这一章讲三个我认为最重要的改造。6.1 签名逻辑独立成模块别和业务混在一起我见过太多脚本签名代码和请求代码、解析代码全揉在一个文件里两百行糊成一团。改的时候不敢动一动就全崩。正确做法是把签名拆成一个独立模块对外只暴露一个函数传参数字典进去返回签好名的字典。这个模块不关心你要抓什么不关心rid是多少只做一件事。这样站点改版的时候你只需要改这一个文件其他代码一行不动。模块内部再拆成三个小函数取密钥、生成 mixin_key、签名。每个函数都能单独测试出问题的时候定位范围立刻缩小到十几行。6.2 变更监控让脚本自己告诉你签名挂了签名体系是会变的。与其等某天跑脚本发现全是错误码不如让脚本主动报警。我的做法很简单每次运行前先做一次健康检查——用固定参数请求一次看code是不是 0。不是 0 就发一条通知邮件、webhook 都行然后中止本次任务不继续消耗请求次数。另外记录一个成功率指标。如果连续几次运行的成功率从 100% 掉到 30%即使还能出数据也说明有问题了值得看一眼。这个指标我一般写在日志里跑完打印一行不需要多复杂的监控系统。6.3 数据校验脏数据比没数据更麻烦最后说个容易忽略的点。排行榜数据里经常有异常值播放量是--、封面地址是空串、作者名里带特殊字符、标题里有换行符。这些脏数据进了库后面做分析的时候非常难受。我一般在入库前做三道清洗类型统一数值字段强制转 int转换失败的置为 None 并记录原始值。字符串净化去掉首尾空白、把连续换行压成一个空格、过滤掉控制字符。完整性检查主键字段标题、作者、排名任一为空就丢弃这条并计数。如果一次采集丢弃超过 10%说明解析逻辑可能出问题了该去看看。还有个小技巧把每次采集的原始 JSON 原文留一份存档压缩后存文件就行解析逻辑改错了可以重新跑不用再请求一遍。这个习惯帮我省过好几次事尤其是在字段名悄悄变更的时候——拿着旧数据一对比立刻知道是哪个字段改了名。我个人在实际操作中的体会是这类带签名的公开接口逆向本身的门槛其实没有想象中高真正拉开差距的是那套出错了怎么快速定位的方法论。签名函数你可能两小时就还原出来了但后面三个月里你还会反复用到第 5 章那张排查表。所以别急着把脚本写得多么花哨先把定位流程、对比手段、缓存和重试这几件事做扎实剩下的都是水到渠成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑