想要一个关于爬虫JS逆向的详细教程。这是一个很专业的技术需求,最近有个可能是正在学习爬虫技术的开发者或者数据分析师,遇到了需要破解JavaScript加密的反爬机制的问题,想让我出一期实战教程,话不多说,开干。
以下是我整理的一份系统化的爬虫JS逆向教程,结合实战技巧与工具使用,能有效的突破前端加密参数限制。内容涵盖基础原理、调试方法、扣代码技巧及高效解决方案:
一、JS逆向核心流程
寻找加密入口
- Network分析:捕获目标请求(XHR/fetch),定位加密参数(如
token
、sign
、xyz
) - 全局搜索:在Sources面板按
Ctrl+Shift+F
搜索参数名(如"nonce"
、password:
) - 调用栈追踪:通过Network请求的
Initiator
标签回溯加密逻辑触发点
- Network分析:捕获目标请求(XHR/fetch),定位加密参数(如
调试分析技巧
- 代码格式化:点击Sources面板左下角
{}
美化压缩代码 - 断点类型:
- XHR断点:针对特定URL拦截请求
- 事件断点:监听
click
、submit
等DOM事件 - 条件断点:在加密函数内设置
Conditional Breakpoint
- Scope面板:查看局部变量、闭包域中的关键值(如密钥、盐值)
- 代码格式化:点击Sources面板左下角
模拟执行方案
方法 适用场景 工具示例 扣JS代码 逻辑简单、无环境依赖 execjs
(Python调用)RPC通信 复杂混淆/浏览器环境依赖 Sekiro
无头浏览器 需完整渲染流程 PlayWright
/Selenium
二、实战案例解析(以汽车之家登录为例)
目标定位
登录请求中
pwd
参数为MD5加密:# 原始请求示例 FormData: {username: "test", pwd: "d6d7d5a8c4e6e7b8"}
逆向步骤:
步骤1:在Sources搜索
pwd
,定位到login.js
文件步骤2:发现关键函数
hex_md5(s)
,其结构如下:function hex_md5(s) { return binl2hex(core_md5(str2binl(s), s.length * chrsz)); } //
步骤3:扣取完整MD5相关函数(
core_md5
,binl2hex
,str2binl
)步骤4:Python通过
execjs
调用:import execjs with open('qichezhijia.js', 'r', encoding='utf-8') as f: js_code = f.read() ctx = execjs.compile(js_code) encrypted_pwd = ctx.call('hex_md5', '123456') # 输出:d6d7d5a8c4e6e7b8
三、高级技巧
Hook拦截
使用Tampermonkey注入脚本监听关键函数(如
btoa
、encrypt
):// Hook Base64示例 (function() { var originBtoa = window.btoa; window.btoa = function(data) { console.log("Base64 input:", data); return originBtoa(data); } })();
反混淆方案
- 控制流平坦化:使用AST还原工具(如
babel
) - 字符串阵列化:动态执行提取真实字符串
- 控制流平坦化:使用AST还原工具(如
Overrides覆盖
- Chrome的Local Overrides功能替换线上JS文件
- 注入调试代码或修复环境检测(如补全
window
对象)
四、常见加密类型与对策
- 哈希算法(MD5/SHA1):扣取标准算法实现(如本文MD5案例)
- RSA加密:提取公钥 + 使用
rsa
库(苏宁易购登录案例) - 动态参数:
- 时间戳:同步生成
- 随机数:
Math.random()
重放或固定盐值
- 自定义编码:Base64变异/移位编码 → Hook
charCodeAt
分析
五、注意事项
- 合法合规:遵守
robots.txt
,避免高频请求 - 更新维护:网站JS变动时需重新分析
- 性能权衡:
- 简单加密 → 扣代码(高效)
- 复杂方案 → RPC/无头浏览器(稳定)
通过上面教程,我们可掌握从基础断点调试到复杂RPC调用的全链路JS逆向技能。遇到具体网站难题时,建议结合Chrome DevTools动态分析快速定位突破点。
具体实战代码示例我公布在下面。
以下是一个完整的JS逆向爬虫实战代码示例,以汽车之家登录密码加密为例,演示如何逆向分析并实现自动化登录:
1. 目标分析(浏览器开发者工具操作)
在汽车之家登录页面:
打开Chrome开发者工具(F12)
输入用户名/密码点击登录
在Network面板查看登录请求,发现密码字段为加密值:
{ "username": "test@example.com", "pwd": "d6d7d5a8c4e6e7b8", // 123456的加密结果 "remember": 1 }
2. JS逆向代码实现
步骤1:提取加密JS代码
在Sources面板找到login.js
,定位并提取MD5加密函数:
// 保存为 autohome_md5.js
function hex_md5(s) {
return binl2hex(core_md5(str2binl(s), s.length * chrsz));
}
function core_md5(x, len) {
/* 此处省略完整MD5算法实现,实际需扣取完整200行代码 */
}
function str2binl(str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz)
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32);
return bin;
}
function binl2hex(binarray) {
var hex_tab = "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
}
return str;
}
var chrsz = 8; // 字符位数 (8-bit)
步骤2:Python爬虫实现
import execjs
import requests
from urllib.parse import urlencode
class AutoHomeSpider:
def __init__(self):
# 加载JS加密代码
with open('autohome_md5.js', 'r', encoding='utf-8') as f:
self.js_code = f.read()
self.ctx = execjs.compile(self.js_code)
self.session = requests.Session()
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
'Origin': 'https://account.autohome.com.cn'
}
def encrypt_password(self, password):
"""JS逆向加密密码"""
return self.ctx.call('hex_md5', password)
def login(self, username, password):
"""执行登录操作"""
login_url = 'https://account.autohome.com.cn/api/login'
# 加密密码
encrypted_pwd = self.encrypt_password(password)
# 构造表单数据
form_data = {
'username': username,
'pwd': encrypted_pwd,
'remember': 1,
'vcode': '',
'redirect': 'https://www.autohome.com.cn/'
}
# 发送登录请求
response = self.session.post(
url=login_url,
data=form_data,
headers=self.headers
)
# 检查登录结果
result = response.json()
if result['returncode'] == 0:
print(f"登录成功! 用户名: {username}")
print(f"Cookies: {self.session.cookies.get_dict()}")
return True
else:
print(f"登录失败: {result['message']}")
return False
def get_user_info(self):
"""获取用户信息(需登录后)"""
info_url = 'https://account.autohome.com.cn/api/GetUserInfo'
response = self.session.get(info_url, headers=self.headers)
return response.json()
if __name__ == "__main__":
spider = AutoHomeSpider()
# 测试账号(请替换为实际账号)
USERNAME = "your_username@example.com"
PASSWORD = "your_password"
if spider.login(USERNAME, PASSWORD):
user_info = spider.get_user_info()
print("用户信息:", user_info)
3. 复杂场景解决方案(RPC通信)
当遇到环境检测或复杂混淆时,使用Sekiro实现JS桥接:
# Python端 (Sekiro客户端)
from sekiro import SekiroClient
sekiro = SekiroClient("your-group-name", "your-client-id")
@sekiro.action("encrypt")
def handle_encrypt(params):
password = params["password"]
# 调用浏览器环境执行加密
return {"encrypted": sekiro.invoke_browser("encryptPassword", password)}
// 浏览器端 (Tampermonkey脚本)
SekiroClient.register("encryptPassword", function(password) {
// 在真实浏览器环境中执行加密
return window.encryptFunction(password);
});
4. 常见错误处理
# 在加密函数中添加错误处理
def encrypt_password(self, password):
try:
return self.ctx.call('hex_md5', password)
except execjs.RuntimeError as e:
# 处理JS执行错误
print(f"JS执行错误: {e}")
# 降级方案:使用Python的MD5实现
import hashlib
return hashlib.md5(password.encode()).hexdigest()
except Exception as e:
print(f"加密失败: {e}")
return None
5. 完整项目结构
js_reverse_demo/
├── autohome_md5.js # 扣取的加密JS
├── autohome_spider.py # 主爬虫程序
├── requirements.txt # 依赖文件
└── sekrio_client.py # RPC桥接文件
requirements.txt内容:
requests==2.31.0
PyExecJS==1.5.1
nodejs==0.1.1 # 需要安装Node.js环境
关键点说明:
- 完整扣取JS:必须确保核心加密函数及其所有依赖函数都被完整提取
- 环境一致性:注意JS中的全局变量(如
chrsz
)需在扣取代码中保留 - 错误降级:准备备用加密方案应对JS执行失败
- 请求仿真:需复制所有必要的请求头(特别是Content-Type和Origin)
- 会话保持:使用requests.Session维持cookies
这里需要注意的是,实际项目中,我们需根据目标网站的加密实现调整扣取的JS代码。可通过Chrome Overrides功能持久化调试修改后的JS文件(Sources > Overrides > Enable Local Overrides)。