
做网页采集时复制 Network 中的一条请求通常只能解释“这一次发出了什么”。要复现请求还要知道参数来自输入框、页面状态、配置还是运行时计算。JS 逆向分析可以先从这条数据链入手原始输入 → 归一化 → 序列化 → 发起请求。本文使用自建本地页面不依赖真实站点。示例故意包含空格、加号和时间戳用来观察字符串在哪一步变化。已通过本地浏览器验证正常请求与空输入分支断点操作方法依据 Chrome 官方文档未把它表述成第三方站点的测试结果。一、准备两个文件在同一个目录建立 index.html 和 result.json。index.html 内容如下!doctypehtmlmetacharsetutf-8title请求参数追踪实验/titlelabelSKUinputidskuvalueA B01/labelbuttonidquery查询商品/buttonpreidoutput等待点击/prescriptconstinputdocument.querySelector(#sku);constoutputdocument.querySelector(#output);functionnormalize(raw){returnraw.trim();}functionbuildQuery(sku,now){returnnewURLSearchParams({sku,region:CN,ts:String(now)}).toString();}asyncfunctionrequestProduct(){constrawinput.value;constskunormalize(raw);if(!sku){output.textContentSKU 不能为空;return;}constquerybuildQuery(sku,Date.now());consturl/result.json?query;try{constresponseawaitfetch(url,{cache:no-store});if(!response.ok)thrownewError(HTTP response.status);constdataawaitresponse.json();output.textContentJSON.stringify({raw,sku,url,data},null,2);}catch(error){output.textContentString(error);}}document.querySelector(#query).addEventListener(click,requestProduct);/scriptresult.json 内容{ok:true,note:静态演示响应不是商品接口}在该目录运行python3-mhttp.server8768--bind127.0.0.1浏览器打开 http://127.0.0.1:8768/ 点击“查询商品”。请通过这个本地 HTTP 地址访问直接双击文件会改变请求上下文。静态服务器忽略查询参数来读取 result.json因此响应只用于验证链路没有实现商品查询也没有验证时间戳。二、先记录一次正常样本默认输入在两端带空格中间包含一个空格与一个字面加号。页面输出的 raw 保留原输入sku 为 A B01URL 中对应片段为skuAB%2B01regionCNts运行时毫秒时间戳这里发生了两件不同的事trim 去除了首尾空白URLSearchParams 把中间空格序列化为 把字面加号序列化为 %2B。它是 URL 查询参数编码不是加密。MDN 的 URLSearchParams 文档说明了此类编码行为。这也是为什么不应先把 SKU 手动编码再交给 URLSearchParams百分号可能被再次编码最终发送值与业务值不同。先确定你比较的是原始字符串、解码后的参数还是 URL 字节表示。三、从请求出口向上追打开 DevTools 的 Network找到 result.json 请求核对查询参数和响应。请求详情中的 Initiator 可以帮助定位发起代码。随后在 Sources 的 XHR/fetch Breakpoints 中添加 result.json再次点击按钮。相关能力见 Chrome 的 断点文档。暂停后先看当前请求相关的 url、query、sku在调用栈中切换到业务函数分清哪层负责准备参数哪层仅仅包装 fetch。异步边界可能使调用栈不完整此时结合 Initiator 和明确的行断点定位。有个很容易踩的坑停在 fetch 时normalize 和 buildQuery 已经执行完了。调试器不会因为你继续单步就回到过去。要看参数生成过程应在 requestProduct 中读取输入前或 buildQuery 调用前设置行断点然后重新触发一次操作。第二轮依次观察 raw、sku、query、url每经过一个函数就记录输入与返回值。这样得到的是可验证的数据流而不是根据压缩函数名猜用途。四、用少量对照输入确认推断本地正常样本已经观察到 A B01 被发送为 AB%2B01仅输入空格时页面显示“SKU 不能为空”不进入 fetch 分支。读者还可以继续做三组对照把字面加号换成空格比较编码结果是否变化。连续点击两次检查只有时间戳变化还是其他字段也变了。保持输入不变修改本地 region 配置确认字段究竟来自哪里。这些对照建议不等于真实站点的业务规则。本例没有服务端签名校验、会话校验和缓存层不能根据它推导生产请求只需要这三个参数。如果值每次都变也先不要断言“用了随机加密”。时间戳、计数器、随机数、会话状态和序列化顺序都可能导致变化。区分它们需要固定其他输入并追到具体赋值点。五、压缩代码与 source map 怎么处理格式化可以改善缩进但不会恢复原始变量名和业务含义。可用的 source map 能帮助把部署代码映射到源文件是否存在、是否加载成功要看实际页面。参考 Chrome 的 source map 文档。没有 source map 时优先用已观察到的 URL 片段、参数键和调用位置收窄范围再从运行时输入输出判断函数作用。不要一开始就试图读懂整个 bundle。AI 可以帮助解释已经定位的小段代码或列出待验证的变量来源假设。给它的材料最好包括函数片段、一次输入、一次输出和你已经确认的调用关系。它给出的“像哈希”“可能是签名”只能作为线索需要回到运行时验证。六、最终应留下什么记录一份有用的逆向笔记应写明触发动作、输入上下文、参数来源、转换函数、请求出口以及尚未确认的依赖。比如本例可归纳为按钮事件读取输入trim 得到 sku固定 CN 和 Date.now() 组成对象URLSearchParams 序列化后交给 fetch。采集实现是否与页面一致要逐字段对比同一业务输入下的结果。先把“这个参数怎样生成”讲清楚再讨论换客户端、移植逻辑或增加 AI 自动化排错会更有依据。本文由 AI 辅助编写本地功能示例已运行验证生产站点的行为需另行核实。