JS逆向实战指南:爬虫接口签名参数的定位与还原方法
2026/9/5 23:52:45 网站建设 项目流程

做爬虫开发的同学,通常都会遇到这样一个场景:同一个接口,在浏览器地址栏里访问没有问题,页面上的数据也能正常展示,可一旦换成 Python 的requestshttpx去请求,结果就变成了 403,或者返回一串看不懂的密文。很多人第一反应是“请求头没写全”,于是补 Cookie、加 User-Agent、带上 Referer,但问题依然存在。

出现这种情况,大概率不是请求头的问题,而是服务器在接口层校验了一组动态生成的签名参数。这组参数往往由网页里的 JavaScript 在请求前动态生成,你无法直接在网络请求中硬编码。于是,就需要把前端这段“黑盒逻辑”还原出来,让它也能在你的爬虫脚本里运行。这个分析过程,就是大家常说的 JS 逆向。

JS 逆向并不是一个神秘的黑客技术,它的本质是“前端代码分析和逻辑复现”。浏览器把 JavaScript 代码下载到本地运行,这些代码本身就是可读或半可读的,我们可以通过调试工具追踪某个请求参数是怎么生成的,再把同样的算法用 Node.js 或 Python 实现一遍。下面我会从概念、工具、核心知识到完整案例,一步步带着你走一遍闭环流程。

1. 什么是 JS 逆向?为什么爬虫开发要学它

1.1 从一个签名接口说起

我们先看一个非常典型的签名接口场景:

GET /api/list?key=user-001&timestamp=1719000000000&sign=3fa92b...

服务器并不是只靠key来识别请求,它还会校验timestampsign

  • timestamp用来防止请求被重复使用,或者防止请求过于陈旧;
  • sign是根据某些参数、密钥和时间戳共同计算出来的签名。

在浏览器页面里,这个接口可以正常返回,是因为浏览器先执行了页面里的 JavaScript,通过加密函数算出了正确的sign。而 Python 脚本直接请求时,我们不知道签名算法,所以只能得到一个失败响应。

于是,所谓 JS 逆向,就是回到浏览器中,找到生成sign的 JavaScript 函数,读懂它的算法,然后在自己的程序里复现相同结果。

1.2 JS 逆向的常见工作流程

一次完整的 JS 逆向分析,通常可以拆成四个步骤。

第一步,抓包确认请求参数。在浏览器开发者工具切到 Network 面板,找到目标 XHR/Fetch 请求,观察 URL Query、请求头、请求体中的关键参数,确定什么是动态参数,什么是静态参数。

第二步,定位加密函数。通过关键字搜索或断点调试,在 JavaScript 源码中找到生成动态参数的函数。

第三步,还原完整算法。分析函数用到的原始字符串、密钥、IV、盐值、编码方式,把流程整理成可独立运行的脚本。

第四步,验证与集成。把还原后的签名算法接到 Python 或其他爬虫框架里,校验输出结果是否与浏览器一致。

虽然真实网站里还有压缩、混淆、动态执行、环境检测等更复杂的情况,但底层思路都一样。只要养成“先定位、再分析、后复现”的习惯,很多问题都能迎刃而解。

1.3 合法边界与适用范围

这里必须补充一个重要前提:JS 逆向和技术学习都应该在合法授权范围内进行。

建议的使用场景包括:

  • 分析自己公司或自己开发的网站接口;
  • 在获得目标站点授权的前提下,做自动化测试或数据采集;
  • 本地自建测试服务,用于学习浏览器调试和前端加密原理;
  • 阅读前端开源项目,理解加密方案的代码结构。

不建议对未授权的站点做绕过访问控制、批量抓取敏感信息、破解登录验证等操作。这类行为可能违反网站服务条款,严重的还会触犯法律。本篇文章的所有示例,都建议在本地模拟环境中操作。

2. 环境准备与工具选择

2.1 软件环境

本文的实战会同时使用浏览器、Node.js 和 Python,因此需要提前准备一套基础环境。

工具作用版本建议
Chrome 或 Edge开发者工具、断点调试更新到较新稳定版即可
Node.js运行后端演示服务、还原签名算法建议 Node.js 16 及以上
Python 3编写请求验证脚本建议 Python 3.9 及以上
代码编辑器写项目代码VS Code 或 PyCharm 均可

版本不一定完全一致,只要满足示例中的功能即可。如果本机没有 Node.js,可以去官网下载 LTS 版本,安装完成后在命令行执行node -v,能看到版本号就说明环境正常。

2.2 浏览器开发者工具

Chrome 和 Edge 的开发者工具是整个 JS 逆向分析的“主战场”。

常用的面板有:

  • Elements:查看页面结构,很少直接用在 JS 逆向中;
  • Console:测试表达式、查看输出、手动调用函数;
  • Sources:查看 JavaScript 源码、打断点、跟踪调用栈;
  • Network:查看接口请求、响应、Cookie、Header;
  • Application:查看 localStorage、sessionStorage、Cookie。

真实定位加密逻辑时,最常用的组合是 Network + Sources。Network 负责告诉我们“哪里生成了这个参数”,Sources 负责让我们停留在加密函数内部,观察参数是怎么变换的。

2.3 抓包工具选择

普通 Web 页面请求通常不需要额外抓包工具,浏览器 Network 面板已经足够。但如果是 App 内嵌 WebView、小程序或客户端请求,就需要用到 Fiddler、Charles、Whistle 等中间人代理工具。

这类工具的核心原理是:把手机或客户端的网络流量代理到电脑上,通过安装证书解密 HTTPS 请求,从而看到真实接口。安装证书、设置代理属于常规调试手段,但要注意只在自己设备或已授权环境使用,不能非法窃听他人通信。

如果是初学者,建议先从浏览器开始,不用一上来就折腾抓包证书。

3. 核心知识拆解:加密、定位与调试

3.1 先分清“加密”的类型

JS 逆向中常见的算法可以分成三类:摘要算法、对称加密、非对称加密。很多新手一看到sign就想到 MD5,这是可以理解的,但不同类型的参数判断方式并不一样。

第一类是摘要算法,常见的有 MD5、SHA-1、SHA-256。摘要算法是单向的,只能从原文计算摘要,不能从摘要反推原文。它的特点通常是输出固定长度字符串,比如 32 位的十六进制字符串很可能是 MD5,64 位的可能是 SHA-256。摘要通常用来做签名或内容校验。

第二类是对称加密,常见的是 AES、DES。对称加密的特点是加密和解密使用同一个密钥,所以代码还原时要特别关注密钥、偏移量 IV、分组模式和填充方式。如果你看到一个密文长度规律、密钥是由固定字符串生成,大概率就是 AES。

第三类是非对称加密,常见的是 RSA。加密使用公钥,解密使用私钥。在 Web 端最常见的使用方式是:服务器把 RSA 公钥下发给前端,前端用公钥加密密码,后端再用私钥解密。即使攻击者拿到公钥,也无法直接还原原文。

除此之外,还要注意 Base64 并不是加密算法,它只是编码。很多网站会先做一次 Base64,再拼接参数做摘要。

3.2 快速定位加密入口

拿到一个接口后,想快速找到加密函数,可以优先使用关键字搜索法。

在浏览器开发者工具里按 Ctrl+Shift+F 可以全局搜索当前页面加载的所有脚本内容。建议先搜索以下关键字:

搜索类别建议关键字
参数名sign、signature、token、params、encrypt、key
算法特征md5、sha256、AES、RSA、CryptoJS、crypto
组合规律timestamp、nonce、appId、clientId、secret
请求方式XMLHttpRequest、fetch、axios、ajax

比如一个请求参数叫sign,我们就可以搜索sign =sign:"sign"setSign等写法。压缩后的 JavaScript 代码虽然变量名很短,但字符串和函数名通常还会保留,因此搜索字符串是最高效的定位方式。

如果搜索出来的结果很多,可以再结合 Network 面板的 Initiator 查看是哪个 JS 文件发起的请求。

3.3 断点调试与调用栈分析

找到源码位置后,下一步是打断点看执行过程。

这里介绍三种最常用的断点方式。

第一种是普通代码断点。在 Sources 面板中打开 JS 文件,点击行号即可设置断点。刷新页面或触发按钮后,代码会停在断点处,我们就可以在右侧 Scope 面板查看当前变量值。

第二种是 XHR/fetch 断点。在 Sources 面板右侧找到 XHR/fetch breakpoints,点击加号添加包含关键字的 URL,比如/api/list。当页面发起匹配请求时,浏览器会主动停在发请求之前的代码位置,这样可以非常快地找到网络请求的地点。

第三种是事件监听断点。比如点击按钮后才触发请求,可以在 Sources 面板的事件监听器断点中勾选 click 事件,从而在点击回调内部断下。

分析过程中要重点观察 Call Stack 调用栈。调用栈能告诉我们当前函数是被谁调用的,一层层往上跳,就能找到参数最早从哪里进入。

3.4 从“看到了”到“能复现”

很多人卡在最后一步:函数逻辑看到了,但不知道怎么写成 Python 或独立 Node 脚本。

这里有一个很实用的思路:不要急着用 Python 重写,先用 Node.js 把加密逻辑 1:1 复现。因为网站前端是 JavaScript,很多库在 Node 中可以直接使用,复现成本最低。等 Node 脚本能跑通后,再根据算法类型决定是否改成 Python。

比如网站上用了CryptoJS.SHA256(...),那么在 Node 项目中可以安装crypto-js,调用方式几乎一样。如果网站用了加密库特定封装,也要尽量保持字符编码和参数拼接顺序一致,例如到底是key + timestamp还是timestamp + key,这种顺序问题最容易出错。

复现后,用同一组入参分别运行浏览器和 Node 脚本,对比输出是否一致。如果一致,就说明算法分析正确。

4. 实战案例:本地签名接口的完整逆向流程

下面我们用一套本地环境,演示从搭建目标服务、浏览器观察请求,到 Node 还原签名、Python 请求验证的完整流程。

4.1 准备一个带签名校验的本地服务

先创建一个项目目录,例如js-reverse-demo,项目结构如下:

js-reverse-demo/ ├── backend/ │ └── server.js ├── public/ │ ├── index.html │ └── app.js └── crawler/ ├── sign.js └── request.py

backend目录初始化 npm 项目并安装 Express:

cd js-reverse-demo npm init -y npm install express

然后编写backend/server.js。这个后端服务会校验三个参数:keytimestampsign,其中sign的规则是:

SHA256(key | timestamp | 固定密钥) 的十六进制结果,取前 16 位

完整代码如下:

// 文件路径:backend/server.js const express = require('express'); const crypto = require('crypto'); const path = require('path'); const app = express(); const PORT = 3000; const SECRET_KEY = 'csdn-demo-secret-2025'; function createSign(key, timestamp) { const raw = `${key}|${timestamp}|${SECRET_KEY}`; return crypto .createHash('sha256') .update(raw) .digest('hex') .substring(0, 16); } function checkSign(req, res, next) { const key = req.query.key || ''; const timestamp = req.query.timestamp || ''; const sign = req.query.sign || ''; if (!key || !timestamp || !sign) { res.status(403).json({ code: 403, message: '缺少签名参数' }); return; } const expectSign = createSign(key, timestamp); const now = Date.now(); if (Math.abs(now - Number(timestamp)) > 5 * 60 * 1000) { res.status(403).json({ code: 403, message: '签名过期' }); return; } if (sign !== expectSign) { res.status(403).json({ code: 403, message: '签名校验失败' }); return; } next(); } app.get('/api/list', checkSign, (req, res) => { res.json({ code: 0, message: 'success', data: [ { id: 1, title: 'JS逆向示例-01', author: 'CSDN' }, { id: 2, title: 'JS逆向示例-02', author: 'CSDN' } ] }); }); app.use(express.static(path.join(__dirname, '../public'))); app.listen(PORT, () => { console.log(`demo server run at http://localhost:${PORT}`); });

这个服务演示的是签名参数校验。SECRET_KEY相当于服务端与前端约定的“盐值”,实际项目中会放在服务端配置里,而不会像演示代码这样直接出现在前端。

启动服务:

node backend/server.js

浏览器访问http://localhost:3000,如果直接请求/api/list是不带签名的,会返回 403。

4.2 前端页面中的加密逻辑

为了让页面正常调用接口,我们需要在public/index.html中加载app.js,并在app.js中动态生成签名。

public/index.html代码如下:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>签名接口演示</title> </head> <body> <h2>签名接口演示</h2> <pre id="result">正在加载...</pre> <script src="/app.js"></script> </body> </html>

public/app.js代码如下:

// 文件路径:public/app.js async function createSign(key, timestamp) { // 这里模拟线上前端代码中的加密函数 const raw = `${key}|${timestamp}|csdn-demo-secret-2025`; const buffer = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode(raw) ); const hash = Array.from(new Uint8Array(buffer)) .map((b) => b.toString(16).padStart(2, '0')) .join(''); return hash.substring(0, 16); } async function loadData() { const key = 'demo-user'; const timestamp = String(Date.now()); const sign = await createSign(key, timestamp); const api = `/api/list?key=${key}&timestamp=${timestamp}&sign=${sign}`; const response = await fetch(api); const result = await response.json(); document.getElementById('result').innerText = JSON.stringify(result, null, 2); } loadData();

刷新页面后,页面会正常显示接口返回的数据。此时,我们就可以把自己当成一个“只拿到了网页源码,但不知道签名算法”的开发者,开始逆向分析。

4.3 从浏览器里定位签名算法

打开 Chrome 开发者工具,切到 Network 面板,刷新页面,可以看到一个/api/list请求。

点击这个请求,在 Payload 或 Headers 面板中能看到:

key=demo-user timestamp=1719000000000 sign=2fa5b19c1c1e2a3d

其中timestamp是不断变化的,sign也随着 timestamp 变化。这里的key是固定的,所以重点怀疑对象就是sign

然后回到 Sources 面板,在左侧文件列表中找到app.js,按 Ctrl+F 搜索sign。很快能看到const sign = await createSign(key, timestamp)这一行。

这里就是关键入口:原来签名由createSign(key, timestamp)函数生成。继续点进这个函数,就能看到它把keytimestamp和固定字符串拼起来,然后使用 Web Crypto API 做 SHA-256 摘要,取前 16 位。

如果遇到的是压缩混淆代码,搜索sign会看到一大段压缩后的 JS,分析起来会更困难。这时候可以给生成签名的那一行打上断点,刷新页面,当代码暂停时,在 Scope 面板中查看参数和函数返回值,也能快速确认算法细节。

4.4 使用 Node.js 还原签名

在云端分析前端逻辑后,我们可以把它翻译成 Node.js 脚本。这里注意:浏览器端使用的是 Web Crypto API,Node.js 端使用内置的crypto模块即可,两者结果一致。

创建crawler/sign.js

// 文件路径:crawler/sign.js const crypto = require('crypto'); const SECRET_KEY = 'csdn-demo-secret-2025'; function createSign(key, timestamp) { const raw = `${key}|${timestamp}|${SECRET_KEY}`; return crypto .createHash('sha256') .update(raw) .digest('hex') .substring(0, 16); } const key = process.argv[2]; const timestamp = process.argv[3]; if (!key || !timestamp) { console.error('用法: node sign.js <key> <timestamp>'); process.exit(1); } console.log(createSign(key, timestamp));

执行下面的命令测试:

node crawler/sign.js demo-user 1719000000000

输出结果是一串 16 位十六进制字符串。把这个签名与浏览器 Network 面板里的对比,只要 timestamp 相同,签名就应该完全一致。

4.5 编写 Python 请求脚本验证

最后,我们用 Python 脚本请求本地接口。为了不依赖第三方库,这里使用标准库urllib,通过subprocess调用 Node 脚本生成签名。

创建crawler/request.py

# 文件路径:crawler/request.py import json import subprocess import time import urllib.parse from urllib.request import Request, urlopen API_URL = "http://localhost:3000/api/list" def get_sign(key: str, timestamp: int) -> str: result = subprocess.run( ["node", "sign.js", key, str(timestamp)], capture_output=True, text=True, check=True, ) return result.stdout.strip() def main(): key = "demo-user" timestamp = int(time.time() * 1000) sign = get_sign(key, timestamp) params = urllib.parse.urlencode({ "key": key, "timestamp": timestamp, "sign": sign, }) url = f"{API_URL}?{params}" req = Request(url, headers={"User-Agent": "Mozilla/5.0"}) with urlopen(req, timeout=5) as resp: data = json.load(resp) print(json.dumps(data, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()

crawler目录下运行:

cd crawler python request.py

正常输出如下:

{ "code": 0, "message": "success", "data": [ { "id": 1, "title": "JS逆向示例-01", "author": "CSDN" }, { "id": 2, "title": "JS逆向示例-02", "author": "CSDN" } ] }

到这里,一个最简单的签名参数逆向闭环就完成了。从浏览器定位到参数生成函数,再用 Node.js 还原算法,最后集成到 Python 请求中拿到数据。

5. 常见问题与排查思路

JS 逆向学习和实战过程中,大家经常会在某个步骤卡住。下面整理了一些高频问题。

问题现象常见原因解决思路
找不到生成 sign 的 JS 文件源码被压缩合并,或搜索词不对用 Initiator 查看调用栈,换更多关键字搜索
在接口请求中看不到加密参数参数被放在 Cookie 或 Header 中打开 Network 请求详情,查看所有请求头
Python 请求得到签名过期timestamp 是字符串拼接,类型或时区不对用毫秒时间戳,确保与浏览器一致
浏览器加密结果和 Node 不一致参数拼接顺序不同,或文本编码不同用相同入参逐字比对拼接前的原始字符串
断点一打就断,但看不到函数断点设在压缩代码错误行在右侧 Call Stack 中向上层函数跳转
Node 中出现 window 未定义前端代码依赖浏览器环境用 jsdom 模拟 window 对象,或把纯算法抽离
Python 脚本运行只显示 Process finished with exit code 0没有调用 main 入口或代码没有打印检查是否写了if __name__ == "__main__",并确保主流程被调用

其中“Python 脚本只显示 exit code 0”是一个很典型的新手问题。很多时候代码里只是定义了函数,却没有在底部调用,或者使用了异步请求但没有等待任务完成。最简单的排查方法是在代码入口加一行打印,确认程序是否真的执行到了目标位置。

另外,签名参数分析出来后,运行结果仍然不一致,需要重点检查字符编码。JavaScript 默认使用 UTF-16 的部分场景可能导致 String 长度与 Python 不一致,尤其是包含中文、特殊符号时。建议在调试阶段,把原始拼接字符串打印出来,再用 Python 完全复制一遍,逐字符核对。

6. 工程化与最佳实践

分析出签名算法只是开始,真正把它用于实际项目时,还需要考虑几个工程问题。

6.1 稳定性与可维护性

签名算法经常更新,如果只是把算法硬编码在爬虫脚本里,一升级就得重新分析。更建议的做法是:

  • 把签名生成逻辑单独封装成一个模块或独立服务;
  • 只暴露输入参数和输出结果,不与其他业务代码耦合;
  • 在签名算法变更时,通过日志和报警及时发现问题;
  • 使用版本管理工具保存每次还原后的签名模块,方便回滚。

例如,你可以把 Node 签名脚本包装成一个 HTTP 微服务,Python 爬虫请求这个微服务获取签名,签名逻辑更新时不用重新发布爬虫。

6.2 请求频率与资源控制

即使接口合法可访问,大量高频请求也会给目标服务造成压力。实际项目中应控制并发数,建议增加延迟、重试退避机制和请求频率限制。

一个比较稳妥的爬虫策略是:先用小批量测试接口稳定性,再按固定频率逐步增加请求量;遇到 429、403 等响应时,立即降低频率,而不是无限重试。日志中要记录响应状态码、耗时和失败原因,方便事后分析。

如果你的爬虫部署在多台服务器上,还要考虑分布式限流。多节点同时高频请求,很容易触发服务端风控,导致 IP 被封禁。加代理是风险较高的操作,不建议在未授权环境中贸然使用。

6.3 合规与防守视角

前端加密无法做到绝对安全,服务器永远不应该把核心密钥放在浏览器端代码里。如果只是用前端 JS 做“防君子不防小人”的签名,那么整个安全方案还需要结合服务端权限校验、频率限制、账号体系、设备指纹等共同完成。

对 JS 逆向学习者来说,最好的心态是:把它理解成“前端代码可观测性分析与调试技巧”,而不是“绕过安全限制的攻击手段”。所有练习都应该在本地环境、自己开发的系统或获得授权的项目中完成。

6.4 更进一步的学习方向

如果想继续深入,有两个方向值得投入。

第一个方向是混淆对抗。真实网站会使用 JavaScript 混淆工具把函数名、变量名替换为无意义字符,甚至加入控制流平坦化、字符串加密、虚拟机保护等方案。想还原这种代码,需要学习 AST 抽象语法树,可以借助 Babel 或在线 AST 工具分析代码结构。

第二个方向是浏览器环境补充。很多前端签名算法会读取windownavigatordocument等浏览器环境变量,一旦脱离浏览器运行就会报错。这种情况下,要么使用 Puppeteer/Playwright 这类无头浏览器来模拟真实浏览器,要么在 Node 里自己补齐环境对象。

这两个方向都属于 JS 逆向的高阶部分,建议先掌握本篇文章的基础定位与还原思路,再逐步深入。

7. 总结与后续学习路线

本篇文章从一个签名接口场景出发,解释了 JS 逆向的核心含义,带你走完了“抓包观察、源码定位、断点调试、Node 还原、Python 验证”的完整流程。如果你能独立跑通本地示例,说明你已经掌握了 JS 逆向分析最基本的方法论。

接下来的学习路线可以按这个顺序推进:

  • 第一周:熟悉 Chrome DevTools 的 Network、Sources、Console,练习分析公开网页的简单请求参数;
  • 第二周:掌握 MD5、SHA、Base64、AES、RSA 算法的识别与 Node.js 复现;
  • 第三周:学习 JavaScript 语法进阶,包括闭包、原型链、异步流程、事件循环;
  • 第四周:学习 AST 与 JavaScript 混淆还原,尝试理解压缩代码和反调试逻辑。

每学一个新知识点,都建议自己搭建一个带加密逻辑的本地页面来做实验。你可以改造本文的 Express 服务,加入 AES 加密、动态混淆、环境校验等条件,然后模拟逆向过程。这样练习得越多,后续遇到真实项目时越有把握。

JS 逆向本质上是“基于浏览器可观测性做逻辑分析”的能力。真正的价值不在于突破某个网站的防护,而在于当你面对一段陌生的、混淆的、压缩的前端代码时,能够快速理解它做了什么,并且有能力用工程化方式复现和沉淀。希望这篇文章能帮你把这个基本功打牢。如果有帮助,也欢迎收藏备用,避免在后面的学习和项目中再走弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询