
JS 动态请求的本质是浏览器通过 JavaScript 脚本,按照特定的规则(请求方法、参数、头信息、加密方式)向后端 API 接口发送请求,后端返回 JSON、XML 等结构化数据后,前端再进行渲染。逆向工程的核心就是还原这些请求规则,其流程可分为四步:
这一流程的关键在于精准定位请求和破解参数加密,也是逆向工程的难点所在。
完成 JS 动态请求的逆向,需要搭配合适的工具链,以下是常用工具的功能与选型建议:
工具类别 | 推荐工具 | 核心作用 |
|---|---|---|
浏览器调试工具 | Chrome/Firefox 开发者工具 | 抓包、查看请求参数、调试 JS 代码 |
JS 代码格式化 / 反混淆 | Prettier、Chrome 开发者工具 Sources 面板 | 将混淆后的 JS 代码格式化,便于阅读 |
加密算法验证 | Online Hash Calculator、CryptoJS 在线工具 | 验证逆向出的加密算法是否正确 |
Python 请求库 | requests/httpx(同步)、aiohttp(异步) | 构造并发送模拟请求 |
JS 代码执行 | PyExecJS、Node.js | 在 Python 中执行逆向得到的 JS 加密代码 |
其中,Chrome 开发者工具是最基础也是最重要的工具,几乎能完成从抓包到初步 JS 分析的所有工作。
以某资讯网站的动态新闻列表为例,我们需要获取其分页加载的新闻数据:
F12打开开发者工具,切换到Network面板;news_list的请求(通常为 JSON 格式),这就是承载新闻数据的核心请求。点击该请求,切换到Headers标签,可查看关键信息:
https://example.com/api/news/list(目标 API 接口);page(页码)、limit(每页条数)、timestamp(时间戳)、sign(签名)等参数;User-Agent、Referer、Token等头信息。其中,page和limit是普通参数,timestamp是当前时间戳,而sign是疑似加密的签名参数,这是逆向的重点。
要找到sign的生成规则,需定位对应的 JS 代码:
{}按钮);sign关键词,找到签名生成的代码段,例如:javascript运行function generateSign(timestamp, page, limit) {
const secret = "abc123xyz"; // 固定密钥
const str = timestamp + page + limit + secret;
return md5(str); // MD5加密
}此时,我们就逆向出了sign的生成规则:将时间戳、页码、每页条数与固定密钥拼接后,进行 MD5 加密。
基于上述逆向结果,实现 Python 爬虫:
import requests
import time
import hashlib
import json
from typing import Dict, List
class JSDynamicRequestCrawler:
def __init__(self):
# 目标API接口
self.api_url = "https://example.com/api/news/list"
# 固定密钥(逆向得到)
self.secret = "abc123xyz"
# 请求头(从浏览器Headers中复制)
self.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",
"Referer": "https://example.com/news",
"Content-Type": "application/json;charset=UTF-8"
}
def generate_sign(self, timestamp: str, page: int, limit: int) -> str:
"""根据逆向规则生成签名"""
# 拼接字符串
str_to_sign = f"{timestamp}{page}{limit}{self.secret}"
# MD5加密
md5_obj = hashlib.md5()
md5_obj.update(str_to_sign.encode("utf-8"))
sign = md5_obj.hexdigest()
return sign
def get_news_list(self, page: int, limit: int = 10) -> List[Dict]:
"""获取单页新闻列表数据"""
# 生成时间戳(秒级,与JS中的一致)
timestamp = str(int(time.time()))
# 生成签名
sign = self.generate_sign(timestamp, page, limit)
# 构造请求参数
payload = {
"page": page,
"limit": limit,
"timestamp": timestamp,
"sign": sign
}
try:
# 发送POST请求
response = requests.post(
url=self.api_url,
headers=self.headers,
json=payload # 若接口为form-data,使用data=payload
)
# 验证响应状态
response.raise_for_status()
# 解析JSON数据
data = response.json()
if data.get("code") == 200: # 假设接口返回code=200表示成功
news_list = data.get("data", {}).get("list", [])
print(f"成功获取第{page}页数据,共{len(news_list)}条新闻")
return news_list
else:
print(f"接口返回错误:{data.get('msg')}")
return []
except requests.exceptions.RequestException as e:
print(f"请求失败:{str(e)}")
return []
def save_data(self, all_news: List[Dict], file_path: str = "news_list.json"):
"""保存数据到本地JSON文件"""
with open(file_path, "w", encoding="utf-8") as f:
json.dump(all_news, f, ensure_ascii=False, indent=4)
print(f"所有数据已保存到{file_path},共{len(all_news)}条新闻")
def start_crawl(self, max_page: int = 5):
"""启动爬虫,爬取多页数据"""
all_news = []
for page in range(1, max_page + 1):
news_list = self.get_news_list(page)
if not news_list:
# 若某页数据获取失败,可选择停止或继续
print(f"第{page}页数据获取失败,停止爬取")
break
all_news.extend(news_list)
# 避免请求过快,添加短暂延迟
time.sleep(1)
self.save_data(all_news)
if __name__ == "__main__":
# 初始化爬虫并启动
crawler = JSDynamicRequestCrawler()
crawler.start_crawl(max_page=5)generate_sign方法按照逆向得到的规则,将时间戳、页码、每页条数与固定密钥拼接后进行 MD5 加密,生成签名参数;get_news_list方法构造 POST 请求的参数和头信息,发送请求并解析返回的 JSON 数据;start_crawl方法循环爬取多页数据,save_data方法将数据保存到本地 JSON 文件;若签名采用 AES、RSA 或自定义复杂算法,直接用 Python 还原可能耗时费力,可采用两种方案:
若请求头中包含动态生成的 Token(如从 Cookie 或其他接口获取),需在爬虫中先请求 Token 接口,获取 Token 后再构造请求。
对于需要爬取大量数据的场景,可将同步的requests替换为异步的aiohttp,结合asyncio实现并发请求,提升爬取效率。
逆向工程是 Python 爬虫处理 JS 动态请求的核心能力,其本质是还原前端与后端的通信规则。从浏览器抓包定位请求,到分析参数与加密逻辑,再到用 Python 模拟请求,整个流程需要开发者具备调试 JS 代码、分析网络请求和编写爬虫的综合能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。