Python逆向京东联盟h5st 3.1签名参数:从JS混淆到数据采集实战

发布时间:2026/6/24 22:00:05
Python逆向京东联盟h5st 3.1签名参数:从JS混淆到数据采集实战 1. 项目概述当爬虫遇上京东联盟的“铜墙铁壁”做数据采集的朋友尤其是关注电商联盟数据的这两年肯定没少被京东联盟的h5st参数折腾。这玩意儿就像是京东给自家数据大门加装的一道动态密码锁而且这锁的算法还在不断升级。我最近刚啃下h5st 3.1这个硬骨头整个过程可以说是“道高一尺魔高一丈”的典型攻防战。简单说h5st是京东联盟H5页面接口中一个核心的签名参数它由多个字段加密生成每次请求都必须携带且唯一有效直接关系到你能否成功拿到商品、订单、佣金这些关键数据。如果你的爬虫脚本里这个参数不对或者过期了服务器立马给你返回一堆错误码数据想都别想。这次实战的目标很明确在不依赖浏览器自动化比如Selenium这种笨重、低效方式的前提下纯粹用Python逆向分析出h5st 3.1参数的生成逻辑并实现本地化计算。这意味着我们要深入JavaScript混淆代码的腹地把那些被压缩、加密、打乱的算法逻辑给捋顺了然后用Python复现出来。这不仅仅是写个爬虫那么简单更像是一次小型的密码学工程涉及JS逆向、算法还原、Python加密库应用等一系列技能。适合谁看呢如果你是对电商数据有需求的开发者、对JS逆向感兴趣的学习者或者正在被类似签名参数困扰的爬虫工程师那这篇从实战踩坑到最终实现的完整记录应该能给你提供一条清晰的路径和不少避坑指南。2. 核心思路与逆向突破口选择面对h5st这种级别的反爬一头扎进海量的混淆JS里是最蠢的做法。我的核心思路是“由外而内定点爆破”。首先得搞清楚h5st参数在哪个环节被生成并加入到网络请求中。最直接有效的方法就是使用浏览器的开发者工具DevTools进行网络抓包。打开京东联盟的H5页面例如某个商品推广页在Network网络面板中筛选XHR或Fetch请求重点关注那些返回数据是商品详情、订单列表的接口。你会发现这些请求的Headers里或者Payload里大概率会有一个名为h5st的参数它是一长串看起来毫无规律的字符。这就是我们的目标。接下来不是直接去搜索h5st而是利用DevTools的“Initiator”发起者标签页或者更强大的“Search in files”在文件中搜索功能去查找生成或包含这串字符的JavaScript代码片段。这里有个关键技巧h5st参数通常不是凭空产生的它往往是对当前页面环境、用户行为、时间戳等一系列参数进行加密计算的结果。因此在搜索时可以尝试搜索h5st参数值中的某一段比如前8位或者搜索可能参与计算的参数名如body、functionId、appid、t时间戳、fp指纹等。一旦定位到疑似生成函数就要开始枯燥但至关重要的代码分析工作了。此时的代码通常是经过obfuscator等工具混淆的变量名都是a, b, c, d函数调用层层嵌套。我们的突破口就是找到最核心的加密函数入口然后通过“跟栈”Follow the call stack的方式一步步理清它的输入、输出和内部处理逻辑。注意京东的JS混淆和反调试手段可能会定期更新。你可能会遇到“无限debugger”、代码动态加载、环境检测等障碍。这时候需要配合使用“禁用断点”、“重写函数”或者一些浏览器插件来绕过。记住我们的目的是理解算法不是和反调试机制死磕必要时可以“黑盒”补环境优先保证能追踪到数据流。3. 逆向环境搭建与关键工具链工欲善其事必先利其器。纯靠肉眼和浏览器控制台去硬啃混淆代码效率极低且容易出错。一套高效的逆向工具链能让你事半功倍。1. 浏览器与开发者工具主力依然是Chrome或Edge的DevTools。除了基本的断点Breakpoint、监控网络请求要特别熟练使用“Call Stack”调用堆栈面板和“Scope”作用域面板。当你在加密函数入口处打上断点发起一个请求时调用堆栈会清晰展示出是哪些函数一步步调用了这个加密函数。而作用域面板则能让你在断点暂停时查看当前函数局部变量、闭包变量以及全局变量的具体值这是理解参数来源和中间计算结果的黄金窗口。2. JS代码格式化与解混淆工具从Sources源代码面板直接拷贝出来的JS代码往往是被压缩成一行的。你需要一个强大的格式化工具。Chrome DevTools自带的“Pretty print”美化打印那个{}图标是基础。对于更复杂的混淆可以使用像jsnice、prepack.io或de4js这样的在线工具或本地Node.js工具如javascript-obfuscator的反向工程尝试。它们能尝试重命名变量、解析简单的控制流平坦化让代码可读性大幅提升。但别完全依赖自动化工具核心逻辑的梳理最终还得靠人脑。3. Python侧的核心库当我们把JS算法逻辑搞清楚后就要在Python里复现。这离不开几个库requests: 用于最终构造和发送HTTP请求。execjs或PyExecJS: 这是关键。有时JS算法过于复杂完全用Python重写成本太高。这两个库允许你在Python环境中直接调用JavaScript代码片段或文件。你可以把关键的、难以移植的加密函数抠出来放在一个.js文件里然后用execjs去调用它传入参数并获得h5st值。这是一种“半自动化”的讨巧方案稳定且快速。nodejs环境execjs通常需要一个JS运行时环境安装Node.js是最常见的选择。纯Python加密库如果决定完全用Python重写那么hashlib用于MD5、SHA等哈希、hmac、json、time、random、Crypto用于AES、RSA等等标准库或第三方库pycryptodome就是必备的。你需要根据逆向出的算法精确地使用对应的Python函数进行等价实现。4. 调试与比对工具在逆向过程中需要不断对比。用浏览器正常访问一次抓取到正确的h5st我们称之为“标准答案”。然后在你用Python或execjs调用计算出一个h5st后需要和“标准答案”进行逐位比对。如果不一样就要回头检查是哪个输入参数不对还是哪一步计算出了偏差。print大法、日志记录以及将中间变量输出与浏览器Debug时看到的值进行比对是调试阶段的核心手段。4. h5st 3.1 参数生成逻辑深度拆解通过逆向分析我们可以将h5st 3.1的生成逻辑抽象为几个核心阶段。请注意以下流程是基于通用模式的分析具体细节可能因京东的迭代而微调但整体框架具有很高的参考价值。4.1 参数收集与预处理阶段h5st不是凭空产生的它是对一系列“原料”进行加工后的产物。在发起一个API请求前客户端浏览器会收集以下关键信息固定参数如functionId标识接口功能、appid应用标识、client客户端类型、clientVersion客户端版本等。这些通常在页面加载的全局变量或特定JS对象中定义。动态参数如t当前时间戳精确到毫秒、body请求体通常是JSON字符串可能包含商品ID、页码等信息、uuid或fp设备或浏览器指纹通过Canvas、WebGL等多种方式生成具有唯一性和一定稳定性。用户上下文参数如cookie中的关键字段例如pin、wskey等用于标识用户身份这些在计算签名时可能会被间接使用。预处理工作包括将body从对象转换为排序后的JSON字符串有时需要按字母顺序排序键名将时间戳t转换为字符串以及其他参数的字符串拼接准备。一个常见的坑是JSON序列化时的空格和键序问题。Python的json.dumps默认会有些微空格且键序在3.7以下版本不固定必须与JS端的JSON.stringify行为严格一致通常需要指定separators(‘,’, ‘:’)来去除空格并确保字典对象的键在传入dumps前就已按字母顺序排列好。4.2 核心加密算法逆向与还原这是最核心、最困难的一步。通过断点和代码跟踪你会发现收集到的参数被送入一个或多个加密函数中。h5st 3.1的算法通常不是单一的MD5或SHA而可能是多层嵌套的哈希如先SHA256再取部分、HMAC或者是自定义的混淆算法比如将字符串与一个秘钥进行循环XOR再进行Base64编码等。逆向时你需要紧盯几个关键点入口函数找到最终输出h5st字符串的那个函数。给它打上断点观察其输入参数。参数流转在作用域面板里记录下所有传入参数的值。然后一步步“Step into”步入这个函数观察参数如何被处理、拼接、转换。识别标准算法注意代码中是否有类似CryptoJS.MD5(...).toString()、window.btoa(...)、createHmac(‘sha256’, key)等调用。这些是标准加密库的用法很容易在Python中找到对应实现。处理自定义算法如果遇到一长串位运算,|,,、数组循环操作那很可能是自定义算法。你需要耐心地将其逻辑翻译成Python。这里execjs的优势就体现出来了如果这个自定义函数不太长但逻辑绕你可以直接把整个JS函数抠出来让execjs去执行省去翻译的麻烦和出错风险。盐值Salt与密钥特别注意算法中是否硬编码了某个字符串或数字盐值或者从某个全局变量、函数返回值中获取了密钥。这些是签名有效性的关键必须完全还原。4.3 最终拼接与输出格式经过核心加密后得到的可能是一个十六进制字符串或Base64字符串。但h5st的最终格式往往不是单纯的密文。观察抓包得到的h5st值你可能会发现它是由几部分通过特定的分隔符如;连接而成的。一个典型的h5st 3.1格式可能是加密结果;时间戳;随机数;其他标识...。例如可能是a1b2c3d4e5f6;1640995200000;123456;1.0。这意味着客户端不仅发送了加密签名还把生成签名所用的时间戳、一个随机数防止重放攻击、版本号等也一并发送了服务端会用同样的逻辑和这些明文参数进行验签。在Python复现时你必须严格按照这个格式进行最终拼接。5. Python复现实战从零到一生成有效h5st理论分析完毕我们进入实战编码环节。这里我提供两种主流的实现思路并给出关键代码示例。5.1 方案一纯Python原生实现推荐用于学习此方案要求已将JS加密算法完全翻译为Python函数。假设我们逆向出的算法是对字符串S functionId ‘’ t ‘’ JSON.stringify(sorted_body) ‘’ secret_salt进行SHA256哈希然后取前16位十六进制字符再与时间戳t、随机数r以分号连接。import hashlib import json import time import random def generate_h5st_v31(function_id, body_dict, secret_salt): 生成 h5st 3.1 参数 (纯Python模拟) :param function_id: 接口函数ID如 ‘unionSearch’ :param body_dict: 请求体参数字典 :param secret_salt: 逆向得到的固定盐值 :return: 完整的h5st字符串 # 1. 生成时间戳和随机数 t str(int(time.time() * 1000)) # 13位毫秒时间戳 r str(random.randint(100000, 999999)) # 6位随机数 # 2. 处理body按键名排序后序列化为紧凑JSON # 注意必须确保键序与JS端一致。Python 3.7 dict默认保持插入序但为保险可显式排序。 sorted_body_str json.dumps(body_dict, separators(‘,’, ‘:’), sort_keysTrue) # 3. 构造待签名字符串 (根据逆向逻辑调整拼接顺序和分隔符) sign_str f“{function_id}{t}{sorted_body_str}{secret_salt}” # 打印中间值便于调试 # print(f“待签名字符串: {sign_str}”) # 4. 计算SHA256并取前16位 sha256_hash hashlib.sha256(sign_str.encode(‘utf-8’)).hexdigest() sign_part sha256_hash[:16] # 5. 按格式拼接最终h5st h5st f“{sign_part};{t};{r};3.1” # 假设版本标识为3.1 return h5st # 使用示例 if __name__ ‘__main__’: # 这些参数需要从实际请求中捕获或根据页面分析得出 test_function_id “unionOpenActivityRedpacketQuery” test_body {“actId”: “1234567890”, “from”: “h5”} test_salt “jd_common_salt_2023” # 示例盐值实际需逆向获取 result generate_h5st_v31(test_function_id, test_body, test_salt) print(f“生成的h5st: {result}”)5.2 方案二Python execjs 混合实现推荐用于生产当JS算法极其复杂包含大量环境依赖或难以翻译的自定义函数时此方案更稳健。我们将核心JS函数保存为文件。首先创建一个h5st_core.js文件内容是你从浏览器中提取并稍作整理确保它自包含不依赖未定义的浏览器全局对象的加密函数// h5st_core.js // 这是一个高度简化的示例真实函数可能非常复杂 function generateSign(functionId, t, bodyStr, secretKey) { // 这里可能是复杂的混淆算法比如调用了CryptoJS或者一堆位运算 // 假设我们这里是一个模拟的复杂操作 var combined functionId t bodyStr secretKey; // ... 一系列复杂的哈希、编码、变换操作 ... // 最终返回签名部分 return “a1b2c3d4e5f6”; // 示例返回值 } // 暴露一个主函数给Python调用 function getFullH5st(functionId, body, salt) { var t Date.now().toString(); var r Math.floor(Math.random() * 900000 100000).toString(); var bodyStr JSON.stringify(body); var sign generateSign(functionId, t, bodyStr, salt); return sign “;” t “;” r “;3.1”; }然后在Python中使用execjs调用它import execjs import json # 1. 读取JS文件 with open(‘h5st_core.js’, ‘r’, encoding‘utf-8’) as f: js_code f.read() # 2. 创建JS执行环境 ctx execjs.compile(js_code) # 3. 准备参数并调用 function_id “unionOpenActivityRedpacketQuery” body_dict {“actId”: “1234567890”} secret_salt “jd_common_salt_2023” # 需与JS文件内使用的保持一致 # 注意execjs调用时参数直接对应JS函数的形参 h5st ctx.call(“getFullH5st”, function_id, body_dict, secret_salt) print(f“通过execjs生成的h5st: {h5st}”)实操心得在生产环境中强烈建议使用方案二execjs。原因有三第一京东的算法可能频繁微调只需更新JS文件即可Python主逻辑不用动第二避免了将复杂JS逻辑翻译成Python可能引入的细微错误第三性能上一次编译execjs.compile可重复调用开销可接受。但务必注意JS代码的纯净性移除或模拟对window、document等浏览器特有对象的依赖这通常被称为“补环境”。6. 请求构造与数据抓取完整流程有了生成h5st的能力我们就能构造出合法的请求了。整个过程可以封装成一个爬虫类以下是关键步骤的代码示例和解释。import requests import time import json from urllib.parse import urlencode class JdUnionH5Spider: def __init__(self, cookie_str): 初始化爬虫 :param cookie_str: 从浏览器拷贝的完整cookie字符串 self.session requests.Session() self.headers { ‘User-Agent’: ‘Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1’, # 使用移动端UA ‘Accept’: ‘application/json, text/plain, */*’, ‘Accept-Language’: ‘zh-CN,zh;q0.9’, ‘Accept-Encoding’: ‘gzip, deflate, br’, ‘Content-Type’: ‘application/x-www-form-urlencoded; charsetUTF-8’, # 注意Content-Type ‘Origin’: ‘https://u.jd.com’, ‘Referer’: ‘https://u.jd.com/’, # 根据实际H5页面设置 ‘Cookie’: cookie_str # 关键身份凭证 } self.session.headers.update(self.headers) # 初始化execjs环境如果采用方案二 # self.ctx execjs.compile(open(‘h5st_core.js’, ‘r’, encoding‘utf-8’).read()) def _generate_h5st(self, function_id, body_dict): 内部方法生成h5st参数。 这里以纯Python方案为例实际可替换为execjs调用。 # 此处应调用上一节中的generate_h5st_v31函数或execjs的call方法 # 需要逆向获取真实的secret_salt secret_salt “YOUR_REVERSED_SECRET_SALT_HERE” return generate_h5st_v31(function_id, body_dict, secret_salt) def fetch_activity_list(self, page1, page_size20): 示例获取联盟活动列表 function_id “unionOpenActivityRedpacketQuery” # 构造请求体参数需根据实际接口文档或抓包分析 body { “actId”: “”, # 可能为空表示查询列表 “pageIndex”: page, “pageSize”: page_size, “from”: “h5” } # 生成h5st h5st_value self._generate_h5st(function_id, body) # 构造最终请求参数 params { “functionId”: function_id, “body”: json.dumps(body, separators(‘,’, ‘:’)), # 注意body参数本身也需要是JSON字符串 “appid”: “u-jd-h5”, # 需根据抓包确认 “client”: “apple”, # 需根据抓包确认 “clientVersion”: “12.0.0”, “t”: str(int(time.time() * 1000)), # 时间戳需与h5st内嵌的一致或无关 “h5st”: h5st_value, # 注入我们计算好的h5st } # 重要有些接口参数是放在URL查询字符串中有些是放在POST的form-data里。 # 此处假设为GET请求参数在URL中。 url “https://api.m.jd.com/api” full_url f“{url}?{urlencode(params)}” try: response self.session.get(full_url, timeout10) response.raise_for_status() # 检查HTTP错误 data response.json() # 检查业务码京东接口通常有‘code’字段0表示成功 if data.get(‘code’) 0: return data.get(‘data’, {}) else: print(f“接口请求失败: code{data.get(‘code’)}, msg{data.get(‘msg’)}”) return None except requests.exceptions.RequestException as e: print(f“网络请求异常: {e}”) return None except json.JSONDecodeError as e: print(f“响应解析异常: {e}”) return None # 使用示例 if __name__ ‘__main__’: # 你的京东联盟H5页面Cookie MY_COOKIE “pinxxx; wskeyxxx; ...” spider JdUnionH5Spider(MY_COOKIE) activity_data spider.fetch_activity_list(page1) if activity_data: print(json.dumps(activity_data, indent2, ensure_asciiFalse))关键点解析Cookie是灵魂Cookie是维持登录态的关键必须从已登录京东联盟H5页面的浏览器中获取。wskey、pin等是关键字段。Cookie会过期需要维护更新机制。参数一致性注意body参数在请求中通常需要被json.dumps成字符串并且其格式空格、键序必须与生成h5st时使用的body字符串完全一致否则服务端验签会失败。这是一个极高频的错误点。请求方式与参数位置仔细分析抓包确定是GET还是POST。GET请求参数在查询字符串URL后POST请求参数可能在form-data或x-www-form-urlencoded中。h5st参数可能放在headers里也可能放在body或query中需以实际抓包为准。其他必要参数appid、client、clientVersion等参数看似固定但也必须携带且值需正确。它们可能也参与了h5st的生成或者服务端会做校验。7. 常见问题排查与稳定性优化策略即使按照上述流程走通在实际运行中也会遇到各种问题。下面是一个常见问题排查表问题现象可能原因排查思路与解决方案返回“签名错误”、“参数无效”等错误码1.h5st生成算法错误。2. 参与签名的参数与发送的参数不一致。3. 时间戳t不同步或格式不对。4. 盐值secret错误或已过期。1.比对大法在浏览器端执行一次正常请求在加密函数入口处断点记录下所有输入参数的精确值字符串形式。在Python端打印出用于生成签名的每一个中间变量进行逐字比对。特别注意JSON字符串是否有多余空格、换行键序是否一致。2.时间戳确保生成h5st时使用的t和请求参数中的t是同一个值或符合其规则。3.更新JS代码算法可能已升级重新抓取最新的JS文件进行分析。返回“未登录”、“权限不足”1. Cookie失效或错误。2. 请求头缺少必要字段如Referer。3. IP或设备指纹被风控。1.检查Cookie手动在浏览器访问对应页面确认Cookie是否有效。检查wskey是否过期需要定期刷新。2.补全Headers将浏览器抓包中的所有Headers特别是Origin,Referer,User-Agent原样复制到爬虫中。3.模拟环境尝试使用更真实的移动端UA并保持会话requests.Session。请求长时间无响应或返回非预期数据1. 请求频率过高触发风控。2. 接口地址或参数结构已变更。1.降低频率在请求间增加随机延时如time.sleep(random.uniform(1, 3))。2.验证接口再次抓包确认目标接口的URL和参数是否发生变化。execjs调用报错提示某些JS对象未定义JS代码中依赖了浏览器环境特有的对象如window,document,navigator。补环境在注入的JS代码开头手动定义这些对象。例如var window this; var document {}; var navigator {userAgent: ‘…’};。更复杂的环境检测需要更细致的模拟。算法看似正确但成功率不高1. 随机数r或指纹fp参与签名且每次需不同。2. 存在“滑动验证”等二次验证仅签名正确不足以通过。1.动态参数确保r随机数每次请求都重新生成。fp指纹可能需要一个稳定的生成算法不能每次随机。2.应对验证码如果遇到滑块或点选验证码说明当前请求已被识别为高风险。需要降低频率、更换IP、或研究验证码破解这属于另一个更复杂的领域。稳定性优化策略Cookie池与更新机制不要使用单一Cookie。构建一个Cookie池并实现自动检测过期、自动刷新的逻辑这可能需要模拟登录流程难度较高。IP代理池对于大规模采集使用高质量的住宅IP代理池是必须的可以避免因单个IP请求过多被封。请求参数动态化除了h5st中的t和r其他如uuid、_t等参数也应尽量模拟真实客户端的生成规律。错误重试与降级网络请求加入重试机制如tenacity库。对于非关键错误如偶发的签名错误可以记录日志并重试一次对于明确的登录失效错误则触发Cookie更新流程。代码与算法同步将核心的JS加密代码单独管理并建立版本意识。一旦发现大量签名错误第一时间去验证目标网站的JS代码是否更新。逆向h5st这类参数是一个持续对抗的过程。没有一劳永逸的解决方案核心能力在于快速定位问题、分析差异和更新代码的逻辑。这套从分析、逆向到实现的完整方法论不仅能用于京东联盟也能应用到其他具有类似签名反爬机制的平台上。关键在于保持耐心细致比对并善用工具。