跳到正文
← 返回博客
FIELD NOTE / 工程实践

前端源码里的 URL,不等于访客能调用的接口

微博热门页的静态 ajax 候选很多,真正可公开读取的只是其中经过响应分类的一小部分。

从打包文件里搜到 /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 仍然只是源码。