招聘网站JS加密参数逆向分析与爬虫实战

📅 2026/8/23 10:20:12
招聘网站JS加密参数逆向分析与爬虫实战
1. 项目背景与核心挑战招聘信息网站的反爬机制一直是爬虫开发者需要面对的技术难题。这类平台通常会采用动态加密参数、JavaScript混淆、Cookie验证等多种手段来阻止自动化数据采集。本次实战的目标网站就采用了典型的JS加密方案所有关键请求参数都经过前端JavaScript代码动态生成直接复制curl命令或简单抓包根本无法复用。在实际测试中发现该网站在获取职位列表和详情页时会向接口发送三个关键加密参数_token: 基于时间戳和用户行为生成的动态令牌sign: 对请求参数进行排序后计算的MD5签名timestamp: 当前请求的时间戳13位毫秒级2. 逆向分析关键步骤2.1 加密定位技巧使用Chrome开发者工具的Search功能全局搜索加密参数名如_token通常会直接定位到加密函数所在位置。但在实际案例中发现网站采用了以下混淆手段参数名动态化每次加载JS代码都会重命名关键变量代码分段执行加密逻辑被拆分成多个IIFE(立即执行函数)字符串加密关键字符串使用\x十六进制编码解决方案是使用Hook技术在参数生成前设置断点// 在Console执行后刷新页面 Object.defineProperty(window, _token, { set: function(val) { console.trace(); return val; } });2.2 核心加密逻辑还原通过调用栈回溯最终定位到核心加密文件vendor.ea12bc.js其中包含如下关键结构function generateToken(t) { var e (new Date).getTime(); return t t || , [e, md5(e SALT_STRING t)].join(_) }实际分析发现还需要注意盐值(SALT_STRING)每小时会通过WebSocket更新部分浏览器指纹参与了最终计算函数被包裹在try-catch中防止报错泄露信息2.3 签名算法破解签名算法采用典型的参数排序盐值方案将所有GET/POST参数按key字母序排序拼接成key1val1key2val2格式的字符串追加固定盐值#HR2023!计算MD5得到32位小写签名Python实现示例import hashlib def generate_sign(params): salt #HR2023! sorted_str .join([f{k}{params[k]} for k in sorted(params.keys())]) return hashlib.md5((sorted_str salt).encode()).hexdigest()3. 完整爬虫架构设计3.1 请求流程设计graph TD A[启动爬虫] -- B[获取WS盐值] B -- C[生成基础Token] C -- D[构造请求参数] D -- E[计算签名] E -- F[发送请求] F -- G[解析响应] G -- H[存储数据]3.2 反反爬策略请求频率控制动态延迟random.expovariate(1/平均间隔)工作日/周末不同策略模拟人工操作轨迹浏览器指纹模拟headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, X-Forwarded-For: f{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}, Accept-Language: zh-CN,zh;q0.9,en;q0.8 }Cookie维护机制自动处理Set-Cookie定期触发验证码请求异常时切换IP池4. 关键代码实现4.1 Token生成器类class TokenGenerator: def __init__(self, ws_url): self.salt None self.ws WebSocketApp(ws_url, on_messageself.on_message) threading.Thread(targetself.ws.run_forever).start() def on_message(self, ws, message): self.salt json.loads(message)[salt] def get_token(self, prefix): ts int(time.time() * 1000) while not self.salt: # 等待盐值初始化 time.sleep(0.1) return f{ts}_{hashlib.md5(f{ts}{self.salt}{prefix}.encode()).hexdigest()}4.2 请求处理器def make_request(url, params): token token_gen.get_token() params.update({ _token: token, timestamp: token.split(_)[0] }) params[sign] generate_sign(params) resp requests.get(url, paramsparams, headersheaders) if resp.json().get(code) 403: raise NeedCaptchaException return resp.json()5. 常见问题与解决方案5.1 高频触发验证码现象连续请求约20次后出现图形验证码解决方案在请求头中添加Referer跳转链路控制单IP请求速率在30次/分钟以下准备打码平台接入方案5.2 WebSocket断开现象运行一段时间后Token失效解决方案实现WS自动重连机制缓存最近3个历史盐值心跳包保持连接5.3 数据字段变异现象相同职位不同时间返回字段结构不同解决方案def safe_get(data, *keys): for k in keys: try: data data[k] except (KeyError, TypeError): return None return data6. 数据存储优化方案6.1 去重设计def get_fingerprint(item): return hashlib.md5(( item[company] item[position] str(item[salary_from]) ).encode()).hexdigest()6.2 增量爬取策略基于最后更新时间过滤使用BloomFilter内存去重断点续爬记录上次最后ID7. 法律合规建议严格遵守robots.txt协议单次采集不超过100页数据仅用于个人分析设置明显的数据来源声明重要提示商业用途需获得平台授权本案例仅用于技术研究目的。建议在本地缓存数据不超过7天。