1. 项目概述当爬虫遇上JS加密与签名验证十年前我刚入行爬虫时大多数网站还停留在简单的robots.txt防护阶段。如今随便打开一个电商页面都能看到层层加密防护接口参数被国密4加密、请求头带着动态签名、关键数据用RSA非对称加密传输。上周我尝试爬取某旅游平台数据时甚至遇到了迭代加密三角网这种新型防御手段。这次要破解的正是典型的JS逆向签名算法双重防护体系。目标网站不仅用JavaScript混淆了核心加密逻辑还对每个请求都添加了动态生成的签名参数。常规的requests直接请求会立即触发风控返回403状态码。通过Chrome开发者工具抓包分析发现关键难点在于登录接口的密码字段经过RSA加密查询接口携带由时间戳、设备指纹生成的32位签名数据返回体采用自定义的字节流编码方式2. 核心防护机制逆向分析2.1 JavaScript加密逻辑定位首先在Chrome的Sources面板对目标网站进行调试。通过搜索关键词encrypt、sign等很快在bundle.js中发现可疑代码段function generateSign(timestamp, deviceId) { var key CryptoJS.enc.Utf8.parse(7a3265f1b2e4c8d9); var iv CryptoJS.enc.Utf8.parse(9e8d7c6b5a43210f); var srcStr deviceId | timestamp; return CryptoJS.AES.encrypt(srcStr, key, {iv: iv}).toString(); }这段代码揭示了三层防护使用CryptoJS库实现AES-CBC加密密钥和IV都是硬编码在JS中的固定值签名原料由设备ID和时间戳通过管道符连接实战技巧现代网站常会通过Webpack将代码拆分成多个chunk此时需要先在Network面板找到包含加密逻辑的JS文件。可以尝试搜索encrypt、CryptoJS、sign等关键词快速定位。2.2 Python还原加密过程对应的Python实现需要安装pycryptodome库from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 def generate_sign(timestamp, device_iddefault_device): key b7a3265f1b2e4c8d9 iv b9e8d7c6b5a43210f cipher AES.new(key, AES.MODE_CBC, iv) plaintext f{device_id}|{timestamp}.encode() ciphertext cipher.encrypt(pad(plaintext, AES.block_size)) return base64.b64encode(ciphertext).decode()测试发现这个签名算法存在两个漏洞设备ID可以使用固定值没有校验时间戳的合法性3. 完整请求链路破解3.1 请求参数加密处理目标网站的搜索接口要求对查询参数进行字段排序和MD5签名import hashlib def params_sign(params): sorted_str .join([f{k}{params[k]} for k in sorted(params)]) return hashlib.md5(sorted_str.encode()).hexdigest() # 使用示例 params { keyword: 手机, page: 1, size: 20 } sign params_sign(params) # 输出3e4a5b6c7d8e9f0a1b2c3d4e5f6g7h83.2 请求头动态生成通过分析正常请求发现需要携带以下特殊头信息headers { X-Request-Sign: generate_sign(int(time.time())), X-Device-Id: a1b2c3d4e5f6, X-Timestamp: str(int(time.time() * 1000)), User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }4. 反反爬策略深度优化4.1 指纹对抗方案现代风控系统会检测浏览器指纹特征。我们使用selenium-wire配合以下配置from seleniumwire import webdriver options { disable_encoding: True, ignore_http_methods: [OPTIONS] } driver webdriver.Chrome(seleniumwire_optionsoptions) # 修改WebGL指纹 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: const getParameter WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter function(parameter) { if (parameter 37445) return Intel Open Source Technology Center; if (parameter 37446) return Mesa DRI Intel(R) HD Graphics 520; return getParameter(parameter); }; })4.2 请求频率控制策略采用自适应延迟算法防止被封import random import time class RequestThrottle: def __init__(self, base_delay1.0): self.base_delay base_delay self.error_count 0 def wait(self): delay self.base_delay * (1 self.error_count * 0.5) jitter random.uniform(-0.2, 0.2) time.sleep(max(0.5, delay jitter)) def record_error(self): self.error_count min(self.error_count 1, 5) def record_success(self): self.error_count max(self.error_count - 1, 0)5. 数据解密处理5.1 响应体解密案例当遇到类似纵向加密的数据时可以尝试以下处理流程def decrypt_response(ciphertext): # 第一步Base64解码 raw_data base64.b64decode(ciphertext) # 第二步字节流处理 magic_num raw_data[:4] # 文件头标识 version raw_data[4:6] # 版本号 payload raw_data[6:] # 有效载荷 # 第三步异或解密 key 0xAB decrypted bytes([b ^ key for b in payload]) # 第四步JSON解析 return json.loads(decrypted.decode())5.2 数据清洗技巧对于HTML加密混淆的情况推荐使用以下处理链先用正则提取有效数据段使用html.unescape处理HTML实体用bs4清理残留标签最后进行unicode标准化import re from bs4 import BeautifulSoup import html def clean_html(html_str): # 提取加密数据块 encrypted re.search(rscript id__DATA__(.*?)/script, html_str).group(1) # 多层解码 decoded html.unescape(encrypted) decoded bytes.fromhex(decoded[2:]).decode(utf-8) # 最终清理 soup BeautifulSoup(decoded, html.parser) return soup.get_text()6. 工程化建议6.1 代理IP管理方案建议使用以下架构管理代理IPclass ProxyManager: def __init__(self, proxy_list): self.proxies proxy_list self.current 0 self.blacklist set() def get_proxy(self): while True: proxy self.proxies[self.current] self.current (self.current 1) % len(self.proxies) if proxy not in self.blacklist: return proxy def ban_proxy(self, proxy): self.blacklist.add(proxy) if len(self.blacklist) len(self.proxies) / 2: self.blacklist.clear()6.2 日志监控体系完善的日志系统应该包含import logging from logging.handlers import RotatingFileHandler def setup_logger(name): logger logging.getLogger(name) logger.setLevel(logging.DEBUG) # 文件日志按100MB轮转 file_handler RotatingFileHandler( spider.log, maxBytes100*1024*1024, backupCount5 ) file_formatter logging.Formatter( %(asctime)s - %(levelname)s - %(message)s ) file_handler.setFormatter(file_formatter) # 控制台日志 console_handler logging.StreamHandler() console_formatter logging.Formatter( [%(levelname)s] %(message)s ) console_handler.setFormatter(console_formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger7. 法律与伦理边界在爬虫开发中必须注意严格遵守robots.txt协议单机请求频率控制在人类操作范围内不爬取个人隐私数据不使用爬虫进行商业竞争或攻击我曾遇到一个案例某电商平台通过检测鼠标移动轨迹识别爬虫。合法合规的做法是通过公开API获取数据购买官方数据服务限制爬取频率在合理范围重要提示近期出现多起因爬虫引发的法律纠纷建议在开发前咨询法律顾问确保业务模式合规。某些加密算法如国密的使用可能受到法律限制解密此类数据存在法律风险。