在 Python 网络爬虫开发中,动态 Token 和加密参数是网站反爬机制的 "核心防线"。无论是电商平台的商品价格、社交平台的接口数据,还是各类 API 的权限验证,都频繁使用这类技术阻止非法爬取。对于爬虫工程师而言,掌握动态 Token 与加密参数的逆向分析能力,是突破反爬限制、获取目标数据的关键。本文将从原理拆解、工具准备、逆向流程、实战案例四个维度,带你全面掌握这一核心技能。
一、核心概念:动态 Token 与加密参数是什么?
在开始逆向之前,我们首先要明确两个核心概念,理解其作用和本质:
1. 动态 Token:接口请求的 "临时通行证"
- 定义:Token(令牌)是服务器生成的一串加密字符串,作为客户端与服务器通信的身份验证凭证。动态 Token 意味着每次请求或每隔一定时间,Token 会动态变化,无法通过固定值复用。
- 常见类型 :
- JWT(JSON Web Token):由 Header、Payload、Signature 三部分组成,常用于前后端分离项目的身份验证;
- 时间戳 + 随机字符串组合 Token:如
token=1695834256_7a5f9c3d2b8e10,通过时间戳保证时效性; - 服务器会话 Token:基于用户登录状态生成,存储在 Cookie 或 LocalStorage 中,超时自动失效。
- 作用:防止请求伪造、验证请求合法性、限制接口访问频率。
2. 加密参数:隐藏关键信息的 "密码锁"
- 定义:加密参数是客户端将原始数据(如用户 ID、请求参数、设备信息等)通过特定加密算法处理后,传递给服务器的参数。
- 常见加密算法 :
- 对称加密:AES、DES、3DES(加密解密使用同一密钥);
- 非对称加密:RSA(公钥加密、私钥解密,常用于密钥交换);
- 哈希算法:MD5、SHA-1、SHA-256(不可逆,常用于数据校验);
- 自定义加密:基于基础算法的二次封装(如加盐哈希、字符移位、Base64 编码 + 加密组合)。
- 作用:隐藏敏感数据、防止参数篡改、增加逆向难度。
二、逆向准备:必备工具清单
工欲善其事,必先利其器。逆向分析需要借助专业工具捕获请求、调试代码、破解加密逻辑,以下是核心工具推荐:
1. 网络请求捕获工具
- Chrome DevTools(F12) :
- 核心功能:Network 面板捕获接口请求,查看 Request Headers、Form Data/Payload、Response 数据;
- 关键用法:筛选 "XHR/Fetch" 类型请求,开启 "Preserve log" 保留历史请求,通过 "Initiator" 定位请求触发的 JavaScript 文件。
- Fiddler :
- 核心功能:全局捕获 HTTP/HTTPS 请求,支持断点调试、请求重放、数据篡改;
- 适用场景:需要捕获桌面应用、移动端 APP 请求时(需配置代理和证书)。
- Mitmproxy :
- 核心功能:命令行版抓包工具,支持 Python 脚本扩展,可自动化处理请求(如批量修改参数、保存数据);
- 适用场景:高级爬虫工程师,需自定义抓包逻辑时使用。
2. JavaScript 调试工具
- Chrome DevTools Sources 面板 :
- 核心功能:查看网站所有 JavaScript 文件,设置断点、单步调试、查看变量值;
- 关键用法:通过 "Search" 搜索加密参数名(如 "token""sign""encryptData")定位加密函数;使用 "Scope" 面板查看函数执行时的局部变量和全局变量。
- VS Code + 调试插件 :
- 核心功能:将网站 JavaScript 文件保存到本地,通过 VS Code 调试器单步跟踪,支持更灵活的代码分析;
- 适用场景:加密逻辑复杂,需要逐行分析代码时。
3. 加密算法辅助工具
- CryptoJS:JavaScript 加密库,支持 AES、DES、MD5 等常见算法,可用于验证逆向的加密逻辑是否正确;
- Online Crypto Tools :在线加密解密工具(如站长工具、Base64.cn),快速验证 MD5、Base64、AES 等算法结果;
- Python 加密库:requests(请求接口)、pycryptodome(AES/RSA 加密解密)、hashlib(MD5/SHA 哈希)、base64(编码解码),用于将逆向得到的逻辑用 Python 复现。
4. 其他辅助工具
- Node.js:运行 JavaScript 代码,验证加密函数的正确性(避免 Python 与 JavaScript 语法差异导致的问题);
- JsonFormatter:在线 JSON 格式化工具,便于查看接口返回的 JSON 数据;
- RegExr:正则表达式测试工具,用于提取 JavaScript 中的密钥、加密逻辑片段。
三、逆向全流程:从捕获到复现(通用步骤)
动态 Token 和加密参数的逆向分析遵循 "定位→调试→破解→复现" 的核心流程,以下是详细步骤,适用于绝大多数网站:
步骤 1:定位目标接口与加密参数
首先需要明确爬取的目标接口,以及接口中包含的动态 Token 或加密参数。
- 打开目标网站,触发数据加载(如点击按钮、滚动页面);
- 打开 Chrome DevTools(F12)→ Network → 筛选 "XHR/Fetch",找到返回目标数据的接口;
- 查看接口的 Request 信息:
- 若为 GET 请求,加密参数可能在 URL 中(如
https://api.example.com/data?token=xxx&sign=yyy); - 若为 POST 请求,加密参数可能在 Form Data(表单提交)或 Request Payload(JSON 格式)中;
- 若为 GET 请求,加密参数可能在 URL 中(如
- 记录加密参数名(如 token、sign、nonce、encryptData),以及是否有固定参数(如 appId、version)。
示例 :某接口 POST 请求的 Payload 如下,其中token和sign为加密参数,timestamp为时间戳:
json
{
"username": "test",
"password": "123456",
"timestamp": 1695834256,
"token": "a7f9d2b4e83c0165978d3f2b1e4c6a89",
"sign": "3e5f7d9b1c2a4e6f8d0b2c4a6e8f0d1b2c3a4e5f6"
}
步骤 2:查找加密参数的生成位置
通过接口的 "发起者" 或搜索功能,定位加密参数的生成代码:
- 在 Chrome DevTools 的 Network 面板中,找到目标接口,点击 "Initiator"(发起者),查看触发请求的 JavaScript 文件和行号;
- 若 "Initiator" 无法直接定位,切换到 Sources 面板,点击 "Search"(快捷键 Ctrl+Shift+F),输入加密参数名(如
token、sign),搜索所有相关的 JavaScript 文件; - 分析搜索结果,重点关注包含参数赋值、函数调用的代码(如
var token = generateToken();、var sign = getSign(params);); - 找到生成加密参数的核心函数(如
generateToken()、getSign()),这是逆向的关键。
技巧 :若搜索结果过多,可结合接口的请求方式(如 POST)、参数上下文(如timestamp常与sign一起生成)缩小范围。
步骤 3:调试加密函数,破解逻辑
找到加密函数后,通过断点调试分析函数的输入、处理过程和输出,破解加密逻辑:
- 在加密函数的关键位置设置断点(点击代码行号前的空白处,出现蓝色标记);
- 重新触发接口请求(如刷新页面、点击按钮),代码执行到断点时会暂停;
- 利用 Sources 面板的调试工具分析:
- Watch :添加变量或表达式(如
params、key),实时查看其值; - Scope :查看当前函数的局部变量(Local)、全局变量(Global),重点关注密钥(如
key = "abc123xyz")、加密算法(如AES、MD5); - Step Over(F10):单步执行代码,观察变量的变化;
- Step Into(F11) :进入函数内部,查看嵌套调用的逻辑(如
encryptAES()的实现);
- Watch :添加变量或表达式(如
- 记录关键信息:
- 加密算法(如 AES-128-CBC);
- 密钥(key)、偏移量(iv,AES-CBC 模式需用到);
- 数据处理方式(如参数排序、拼接、加盐);
- 输出格式(如 Base64 编码、十六进制字符串)。
示例 :调试发现sign的生成逻辑为:
- 将请求参数(
username、password、timestamp)按字典序排序; - 拼接为字符串:
password=123456×tamp=1695834256&username=test; - 拼接固定密钥:
password=123456×tamp=1695834256&username=test&key=abc123xyz; - 对拼接后的字符串进行 SHA-256 哈希,得到
sign值。
步骤 4:用 Python 复现加密逻辑
破解加密逻辑后,使用 Python 对应的加密库复现生成过程,确保生成的参数能通过服务器验证:
- 根据加密算法选择对应的 Python 库(如 AES 用 pycryptodome,SHA-256 用 hashlib);
- 严格按照调试得到的逻辑编写代码(注意参数排序、拼接方式、编码格式,如 UTF-8);
- 验证结果:将 Python 生成的参数与浏览器中捕获的参数对比,若一致则说明复现成功;
- 处理动态 Token 的特殊情况:
- 若 Token 通过接口获取(如登录接口返回
access_token),则先请求 Token 接口,再将 Token 带入目标接口; - 若 Token 基于时间戳、设备指纹生成,需在 Python 中模拟对应的生成逻辑(如获取当前时间戳、生成随机字符串)。
- 若 Token 通过接口获取(如登录接口返回
示例代码(复现 SHA-256 签名逻辑):
python
import hashlib
import urllib.parse
def generate_sign(params, secret_key):
# 1. 按字典序排序参数
sorted_params = sorted(params.items(), key=lambda x: x[0])
# 2. 拼接为URL编码格式的字符串
encoded_params = urllib.parse.urlencode(sorted_params)
# 3. 拼接密钥
sign_str = f"{encoded_params}&key={secret_key}"
# 4. SHA-256哈希
sha256 = hashlib.sha256()
sha256.update(sign_str.encode("utf-8"))
return sha256.hexdigest()
# 测试
params = {
"username": "test",
"password": "123456",
"timestamp": 1695834256
}
secret_key = "abc123xyz"
sign = generate_sign(params, secret_key)
print("生成的sign:", sign)
# 输出应与浏览器中捕获的sign一致
步骤 5:集成到爬虫,测试验证
将复现的加密逻辑集成到爬虫代码中,发起请求并验证是否能成功获取数据:
- 构造完整的请求参数(包含动态 Token、加密参数、其他必要参数);
- 设置正确的请求头(如 User-Agent、Referer、Cookie,需与浏览器一致);
- 发起请求,查看响应状态码和响应数据:
- 若状态码为 200 且返回目标数据,说明逆向成功;
- 若状态码为 401(未授权)、403(禁止访问),可能是 Token 过期、签名错误或请求头不完整,需重新检查加密逻辑和请求配置;
- 处理异常情况:如 Token 有效期较短,需定时刷新;加密参数变化时,需重新逆向更新逻辑。
四、实战案例:破解某电商平台动态 Token 与 AES 加密参数
以下以某电商平台的商品价格接口为例,完整演示逆向流程:
1. 场景分析
目标接口:https://api.shop.com/api/goods/price(POST)请求 Payload:
json
{
"goodsId": "123456",
"timestamp": 1695834567,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"encryptData": "f8d2b4e6a8c013579f2d4b6a8c0e2f4a..."
}
其中token为 JWT 令牌,encryptData为 AES 加密的商品 ID 和时间戳。
2. 逆向过程
步骤 1:定位加密函数
- 搜索
encryptData,找到生成代码:var encryptData = aesEncrypt(JSON.stringify({goodsId: goodsId, timestamp: timestamp})); - 定位
aesEncrypt函数,发现其定义在utils.js文件中:
javascript
function aesEncrypt(data) {
var key = CryptoJS.enc.Utf8.parse("abcdef1234567890"); // 密钥(16位)
var iv = CryptoJS.enc.Utf8.parse("0987654321fedcba"); // 偏移量(16位)
var encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}
步骤 2:破解 JWT Token
- 搜索
token,发现其通过登录接口/api/user/login返回,有效期 2 小时; - JWT 结构解析:Header(算法:HS256)、Payload(包含 userId、exp 过期时间)、Signature(密钥:
shop_secret_key)。
步骤 3:Python 复现
python
import requests
import hashlib
import base64
import json
from datetime import datetime, timedelta
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
# 1. JWT Token生成(模拟登录后获取,此处直接生成)
def generate_jwt(user_id, secret_key):
# Header
header = {"alg": "HS256", "typ": "JWT"}
header_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=")
# Payload(exp为过期时间,当前时间+2小时)
exp = int((datetime.now() + timedelta(hours=2)).timestamp())
payload = {"userId": user_id, "exp": exp}
payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
# Signature
signature = hashlib.sha256(f"{header_b64}.{payload_b64}".encode() + secret_key.encode()).digest()
signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip("=")
return f"{header_b64}.{payload_b64}.{signature_b64}"
# 2. AES加密(encryptData生成)
def aes_encrypt(data, key, iv):
key_bytes = key.encode("utf-8")
iv_bytes = iv.encode("utf-8")
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
encrypted_bytes = cipher.encrypt(pad(data.encode("utf-8"), AES.block_size))
return base64.b64encode(encrypted_bytes).decode()
# 3. 爬虫主逻辑
def crawl_goods_price(goods_id):
# 配置信息(从逆向中获取)
secret_key_jwt = "shop_secret_key"
aes_key = "abcdef1234567890"
aes_iv = "0987654321fedcba"
user_id = "789" # 模拟登录后的用户ID
# 生成参数
timestamp = int(datetime.now().timestamp())
token = generate_jwt(user_id, secret_key_jwt)
encrypt_data = aes_encrypt(json.dumps({"goodsId": goods_id, "timestamp": timestamp}), aes_key, aes_iv)
# 构造请求
url = "https://api.shop.com/api/goods/price"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
"Content-Type": "application/json"
}
data = {
"goodsId": goods_id,
"timestamp": timestamp,
"token": token,
"encryptData": encrypt_data
}
# 发起请求
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
return response.json()
else:
return f"请求失败:{response.status_code}"
# 测试
if __name__ == "__main__":
result = crawl_goods_price("123456")
print("商品价格数据:", result)
3. 验证结果
运行代码后,若返回如下响应,说明逆向成功:
json
{
"code": 200,
"msg": "success",
"data": {
"goodsId": "123456",
"price": 99.9,
"stock": 1000
}
}
五、常见问题与应对技巧
在逆向过程中,经常会遇到各种反制措施,以下是高频问题及解决方案:
1. JavaScript 代码混淆(变量名加密、代码压缩)
- 现象 :加密函数的变量名是乱码(如
a、b、_0x123456),代码无换行、无注释,难以阅读; - 应对技巧 :
- 使用 Chrome DevTools 的 "Pretty Print"({} 按钮)格式化代码,自动换行和缩进;
- 重命名变量和函数(右键变量名→Rename),将乱码变量改为有意义的名称(如
key、iv); - 借助在线反混淆工具(如jsnice.org)还原变量名和代码结构。
2. 密钥隐藏(动态生成密钥、从服务器获取密钥)
- 现象:调试时未找到固定密钥,密钥通过其他接口请求、设备指纹生成或藏在 DOM 元素中;
- 应对技巧 :
- 搜索密钥相关的关键词(如
key、secret、token),查看是否有接口返回密钥; - 检查 DOM 元素(Sources→Elements),是否有隐藏的
meta标签或script标签存储密钥; - 若密钥动态生成(如基于设备 ID),在 Python 中模拟设备 ID 的生成逻辑。
- 搜索密钥相关的关键词(如
3. 调试断点失效(代码动态加载、反调试)
- 现象:设置断点后,代码执行时未暂停,或出现 "Debugger statement detected" 提示;
- 应对技巧 :
- 禁用浏览器的反调试:在 Chrome DevTools→Settings→Debugger→取消勾选 "Disable JavaScript breakpoint debugging";
- 对于动态加载的代码(如通过
eval、document.write加载),在代码执行前设置 "XHR Breakpoints"(拦截 JS 文件请求); - 移除代码中的
debugger语句(搜索debugger,替换为空字符串)。
4. 加密参数频繁变化(服务器更新加密逻辑)
- 现象:之前复现的代码突然失效,接口返回 403 或参数错误;
- 应对技巧 :
- 重新捕获请求,对比新旧参数的差异;
- 重点查看加密函数是否有修改(如密钥变更、算法调整);
- 编写自适应代码,预留加密逻辑的扩展接口(如将密钥、算法参数配置在外部文件中)。
六、合规与伦理提醒
逆向工程是一把 "双刃剑",在使用该技术时,必须遵守以下原则:
- 合法合规:仅对公开可访问的网站进行逆向,不得破解付费内容、隐私数据或涉及商业机密的系统;
- 尊重 robots.txt 协议:虽然动态 Token 反爬通常不遵循 robots.txt,但应避免对网站造成服务器压力(如控制请求频率);
- 不用于恶意用途:不得利用逆向得到的逻辑进行刷量、爬虫攻击、数据窃取等违法活动;
- 保护知识产权:网站的加密逻辑属于知识产权,不得将逆向得到的代码用于商业盈利或公开传播。
七、总结
动态 Token 与加密参数的逆向分析,核心是 "定位 - 调试 - 复现" 的闭环流程。掌握这一技能,需要熟悉 JavaScript 代码结构、常见加密算法原理和调试工具的使用,同时具备耐心和逻辑分析能力。随着网站反爬技术的不断升级,逆向过程可能会遇到各种挑战,但只要掌握核心方法,不断积累实战经验,就能突破大多数反爬限制。