JS逆向入门:从动态参数定位到Python模拟实现

📅 发布时间:2026/8/27 5:14:05
JS逆向入门:从动态参数定位到Python模拟实现 做爬虫时间长了你会发现一个铁律能拿到的数据都是服务器允许你拿到的拿不到的数据才是真正值得研究的对象。刚开始用requests写爬虫时只要处理好 headers、cookie、代理大部分网站都能轻松拿下。但一旦遇到需要生成动态参数的反爬机制例如签名sign、动态 token、加密的buvid之类的参数事情就变得不那么简单了。很多新手在第一次看到请求参数里有一串看不懂的加密字符串时第一反应是“这网站是不是不想让人爬了”。其实不然动态参数本质上就是网站前端为了防止自动化脚本批量请求而设计的一道门槛。想跨过这道门槛就需要打开浏览器开发者工具进入 JS 的世界去定位“参数是怎么生成的”。这也是爬虫进阶路上最值得投入时间掌握的技能——JS 逆向。本文将围绕“动态参数怎么生成”这个问题从概念铺垫、抓包定位、JS 断点调试、Python 模拟实现四个维度带你完成一次完整的 JS 逆向入门实操。文章默认你已经有 Python 基础会用requests写简单爬虫但还没系统接触过 JS 逆向。1. 先说清楚什么是动态参数为什么网站要生成它1.1 静态参数与动态参数的区别在写爬虫时我们通常会先通过浏览器 F12 打开开发者工具看某个数据请求的 URL、Headers 和 Payload。看到的结果往往分成两类类型特点示例静态参数固定不变直接写死在代码里appid123456、version1.0.0、platformweb动态参数每次请求都会变化由前端 JS 动态计算sign2e3f4d5c...、tokenabc123、timestamp1699999999静态参数没什么好说的写死即可。动态参数才是麻烦所在因为同一个接口你刷新一次页面参数值就变了直接复制浏览器里的参数到requests代码中下一次请求大概率会失效。1.2 网站为什么需要动态参数从网站开发者的视角来看动态参数通常承担以下职责防止请求重放通过时间戳 签名的方式确保一个请求在短时间内有效过期作废。防止自动化脚本参数由前端 JS 加密生成Python 的requests库默认不会执行 JS所以不处理动态参数就拿不到合法的请求数据。区分正常用户与爬虫正常用户通过浏览器访问页面浏览器会执行 JS动态参数自然生成爬虫直接构造请求缺少动态参数服务器就可以直接拒绝返回数据。风控与反爬追踪部分平台通过动态参数埋入设备指纹、浏览器环境信息用于识别同一台设备或同一 IP 的异常行为。1.3 动态参数的常见形式不同网站的封装方式不一样但常见的动态参数主要有以下几类时间戳型动态参数参数值就是当前时间戳服务器用来判断请求时间差。MD5 / SHA 系列散列把 URL、密钥、时间戳按一定规则拼接后做散列处理。AES / RSA 对称或非对称加密把请求体加密后传输服务器端再做解密。自定义混淆算法前端把明文参数经过字符替换、数组打乱、编码嵌套等混淆手段生成动态值例如 B 站早期buvid参数、某些加速乐 cookie 参数就属于此类。环境指纹型参数基于 Canvas 指纹、WebGL 信息、浏览器版本等生成的参数。理解了上面这些你就明白为什么学爬虫必须学 JS 逆向。因为动态参数的生成逻辑写在 JS 文件里你不读 JS就永远只能猜。2. 环境准备与工具清单学习 JS 逆向最忌讳空谈理论必须打开真实项目来练手。下面先把开发环境准备好。2.1 基础运行环境操作系统Windows / macOS / Linux 均可本文示例以 Windows 10/11 为主。Python建议使用 Python 3.9 及以上版本。Node.js建议使用 Node.js 16 及以上版本用于本地运行和调试 JS 片段。浏览器Chrome 或 Edge本文以 Chrome 为例。2.2 Python 需要安装的库建议创建一个独立的虚拟环境避免污染全局 Pythonpython -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate pip install requests pyexecjs如果你打算做更复杂的 JS 逆向还可以用到这些库库名用途requests发送 HTTP 请求pyexecjs在 Python 中调用 JS 代码PyMiniRacer更快的 JS 执行环境node子进程通过 Python 调起 Node 执行 JS 文件playwright/selenium浏览器自动化协助分析动态参数需要注意的是pyexecjs依赖本地的 JS 运行环境。在 Windows 上默认使用 JScript执行 ES6 语法可能会报错更建议在系统安装 Node.js然后在pyexecjs中配置使用 Node。2.3 抓包工具Chrome DevTools最常用也是新手优先掌握的。Charles / Fiddler用于 Android 模拟器、真机 App 抓包做 App 逆向时再用。本文主要用 Chrome DevTools因为 JS 逆向的核心场景是 Web 端动态参数分析。对 App 里的 JS 逆向抓包工具和脱壳工具又是另一套体系这里先不展开。3. JS 逆向的第一课学会在 DevTools 里定位动态参数生成位置这一节是整个 JS 逆向的核心基本功。很多人卡在“不知道从哪里下手”本质上是 DevTools 的使用方法没掌握好。下面我把定位流程拆解成四步。3.1 第一步Network 面板找请求与参数打开 Chrome 访问目标网站按F12打开开发者工具切换到Network面板。刷新页面在请求列表中点击你要分析的数据请求例如一个 JSON 数据接口。在请求详情里关注两个地方Headers 下的 Query String Parameters或Payload。Initiator列它表示该请求是由哪个 JS 文件发起的。例如你在 Payload 里看到一个参数叫sign值是一串 32 位的十六进制字符串那基本可以断定它的生成算法大概率是 MD5 或某种自定义拼接后散列。此时点击该请求右键选择Copy - Copy as cURL可以快速拿到完整的请求信息方便后续模拟。3.2 第二步通过全局搜索定位参数名在 DevTools 的Sources面板中按Ctrl Shift F进行全局搜索输入动态参数名比如sign、token、buvid回车后浏览器会在所有加载的 JS 文件中查找包含该关键词的代码。这里有一个关键技巧搜索时不要只搜参数名也要搜参数赋值前后的关联字符串。举个例如果请求参数是{data: xxx, sign: yyy}那在 JS 源码中大概率会出现sign:或sign 或sign这样的片段。搜索时尽量用带引号的完整写法减少匹配到无关内容的情况。3.3 第三步XHR 断点回溯调用栈如果全局搜索找到很多匹配文件看不出哪个是真正的生成位置可以改用XHR/fetch Breakpoints断点。在Sources面板右侧找到XHR/fetch Breakpoints点击加号输入接口 URL 中包含的关键词例如api/list。然后刷新页面浏览器会在发请求之前中断执行。此时点击右侧Call Stack面板从上往下查看调用栈重点关注(anonymous)或某个函数名中出现的 JS 文件。调用栈会把请求发起前的所有 JS 执行步骤列出来你只需要往调用栈下面找通常就能找到参数被拼接的那个函数。3.4 第四步在函数调用处设置断点找到可疑函数后点击对应 JS 文件在函数内部行号处单击设置断点。再次触发请求代码执行到这里就会暂停。此时把鼠标悬停在变量名上可以查看变量的当前值。在Console面板里输入变量名回车可以看到值。使用F10下一步F11进入函数内部。通过这种方式你可以逐行观察sign参数是怎么被计算出来的。这是 JS 逆向最核心的调试手段。下面用一个简化示例来展示这个流程。假设某个接口的 JS 代码经过格式化后长这样// 文件路径/assets/js/chunk-xxxx.js function getSign(params) { var sorted Object.keys(params).sort(); var str ; for (var i 0; i sorted.length; i) { str sorted[i] params[sorted[i]] ; } str key9a5f2c1e; return md5(str); }你在断点处停留时可以在 Console 中执行// 查看当前传入的参数 JSON.stringify(params) // 查看拼接后的字符串 str // 查看最终生成的签名 md5(str)通过这种代入式调试很快就能还原整个动态参数的生成流程。4. 完整实战案例通过浏览器环境模拟生成动态参数下面用一个通用场景来演示动态参数生成的完整链路。我们模拟的是一个需要timestamp和sign两个动态参数的接口sign的生成规则是把参数按字典序拼接后加盐做 MD5。注意以下为教学演示示例目标地址是虚构的只用于演示 JS 逆向的流程。实际项目请以你遇到的站点为主并确保操作合法合规。4.1 创建项目结构先在本地创建一个项目文件夹js_reverse_demo/ ├── main.py # Python 爬虫主程序 ├── sign.js # 从页面提取并整理后的签名生成 JS └── requirements.txt # 依赖清单4.2 整理页面中的 JS 算法假设我们从网站的 JS 文件中还原出了下面的签名算法// 文件路径sign.js function generateSign(params) { // 1. 过滤空值 var filtered {}; for (var key in params) { if (params[key] ! undefined params[key] ! ) { filtered[key] params[key]; } } // 2. 按键名升序排序 var keys Object.keys(filtered).sort(); var arr []; for (var i 0; i keys.length; i) { arr.push(keys[i] filtered[keys[i]]); } // 3. 拼接字符串并加盐 var baseStr arr.join() secreta1b2c3d4e5f6; // 4. MD5 哈希输出 32 位小写字符串 return md5(baseStr); }如果你是在浏览器里调试还有一个更快的方法在 Console 面板直接执行页面加载的generateSign函数验证它对某个输入输出的结果是否符合预期。4.3 在 Node.js 中补齐 MD5 工具函数上面的md5函数不是原生 JS 内置的如果要在 Node 中跑还需要引入一个 MD5 实现。实际开发中你可以把网站的 MD5 函数直接抠出来也可以自己安装js-md5包npm init -y npm install js-md5然后在sign.js里补充引入// 文件路径sign.js const md5 require(js-md5); // ... generateSign 函数定义不变 ... // 导出函数方便外部调用 module.exports { generateSign };4.4 使用 Python 调用 JS 生成签名现在来到 Python 侧。最直接的方式是通过subprocess调起 Node 执行 JS 文件把结果打印出来再读取。这种方式的好处是 JS 执行环境与浏览器一致兼容性最好。# 文件路径main.py import json import subprocess import requests def generate_sign_by_node(params: dict) - str: 通过 Node.js 执行 sign.js 中的 generateSign 函数 js_code f const {{ generateSign }} require(./sign.js); const params {json.dumps(params, ensure_asciiFalse)}; console.log(generateSign(params)); result subprocess.run( [node, -e, js_code], capture_outputTrue, textTrue, encodingutf-8 ) if result.returncode ! 0: raise RuntimeError(fNode 执行失败: {result.stderr}) return result.stdout.strip()然后在主流程里组装请求参数# 文件路径main.py接上面的代码 def fetch_data(): # 准备基础参数 params { keyword: python爬虫, page: 1, pageSize: 20, timestamp: int(__import__(time).time()), } # 生成动态签名 sign generate_sign_by_node(params) params[sign] sign # 发起请求 headers { 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, Content-Type: application/json, Referer: https://example.com/search, } resp requests.post(https://api.example.com/search, jsonparams, headersheaders, timeout10) print(resp.status_code) print(resp.json()) if __name__ __main__: fetch_data()4.5 运行与验证先测试 JS 函数本身是否能正确输出node -e const { generateSign } require(./sign.js); console.log(generateSign({page: 1, keyword: test}))如果 Node 环境没问题它会输出一个 32 位的 MD5 字符串。接着运行 Python 脚本python main.py如果签名生成正确服务器会正常返回 JSON 数据如果签名错误服务器通常会返回sign error、invalid request之类的提示。这个例子展现的是一种通用思路把网站 JS 里的算法提取出来在本地用 Node 或 Python 重新执行从而在每一次请求中动态生成合法参数。5. 另一种思路用浏览器自动化直接拿到动态参数有时候某些 JS 逆向难度极高代码经过了重度混淆、控制流平坦化、正则替换、虚拟机保护短时间无法还原算法。这种情况下可以考虑用浏览器自动化工具来“代执行” JS。5.1 Playwright 示例以 Playwright 为例它的page.evaluate方法可以把任意 JS 代码放到页面上下文中执行从而复用页面加载过的全部 JS 函数。# 文件路径browser_demo.py from playwright.sync_api import sync_playwright def get_sign_from_browser(keyword: str) - str: with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com/search, wait_untilnetworkidle) # 在页面上下文中执行 JS调用页面已有的函数 sign page.evaluate( (keyword) { // 模拟页面内部生成签名的逻辑 const params { keyword: keyword, page: 1 }; return window.generateSign ? window.generateSign(params) : fallback; }, keyword ) browser.close() return sign if __name__ __main__: print(get_sign_from_browser(python爬虫))这种方案的核心优势是不需要完全理解 JS 算法。缺点是执行速度慢需要启动浏览器且会增加资源消耗。在实际工程中一般用于算法复杂度极高、短期无法还原的情况或者用于验证自己还原出的算法是否正确。5.2 何时选择浏览器自动化何时选择本地执行 JS场景推荐方案原因JS 代码轻度混淆能定位到算法本地执行 JSNode速度快适合大规模请求JS 代码重度混淆算法还原成本高浏览器自动化快速上线减少人力投入需要验证自己的算法是否正确浏览器自动化对照对比输出结果是否一致生产环境对请求速度要求高本地执行 JS避免启动浏览器性能更优6. 从实战角度拆解JS 逆向中常见的动态参数类型与应对思路上面完成了整体流程演示。下面再从实战案例角度把动态参数按照“生成方式”做一次分类总结。这会帮助你在面对陌生网站时更快判断应该从哪个方向入手。6.1 时间戳类参数特征参数名通常是timestamp、_t、time、ts值是一串 10 位或 13 位数字。生成方式// 10位秒级时间戳 Math.round(new Date().getTime() / 1000) // 13位毫秒级时间戳 Date.now()应对方案直接在 Python 里生成不需要读 JS。import time timestamp int(time.time()) # 秒级 timestamp_ms int(time.time() * 1000) # 毫秒级6.2 拼接散列类参数特征参数值通常是 32 位或 64 位的十六进制字符串算法的关键词带有md5、sha1、sha256、hash、digest等。生成方式将请求参数、时间戳、固定密钥按某种规则拼接后散列。应对思路在 JS 文件中搜索md5(、sha1(、CryptoJS、js-md5等关键词。找到传入函数的参数逐步向上回溯拼接逻辑。在本地用 Python 的hashlib或 Node 的crypto复现。例如 JavaScript 里是md5(keywordpythonpage1secretabc123)Python 里就可以写import hashlib def make_sign(keyword: str, page: int, secret: str) - str: raw_str fkeyword{keyword}page{page}secret{secret} return hashlib.md5(raw_str.encode(utf-8)).hexdigest()这类参数是最常见的入门练手目标建议优先掌握。6.3 Cookie 动态生成类参数特征每次刷新页面Cookie 中某个字段的值都会变化例如_signature、acw_sc__v2、buvid3等。生成思路这类 Cookie 通常是在页面加载早期的 JS 中生成有时是服务端返回一段 JS 代码浏览器执行后设置 Cookie。应对方案先用抓包工具确认 Cookie 是哪个响应头或哪个 JS 设置的。在 JS 中搜索 Cookie 名定位生成函数。如果 JS 代码需要浏览器环境例如document.cookie可以考虑在 Python 中借助execjs补环境或者用浏览器自动化处理。这里提醒一句Cookie 类动态参数往往与风控系统绑定更深技术难度比普通 sign 参数更高需要掌握补环境技能。6.4 请求头动态参数特征Headers中某个字段例如Authorization、X-Sign、X-Token值是一长串加密字符串。应对思路定位方式与普通参数一致只是在Network面板中要看Request Headers搜索关键词时在 Sources 面板中搜 Header 名称即可。6.5 滑块验证与行为参数特征请求参数中包含captcha、verify、risk、trace等字段值可能是加密的行为轨迹数据。应对思路这类参数涉及浏览器环境检测、鼠标轨迹采集、Canvas 指纹等已经超出了入门范畴。新手不建议一上来就碰先把基础的 sign 参数搞清楚再逐步接触滑块逆向。7. JS 逆向调试的实用技巧与常见问题排查7.1 实用调试技巧技巧一格式化压缩后的 JS 文件大部分网站的 JS 文件都是压缩过的函数名可能是单个字母可读性极差。在 Sources 面板左下角点击Pretty-print花括号图标{}代码会自动格式化。格式化后配合全局搜索定位效率会提高很多。技巧二利用 Console 在断点处执行代码断点暂停时Console 依然可以执行代码而且可以访问当前作用域内的变量。这是逆向过程中最常用的手段。你不需要把所有逻辑一次性看懂只需要在 Console 里不断执行变量名观察每个中间变量的值通常很快就能拼凑出完整逻辑。技巧三Hook 函数定位有时候 JS 里对某个 API 的封装层很多很难直接找到调用的位置。这时可以在 Console 中提前改写关键函数插桩打印调用信息。例如某个参数使用了JSON.stringify你可以先执行(function() { var originalStringify JSON.stringify; JSON.stringify function() { console.log(JSON.stringify called with:, arguments); return originalStringify.apply(this, arguments); }; })();然后在页面里正常触发请求Console 就会打印出所有JSON.stringify的调用参数。这种方法叫做Hook在 JS 逆向中非常实用。技巧四使用 Override 保存修改后的 JS如果需要对 JS 文件做修改例如把某个函数的返回值直接改为固定值可以在 Sources 面板中右键文件选择Override content结合本地文件夹实现文件替换。这样每次刷新页面都会加载修改后的 JS方便验证算法。7.2 常见问题排查表问题现象常见原因解决思路本地执行 JS 报window is not definedJS 依赖浏览器环境在代码中补充window、document等环境变量或用浏览器自动化替代pyexecjs执行 ES6 报语法错误默认执行环境不支持 ES6安装 Node.js 并让pyexecjs使用 Node 环境生成的签名与浏览器不一致拼接顺序、编码格式、大小写不一致在 Node 中打印每一段中间结果与浏览器 Console 对比请求返回 403 或风控提示请求头缺失、IP 被标记、频率过高模拟完整请求头降低请求频率使用代理池全局搜索参数名找不到任何结果参数名被混淆加密搜索参数名的一部分、搜索请求 URL 关键字或改用 XHR 断点回溯调用栈JS 算法里包含atob、Base64嵌套编码多层编码混淆从最内层开始解码先还原内层明文再分析外层逻辑7.3 新手最容易犯的三个错误错误一拿到一个参数就去搜“Python 实现”而不是先分析 JS。很多新手看到sign是 MD5就立刻在 Python 里写 MD5但忽略了拼接规则中的顺序、分隔符、盐值。只有先把 JS 里的完整拼接逻辑搞清楚Python 才能正确模拟。错误二忽略了字符集和编码问题。中文参数在 JS 中可能经过encodeURIComponent在 Python 中则需要使用urllib.parse.quote处理。直接拼接中文生成的签名往往不一致。错误三只关注参数值不关注请求头。很多网站的签名校验不只校验参数内容还会校验User-Agent、Referer、Origin等请求头。即使你签名生成了请求头不完整依然拿不到数据。所以模拟请求时要尽量把浏览器里的请求头带全。8. JS 逆向入门学习路线与工程建议8.1 推荐学习顺序如果完全零基础建议按照下面的路线推进掌握浏览器开发者工具Network、Sources、Console、Application 四个面板的常用功能。掌握 JavaScript 基础语法变量、函数、对象、数组、字符串处理、回调函数。掌握常见加密算法识别MD5、SHA、AES、RSA、Base64能通过特征判断是哪种算法。练习简单站点逆向找一两个技术博客或开源网站练手完成“定位参数 - 还原算法 - Python 模拟”的完整闭环。学习补环境与 Hook 技术当 JS 依赖浏览器环境时学会在本地模拟window、document、navigator等对象。学习浏览器自动化兜底方案Playwright / Selenium 常备用于验证或兜底。8.2 工程化实践建议在实际项目里JS 逆向不应该是一个“一次性脚本”而是需要做成可持续维护的模块。建议从第一天就注意几点。第一把 JS 算法与爬虫逻辑分离。不要在主爬虫文件里写一大段加密逻辑。把sign.js这类文件独立存放Python 侧只负责调用这样算法更新时只需要修改 JS 文件不需要改动整个爬虫主程序。第二增加参数校验日志。在生成签名后建议先把自己的参数和浏览器中的参数做一次对比。可以在代码里加一个 debug 开关打印每一步的中间值方便快速定位问题。第三控制请求频率预留重试机制。即使动态参数生成正确请求频率过高也会触发风控。建议在爬虫中加入随机延迟、请求重试、异常报警机制。第四遵守 robots 协议与网站条款。爬虫技术本身是中性的但使用方式必须合规。用于学习、测试、公开数据采集没有问题但不要对未授权接口发起高频请求不要绕过付费墙、验证码、登录授权获取非公开数据。尤其是涉及用户隐私、付费内容、版权内容时要特别谨慎。第五注意 JS 逆向的维护成本。网站的 JS 算法会随时更新今天还原的签名算法可能过几天就变了。因此JS 逆向不是一劳永逸的方案需要建立持续监控机制例如定时检查页面 JS 文件是否变更。9. 小结与动手实践建议本文从动态参数的概念讲起介绍了动态参数产生的背景梳理了使用 Chrome DevTools 定位 JS 动态参数的完整流程并通过一个可运行的实战案例演示了“提取 JS 算法 - 本地 Node 执行 - Python 模拟请求 - 获取数据”的完整链路。同时还介绍了几类常见的动态参数类型、应对思路、调试技巧和工程建议。对于刚开始接触 JS 逆向的读者下一步可以这样练找一台自己常用的网站打开 DevTools定位一个带签名参数的接口尝试还原它的签名算法。把本文的实战案例代码跑通理解subprocess调 Node 和playwright调页面的两套思路。尝试在不使用浏览器自动化的情况下用 Python 或 Node 生成动态参数再对比浏览器中的真实请求是否一致。JS 逆向是一条需要耐心积累的路遇到难啃的混淆代码不要急着放弃。先从小参数入手一步一步记录中间变量随着经验增加你对动态参数的直觉会越来越准。如果本文对你有帮助可以收藏备用后续我会继续写 JS 逆向中更进阶的内容比如补环境、AST 混淆还原、验证码处理等方向。