案例背景
假设我们需要爬取一家内部测试系统的动态数据API接口。该系统前端页面使用了复杂的JavaScript混淆技术来防止接口被直接调用,同时对请求参数进行了加密签名。另外,登录环节带有图形验证码用于防护。我们的目标是:
- 分析JavaScript代码,逆向加密签名算法。
- 模拟登录过程,自动识别图形验证码并提交。
- 构造正确请求参数,获取动态数据。
- 完整实现Python爬虫,稳定批量抓取数据。
环境准备
- Python 3.8+
- 主要依赖库:
- requests (HTTP请求)
- execjs (调用JavaScript引擎)
- Pillow & pytesseract(验证码图像处理与OCR)
- jsbeautifier(JS格式化辅助阅读)
- lxml(HTML解析)
- selenium & webdriver-manager(动态交互及验证码抓取,可选)
Step 1:分析网页结构和JavaScript代码
模拟环境下,打开前端页面,按F12打开开发者工具:
- 页面HTML框架简单,核心数据通过POST提交参数调用
/api/v1/getData
接口,返回JSON。 - POST请求中的参数均为加密后的签名串,且请求头带有特殊字段
X-Custom-Token
。 - 登录页带有断码的图形验证码,图片URL是
/captcha/image
,验证码刷新时参数带时间戳。
1.1 网络数据初探
使用Chrome Network面板,关注XHR请求:
POST https://internal.test/api/v1/getData
Request Payload:
{
"param": "EncryptedStringHere",
"sign": "GeneratedSignature"
}
Response:
{
"code": 0,
"data": [ ... ]
}
1.2 找到加密签名函数
通过Sources面板,加载执行的JS文件(例如main.min.js
,经过混淆压缩),使用jsbeautifier进行格式化,定位请求相关代码片段。
逆向发现关键函数generateSign(params)
:
function generateSign(params) {
var a = btoa(encodeURIComponent(JSON.stringify(params)));
var b = someObfuscatedFunction(a);
return md5(b + secretKey);
}
大致逻辑是:
- 将参数JSON字符串化,编码成URI,再做Base64编码。
- 经过部分混淆函数处理(
someObfuscatedFunction
)。 - 最后加上固定密钥用MD5加密。
Step 2:JavaScript逆向与关键代码还原
2.1 还原混淆函数
原始混淆函数结构类似:
function someObfuscatedFunction(str) {
var res = '';
for (var i = 0; i < str.length; i++) {
res += String.fromCharCode(str.charCodeAt(i) ^ 123); // 按位异或123
}
return res;
}
这是典型的异或加密,解密时再次异或同样的数字即可还原。
2.2 Python实现等效签名函数
利用Python的base64
,hashlib
和自定义异或函数实现:
import base64
import hashlib
import urllib.parse
secret_key = 'FixedSecretKey123' # 从JS中提取的密钥
def xor_str(s, key=123):
return ''.join(chr(ord(c) ^ key) for c in s)
def generate_sign(params):
# JSON序列化
import json
param_str = json.dumps(params, separators=(',', ':'), ensure_ascii=False)
# URI编码
encoded = urllib.parse.quote(param_str)
# Base64编码
b64 = base64.b64encode(encoded.encode('utf-8')).decode('utf-8')
# 异或处理
xor_result = xor_str(b64)
# 计算MD5签名
sign_str = xor_result + secret_key
md5_hash = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
return md5_hash
Step 3:验证码识别技术
3.1 获取验证码图片
验证码在登录过程中返回,URL例子:
https://internal.test/captcha/image?_t=时间戳
3.2 图片预处理与OCR识别
验证码为简单断码数字。使用Pillow处理,pytesseract识别。
示例预处理代码:
from PIL import Image, ImageFilter
import pytesseract
import io
import requests
def get_captcha(session):
url = f'https://internal.test/captcha/image?_t={int(time.time()*1000)}'
response = session.get(url)
img = Image.open(io.BytesIO(response.content))
# 灰度化
img = img.convert('L')
# 二值化
img = img.point(lambda x: 0 if x < 140 else 255, '1')
# 去噪
img = img.filter(ImageFilter.MedianFilter())
return img
def recognize_captcha(img):
text = pytesseract.image_to_string(img, config='--psm 7 digits')
text = text.strip().replace(' ', '')
return text
Step 4:登录流程模拟
登录时需提交用户名、密码、验证码。
def login(session, username, password):
# 获取验证码并识别
captcha_img = get_captcha(session)
captcha_text = recognize_captcha(captcha_img)
login_data = {
'username': username,
'password': password,
'captcha': captcha_text,
}
response = session.post('https://internal.test/api/login', data=login_data)
result = response.json()
if result['code'] == 0:
print('登录成功')
else:
print('登录失败:', result['msg'])
raise Exception('登录失败')
Step 5:核心数据接口调用
构造请求参数,调用数据接口:
def fetch_data(session, params):
sign = generate_sign(params)
post_data = {
'param': params, # 通常是原始参数JSON对象,部分实现会转换为字符串,请按实际情况调整
'sign': sign,
}
headers = {
'X-Custom-Token': 'token-from-cookie-or-js', # 需要通过登录等动态获取
'Content-Type': 'application/json',
}
resp = session.post('https://internal.test/api/v1/getData', json=post_data, headers=headers)
data = resp.json()
if data['code'] == 0:
return data['data']
else:
raise Exception(f"接口调用失败: {data['msg']}")
Step 6:整体爬虫流程整合
import requests
import time
def main():
session = requests.Session()
# 登录
login(session, 'test_user', 'test_password')
# 伪装UA头
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
})
# 请求示例参数
params = {'type': 'recent', 'limit': 20, 'timestamp': int(time.time())}
# 调用接口
data = fetch_data(session, params)
print('获取数据:', data)
if __name__ == '__main__':
main()
总结
本案例通过模拟环境设计,综合讲述了下一些高级Python爬虫技术:
- JavaScript逆向:理解并还原混淆及加密算法核心。
- 签名构造:使用Python重现JS签名逻辑,成功通过接口认证。
- 图形验证码识别:图像预处理加OCR,自动突破登录验证。
- 会话管理:使用requests.Session维护登录态。
- 爬虫实战:整合流程实现自动登录、数据抓取的完整爬虫。