
做爬虫的朋友应该都有过这种体验接口参数明明抓包看得一清二楚但把请求复制出来重新发一次返回的就是错误、签名校验失败、或者频繁出现风控提示。问题往往出在一个环节——你没有搞懂请求里的动态参数是怎么生成的。这次我们来看爬虫绕不开的核心技能JS 逆向入门重点解决“看懂动态参数怎么生成”这个问题。我会用一套通用分析思路带你从浏览器开发者工具定位参数、到打断点观察生成过程、再到用 Python 模拟请求完整走一遍。阅读前提是你已经会用 requests 写简单爬虫并且能看懂最基本的 JavaScript 函数。没有 JS 基础也不怕第 6 节会从最核心的调试方法讲起。这篇文章会讲清楚这几件事动态参数通常藏在哪些位置、怎么在浏览器里定位到生成它的 JS 代码、断点调试怎么用、拿到加密函数后怎么在 Python 里调用以及爬虫逆向过程中最常见的坑和合规边界。1. 核心能力速览能力项说明学习难度中等需要 Python 基础 浏览器开发者工具基础必备基础Python 3、requests、HTTP 协议、HTML/JS 基础核心工具Chrome DevTools、Node.js、Python、二选一抓包工具主要知识点动态参数定位、JS 断点调试、加密函数识别、请求模拟适合场景授权接口调试、自身系统安全测试、爬虫学习、逆向学习不适合场景未经授权的数据采集、绕过风控和验证码、攻击线上系统推荐学习路径先抓包 - 再定位 - 后调试 - 最后模拟先说结论动态参数的本质就是“服务器不希望请求里出现固定内容”。它要求每次请求携带一个由时间戳、随机数、业务参数、密钥共同计算出来的值。你要做的不是硬猜算法而是顺着浏览器执行环境把生成流程找出来。整个入门阶段最值得投资的时间在“定位”和“调试”而不是在 Python 端反复猜测。把 Chrome DevTools 的 Network、Sources、Call Stack 用熟大部分动态参数都能定位到。2. 适用场景与使用边界哪些场景需要学动态参数的逆向接口联调时对方接口加了签名参数你需要理解参数逻辑才能完成授权范围内的调试。自动化测试中被测系统每次生成动态 token脚本里需要动态计算。自研系统的安全自测检查动态参数是否容易被篡改。技术学习与安全方向研究。需要明确的是爬虫逆向能力是一把双刃剑。请务必只在下面这些边界内使用只测试自己有权限的接口比如自己开发的系统、公司授权测试的站点。只用于学习研究方法论不针对特定站点做批量采集、验证码绕过或风控对抗。不传播商业平台的核心签名算法、加密密钥和反爬绕过方案。遇到登录后才可见的数据优先走正规接口或官方 OpenAPI。这篇文章后面所有示例都是通用教学代码不针对任何真实平台。在实际操作时请先确认目标接口的使用条款、Robots 协议和当地法律法规。“能不能做”这个问题永远比“怎么做”更优先。3. 环境准备与前置条件做 JS 逆向不要求你从零学会 JavaScript但至少要能看懂函数定义、对象取值、字符串拼接、md5/hash 调用这些片段。下面列一套最小环境。3.1 最小工具清单工具用途建议Chrome 浏览器抓包、调试、检索 JS最新稳定版即可Python 3.8请求模拟、脚本编写建议 3.10 以上requests 库发送 HTTP 请求pip install requestsNode.js本地运行 JS 函数学习阶段非必须但推荐execjsPython 调用 JSpip install PyExecJS需要 Node 环境Fiddler / Charles移动端抓包、HTTPS 解密入门阶段非必须3.2 检查本机环境# 查看 Python 版本 python --version # 查看 Node.js 版本 node -v # 安装 requests pip install requests # 安装 PyExecJS pip install PyExecJS3.3 验证 Node.js 是否可用node -e console.log(hello)如果输出hello说明 Node 可用。后面如果用 execjs必须保证本机 Node 能正常执行 JS。3.4 目录规划建议学习阶段统一用一个目录js_reverse_study/ ├── 01_analysis/ # 抓包截图、请求记录 ├── 02_js_scripts/ # 从页面提取的 JS 函数 ├── 03_simulate/ # Python 模拟脚本 └── outputs/ # 测试输出把 JS 脚本和 Python 脚本分开放方便反复调试。4. 动态参数最常见的四类位置先建立直觉请求里哪些值可能被服务端校验我从大量入门案例里总结出四类高发位置。4.1 URL Query 参数比如https://example.com/api/list?page1signabc123ts1710000000其中sign是签名ts是时间戳。服务端会算一遍签名如果与sign不一致就拒绝请求。4.2 Request Headers 自定义头常见的有X-Sign: a1f2b3c4 X-Timestamp: 1710000000 Authorization: Bearer xxxxx这类参数往往在请求头里而不是 URL 上新手很容易忽略。4.3 Request Body 表单或 JSON 字段{ keyword: test, sign: 9f3b6e2a, nonce: 89c12d }有些站点会先把表单内容做拼接加密再把加密结果放到 body 里。这样服务端既能校验参数完整性又能防止请求被篡改。4.4 Cookie 动态字段某些服务端会在响应头 Set-Cookie 里下发一段随机值或由 JS 动态生成 Cookie 字段之后的请求需要携带。定位这类参数稍微绕一些因为生成时机可能在页面加载阶段。入门阶段的定位策略先抓包看请求参数找出“每次都不一样”的字段再针对这个字段搜索其生成位置。5. 定位动态参数的生成位置这是整个逆向流程最核心的一步。下面用一套通用操作流程说明。5.1 用 Network 面板确认目标请求在 Chrome 里打开目标页面按 F12切到 Network 面板勾选 Fetch/XHR触发一次请求。点击请求后在 Payload/Headers 面板确认动态参数名。例如请求 URL 里有sign复制这个值。然后重新触发请求对比两次sign是否变化。如果每次都不一样它就是一个动态参数。5.2 用 Search 搜索参数名打开 Sources 面板按CtrlShiftF打开全局搜索输入参数名比如sign。搜索结果会列出所有包含sign的 JS 文件与代码位置。注意文件名可能被混淆成a.js、w_1.js不要被目录名迷惑。结果太多时优先看与当前请求 URL 所在域名一致的 JS 文件。优先看带有function、return、md5、hex、encrypt、sign等关键词的片段。5.3 通过 XHR 调用栈回溯更高效的方法是在 Network 面板中找到目标请求点击Initiator或Call Stack这里会列出 JS 发起请求时的调用链。顺着调用链点进去能快速定位到发送请求的函数。这段话很关键不要从代码第一行开始读 JS而是从请求发起处往回找参数。这是入门阶段最实用的方法论。5.4 使用 XHR/fetch 断点在 Sources 面板右侧的 Event Listener Breakpoints 里展开 XHR勾选fetch和XMLHttpRequest然后重新触发请求。浏览器会在发请求前停下。此时查看 Call Stack就能看到当前请求由哪个函数发出再往前找参数即可。示例调用栈示意 sendRequest (index.js:120) handleClick (app.js:80) bindEvent (app.js:45)从handleClick往上看动态参数很可能在sendRequest内部生成也可能在更早的app.js中被预计算。6. JS 调试三个核心断点技巧定位到参数生成代码后下一步是理解它。下面三个断点技巧能覆盖大多数入门场景。6.1 在可疑函数处打断点在 Sources 面板里找到函数点击行号添加断点重新触发请求浏览器会在断点处暂停。此时可以看右侧 Scope 面板中的局部变量和参数值。6.2 条件断点如果某个函数会被调用很多次每次打断点太烦可以在断点上右键选择Edit breakpoint输入条件比如arg1 sign只有条件满足时才会暂停。这是过滤干扰的常用手段。6.3 单步执行并观察变量Step over跳过一步一步执行不进入函数内部。Step into进入进入当前函数内部。Step out跳出跳出当前函数。右键变量点Add to watch持续观察某个变量的变化。找参数生成逻辑时重点观察输入参数时间戳、随机数、业务字段。中间拼接字符串原始字符串长什么样。输出结果最终传给请求的值。6.4 一个通用示例模拟签名函数下面是一段知识讲解用的通用 JS 签名示例不针对任何平台。它的逻辑在很多接口签名里都有影子。// 通用示例模拟签名生成逻辑仅用于教学 function md5(str) { // 假设这是项目里内置的 md5 实现 // 实际逆向时断点打在内部可以观察输入 return custom_md5_ str; } function buildSign(params, secret) { const keys Object.keys(params).sort(); let raw ; for (let i 0; i keys.length; i) { const key keys[i]; if (key ! sign) { raw key params[key] ; } } raw secret secret; // 在这里打断点可以观察 raw 的完整格式 const sign md5(raw); return sign; } // 调用 const params { page: 1, size: 20, ts: 1710000000 }; const sign buildSign(params, test_secret); console.log(sign);调试时你会看到raw先对 key 排序再拼接成page1size20ts1710000000secrettest_secret最后取 md5。类似结构在真实站点中非常常见。6.5 混淆代码如何处理有些 JS 会被混淆函数名变成_0x2a3f字符串变成 Unicode 或 Base64。入门阶段不需要立刻学会反混淆先做三件事找代码里的_0x、\x、atob、charCodeAt等特征。在混淆位置附近打断点观察运行时输入输出。用 Chrome 的 Pretty Print左下角{}按钮格式化压缩代码。记住断点调试不依赖读懂混淆只依赖观察输入和输出。你不需要理解每一行只要能复现最终动态参数即可。7. 用 Python 复现动态参数请求找到参数生成逻辑后有三条路可以复现。难度从低到高分别是直接调接口、execjs 调 JS、Python 重写算法。7.1 方式一先用 requests 跑通固定请求拿到一个真实请求的完整信息后先用 requests 原样复现一遍确认 Cookie、Headers、参数都对。import requests url https://example.com/api/list headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Content-Type: application/json } payload { page: 1, size: 20, ts: 1710000000, sign: 替换成抓包得到的值 } resp requests.post(url, jsonpayload, headersheaders, timeout10) print(resp.status_code) print(resp.text)如果这一步能返回正常数据说明请求结构没问题剩下的问题只是怎么动态生成sign。7.2 方式二用 execjs 直接执行 JS 函数把定位到的签名函数提取成一个独立 JS 文件用 PyExecJS 调用。假设02_js_scripts/sign.js内容为// 通用示例仅用于教学 function generateSign(params, secret) { const keys Object.keys(params).sort(); let raw ; for (let i 0; i keys.length; i) { raw keys[i] params[keys[i]] ; } raw secret secret; // 教学示例用简单字符串模拟 hash return hash_ raw; }Python 侧调用import execjs import time import requests with open(02_js_scripts/sign.js, encodingutf-8) as f: js_code f.read() ctx execjs.compile(js_code) params { page: 1, size: 20, ts: int(time.time()) } secret test_secret sign ctx.call(generateSign, params, secret) print(生成的 sign:, sign) # 组装请求 url https://example.com/api/list headers { User-Agent: Mozilla/5.0, Content-Type: application/json } payload { **params, sign: sign } resp requests.post(url, jsonpayload, headersheaders, timeout10) print(resp.status_code) print(resp.text)execjs 的好处是“算法一点不改”坏处是依赖 JS 环境。如果目标 JS 里用了浏览器特有的window、document对象execjs 会报错。入门阶段先处理能直接运行的纯 JS 函数即可。7.3 方式三Python 重写加密算法当 JS 里只用到 md5、sha256、base64 等标准算法时可以用 Python 直接重写性能最好。import hashlib import time import requests def generate_sign(params: dict, secret: str) - str: keys sorted(params.keys()) raw for key in keys: if key ! sign: raw f{key}{params[key]} raw fsecret{secret} # 用 md5 模拟实际以目标算法为准 return hashlib.md5(raw.encode()).hexdigest() params { page: 1, size: 20, ts: int(time.time()) } secret test_secret sign generate_sign(params, secret) url https://example.com/api/list payload { **params, sign: sign } resp requests.post(url, jsonpayload, timeout10) print(resp.status_code) print(resp.text)如果你发现 JS 里使用了非标准、自定义的加密过程比如多次迭代、字符串置换、依赖大型加密库那就优先用 execjs 运行原 JS而不是花大量时间重写。7.4 批量请求时的注意点当业务需要批量请求时动态参数通常要求“每个请求都有独立的时间戳和签名”。不要在循环里复用同一个sign。正确做法是每次请求前重新生成。import time import requests def fetch_page(page_num: int): params { page: page_num, size: 20, ts: int(time.time()) } sign generate_sign(params, test_secret) params[sign] sign resp requests.post(url, jsonparams, timeout10) return resp for page in range(1, 6): resp fetch_page(page) print(page, resp.status_code) time.sleep(1) # 控制频率避免对服务端造成压力同时批量任务一定要加日志和失败重试避免某个请求因频率过高失败后直接中断整个流程。8. 常见问题与排查方法问题现象可能原因排查方式解决方案抓包能看到参数但搜索 JS 找不到参数名被拼接或动态生成搜索参数子串如sign的一半在请求处打断点观察参数对象execjs 调用报错window is not definedJS 依赖浏览器全局对象检查代码中是否使用 window/document改用 Node 环境补齐全局对象或把相关逻辑抽象出来Python 生成的签名与抓包不一致拼接顺序或分隔符不同对比原始请求的 key-value 排序在 JS 断点处观察 raw 字符串逐步比对请求返回签名错误漏传其他参与计算的参数检查请求头、Cookie 是否完整将抓包请求完整复制逐项比对请求频繁触发风控请求频率过高或缺少浏览器指纹检查 User-Agent、请求间隔降低频率使用合法授权流程JS 代码被混淆看不懂逻辑代码经过压缩混淆使用 Pretty Print 格式化观察断点处的输入输出不纠结每一行Node.js 版本导致 execjs 执行失败Node 路径未正确配置命令行执行node -v重新安装 Node 并配置环境变量批量任务中途停止某一请求超时或抛出异常在循环中加 try/except 和日志增加失败重试与断点续跑逻辑9. 最佳实践与合规建议9.1 工程化建议第一次跑通后先保存一组“最小可运行请求”作为基准后续改动时用来做对比。把 JS 脚本、抓包记录、Python 脚本分目录管理命名带日期。批量请求必须限制并发数建议 1 到 3 并发起步并设置单次请求超时。输出结果统一写入文件或数据库而不是只打印到终端。接口服务如果开放给其他模块调用要考虑请求频率、错误日志和访问控制。9.2 降低资源占用JS 逆向通常是短时任务不需要常驻高内存服务。用 execjs 时建议重复利用同一个 Context而不是每次请求都重新编译 JS。Python 调用时保持一个编译后的 context 对象复用即可。# 复用 context避免每次都编译 with open(02_js_scripts/sign.js, encodingutf-8) as f: ctx execjs.compile(f.read()) def make_sign(params): return ctx.call(generateSign, params, test_secret)9.3 合规红线再强调一次不要在未经授权的情况下采集非公开数据。不要用于绕过登录、验证码、滑块、人机校验等安全机制。不要传播或商业化使用从逆向中获取的密钥、签名算法和加密参数。涉及用户隐私数据的接口一律不得处理。企业内使用时先确认是否违反目标平台的服务条款。如果你只是学习建议在自己的测试环境或开源靶场项目上练习这样既能掌握技术又不会触碰法律风险。9.4 接口 API 对接建议如果你把动态参数计算封装成 API 服务给其他脚本或团队使用需要注意from flask import Flask, request, jsonify import hashlib import time app Flask(__name__) def generate_sign(params: dict, secret: str) - str: keys sorted(params.keys()) raw .join(f{k}{params[k]} for k in keys if k ! sign) raw fsecret{secret} return hashlib.md5(raw.encode()).hexdigest() app.route(/sign, methods[POST]) def sign_api(): data request.get_json() if not data or params not in data: return jsonify({error: params required}), 400 params data[params] sign generate_sign(params, data.get(secret, test_secret)) return jsonify({sign: sign, ts: int(time.time())}) if __name__ __main__: app.run(host127.0.0.1, port7860)启动后可以用 curl 或 Python 调用curl -X POST http://127.0.0.1:7860/sign \ -H Content-Type: application/json \ -d {params:{page:1,size:20}}这样把动态参数计算独立成服务其他语言也能调用。但要注意接口服务只应跑在受控网络内并加上调用方鉴权避免被滥用。10. 总结动态参数逆向入门核心不是“破解”而是“看懂”。看懂参数从哪里来、经过什么计算、最后放到哪个位置。这篇文章给的定位方法和调试思路希望能让你在遇到签名参数、token 参数、自定义请求头时不再一头雾水。先跑通一件小事用 Chrome DevTools 找到一个接口对比两次请求找出动态参数再用 Sources 面板搜索参数名最后在可疑函数处打断点观察输入输出。这个过程不需要懂全部 JS也不需要安装重型工具但对理解动态参数很有帮助。最容易踩的坑是一上来就把时间花在“把抓包参数直接复制进 Python”上结果发现参数过期或签名错误。正确的顺序是先确认请求结构再定位生成逻辑最后选择 execjs 或 Python 重写。批量请求时记住动态参数必须实时生成不能复用旧签名。后续可以继续扩展的方向包括更复杂的 JS 混淆处理、Node.js 下的浏览器全局环境模拟、以及把动态参数封装成可复用的签名服务。无论往哪个方向深入都记得守住合法授权和合规使用的边界。