从打包文件里搜到 /ajax/,很容易当成协议已经到手。
微博热门页把我按回去了。前端资源里能列出二十多个候选入口,页面模块也写着热榜、频道、侧栏和单条微博的调用参数。可是用同一访客会话逐个打过去之后,它们并不是同一种东西:有的给出列表,有的给出权限失败,有的给出登录空态,有的只是搜索无结果。
静态字符串证明前端曾经为这些路径写过代码。它不证明当前访客、当前会话、当前权限下这些路径仍然交付目标数据。
先把页面打开,再谈接口
更早的一次观测里,普通 HTTP 客户端拿到的并不是热门内容,而是访客分流页。补 User-Agent 不够;换成接近 Chrome 的 TLS / HTTP2 栈,也还停在访客协议。真正让后续接口开始说话的,是访客状态机走完,并在同一个 Cookie 容器里留下会话,而不是某一个 ajax 路径被“找出来”。
这一步已经足够改变验证顺序。如果 HTML 应用壳还在访客页,后面所有 200 JSON 都可能只是分流结果。内容边界必须落到具体字段:热门时间线里的状态数组,或侧栏热搜里的 realtime 列表。visitor_gate=false 只说明访客协议结束,不等于正文成功。
《同一个 200 里至少有三种拒绝》 记录的是响应码被压缩后的错觉。这里要记录的是更前面一步:候选 URL 被压缩成“协议”的错觉。
同一模块里的路径,要用不同解析器
热门首页的前端把 tab、type 和 apiParams 写在一起。这会让人以为所有列表都能用同一个解析器。live 响应否定了这件事。
分类页归一到 band_list,热门时间线归一到顶层 statuses。侧栏统一热搜用 type 选择不同榜,成功时走 data.realtime,失败时用 ok/msg 表达权限。单条微博子链以 id 为主键,长文、评论和转发时间线的空态含义也不相同。
如果把这些响应喂给同一个“找数组”函数,权限失败会被当成空列表,登录空态会被当成协议未还原,搜索无结果会被当成签名错误。正确的动作是先分类,再解析。
我把请求元数据、原始 JSON、schema 契约和可重放 URL 分开保存,最后用离线结构检查验收。验收看的是:访客会话是否建立、HTTP 是否到达、字段属于哪一个业务族、权限分支有没有被保留。它不看某次热词或用户计数,因为那些不是稳定协议常量。
静态候选里最贵的误报
前端同一模块同时包含公开、登录态和权限模块。静态扫描会把趋势榜、爆榜之类的路径一起收进来。访客会话打过去之后,其中一部分明确返回无权限。
这类负证据必须保留。删掉它们,报告会看起来更“完整”,却把权限边界写丢了。留下它们,才能避免下一步把登录墙当成传输问题,或把权限接口写进公开采集范围。
网络瞬态也要单独记账。访客引导第一次出现过 TCP 超时,加大一次连接超时后成功。如果把这次超时写成“接口不存在”,整条业务图都会画错。
这一次只证明到哪里
它证明:微博热门页的公开读取,依赖访客状态机、同一会话和按业务族分类的响应 schema;静态 ajax 列表只是候选,不是协议。
它没有证明登录后的写操作、验证码或其他客户端保护。热榜内容会变,所以验证停在结构和分支,不停在某条具体微博。公开文章也不提供可复现的采集入口。
《逆向完成之后:把一次性结论变成可回归工具》 说的是算法样本和测试入口。对业务协议,最小可回归物是另一样东西:一份带分类的接口清单,以及每个分类对应的 schema 与空态语义。没有这份分类,源码里的 URL 仍然只是源码。