Python爬虫进阶:手把手教你破解黑猫投诉平台的JS加密(附完整代码)
·
Python爬虫进阶:破解JS加密实战指南
从入门到精通:逆向工程的核心思路
当我们面对一个采用JS加密的网站时,第一步不是急着写代码,而是理解整个加密流程的运作机制。现代Web应用的前端加密通常遵循几个常见模式:时间戳验证、参数排序哈希、随机字符串生成等。这些机制看似复杂,但拆解后往往由几个基础组件构成。
以典型的请求参数加密为例,开发者通常会关注三个关键点:
- 时间敏感性:确保请求在特定时间窗口内有效
- 唯一性标识:防止请求被简单重放
- 完整性校验:验证参数未被篡改
# 基础加密组件示例
import hashlib
import time
import random
import string
def generate_timestamp():
return str(int(time.time() * 1000))
def generate_random_string(length=16):
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(length))
def calculate_signature(params, secret_key):
sorted_params = sorted(params.items())
concatenated = ''.join([f"{k}{v}" for k,v in sorted_params])
return hashlib.sha256((concatenated + secret_key).encode()).hexdigest()
提示:在实际逆向过程中,Chrome开发者工具的"Sources"面板是最重要的工具,学会使用断点调试和调用堆栈追踪能极大提高效率。
逆向工程四步法
1. 网络请求分析
打开Chrome开发者工具(F12),切换到Network面板并保留日志。执行目标操作后,重点关注XHR/fetch请求。筛选出包含关键数据的请求,查看其请求头和参数。
典型加密参数特征:
- 包含
sign、token、nonce等字段名 - 参数值呈现哈希特征(固定长度、十六进制字符串)
- 时间戳参数(通常以13位数字形式出现)
2. JS代码定位
在Network面板中找到发起请求的JS文件,点击"Preview"查看内容。使用以下搜索关键词:
- 加密参数名(如
signature) - 常见加密函数名(如
encrypt、hash) - 加密算法名(如
SHA256、MD5)
// 典型加密代码结构示例
function generateSignature(params) {
const secret = "$d6eb7ff91ee257475%";
const sortedKeys = Object.keys(params).sort();
let rawString = "";
sortedKeys.forEach(key => {
rawString += params[key];
});
return sha256(rawString + secret);
}
3. 加密逻辑还原
找到关键加密代码后,需要理清:
- 输入参数的来源(硬编码、DOM元素、其他API等)
- 参数的组合方式(直接拼接、特定分隔符、排序等)
- 使用的加密算法(哈希、对称加密、非对称加密等)
常见加密模式对照表:
| 加密类型 | 特征 | Python实现方案 |
|---|---|---|
| 简单哈希 | 固定长度输出 | hashlib.sha256 |
| HMAC | 需要密钥 | hmac.new |
| AES | 块加密,需要IV | Crypto.Cipher.AES |
| RSA | 非对称加密 | Crypto.PublicKey.RSA |
4. Python代码实现
将JS逻辑准确转换为Python时需注意:
- 字符编码处理(确保UTF-8一致)
- 时间戳精度(JS是毫秒,Python默认秒)
- 随机数生成机制差异
- 排序算法的一致性
# 完整参数生成示例
def generate_request_params(keyword, page=1, page_size=10):
secret = "$d6eb7ff91ee257475%"
ts = generate_timestamp()
rs = generate_random_string()
params = {
'ts': ts,
'rs': rs,
'keywords': keyword,
'page_size': str(page_size),
'page': str(page),
'type': '1'
}
# 生成签名
signature_items = [ts, rs, secret, keyword, str(page_size), '1']
signature_string = ''.join(sorted(signature_items))
signature = hashlib.sha256(signature_string.encode()).hexdigest()
params['signature'] = signature
return params
实战技巧与优化方案
反反爬策略进阶
现代网站通常会采用多种防御机制组合:
-
请求频率检测:随机延迟+代理IP池
import time from random import uniform def random_delay(min=1, max=3): time.sleep(uniform(min, max)) -
浏览器指纹识别:完善请求头
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Referer': 'https://example.com/', } -
行为模式分析:模拟真实用户操作流程
性能优化方案
当需要大规模爬取时,考虑以下优化:
-
签名预生成:批量计算多个页面的参数
-
异步请求:使用aiohttp提高IO效率
import aiohttp import asyncio async def fetch_page(session, params): async with session.get(url, params=params) as response: return await response.json() -
缓存机制:对不变的数据进行本地缓存
调试与错误处理
健壮的爬虫需要完善的错误处理:
def safe_request(session, params, max_retries=3):
for attempt in range(max_retries):
try:
response = session.get(url, params=params, timeout=10)
if response.status_code == 200:
return response.json()
elif response.status_code == 403:
raise Exception("IP可能被封禁")
else:
raise Exception(f"状态码异常: {response.status_code}")
except Exception as e:
print(f"请求失败 (尝试 {attempt+1}/{max_retries}): {str(e)}")
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # 指数退避
数据解析与存储
获取到数据后,需要有效解析和存储:
HTML解析技巧
使用BeautifulSoup时的最佳实践:
from bs4 import BeautifulSoup
def parse_complaint(html):
soup = BeautifulSoup(html, 'lxml')
# 使用CSS选择器提高准确性
date_elements = soup.select('.u-date')
complaint_items = soup.select('.ts-q-list li')
return {
'start_time': date_elements[6].text.strip(),
'end_time': date_elements[2].text.strip(),
'complaint_id': complaint_items[0].text.strip().split(':')[-1]
}
数据存储方案
根据数据量选择适当存储方式:
| 存储方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| CSV | 小规模数据 | 简单易用 | 无索引 |
| SQLite | 中等规模 | 零配置 | 并发性能有限 |
| MySQL | 大规模 | 性能好 | 需要服务器 |
| MongoDB | 非结构化数据 | 灵活 | 资源占用高 |
# SQLite存储示例
import sqlite3
def init_db():
conn = sqlite3.connect('complaints.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS complaints
(id TEXT PRIMARY KEY, start_time TEXT, end_time TEXT)''')
conn.commit()
return conn
def save_to_db(conn, data):
c = conn.cursor()
c.execute("INSERT OR IGNORE INTO complaints VALUES (?, ?, ?)",
(data['complaint_id'], data['start_time'], data['end_time']))
conn.commit()
法律与道德考量
在开发爬虫时,必须注意:
- 遵守robots.txt:检查目标网站的爬虫政策
- 限制请求频率:避免对目标服务器造成负担
- 数据使用范围:仅用于合法用途
- 隐私保护:不收集敏感个人信息
注意:本文技术方案仅用于学习交流,实际应用中请确保遵守相关法律法规和网站使用条款。
更多推荐
所有评论(0)