ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Node.js实现京东h5st签名与自动下单实战

Node.js实现京东h5st签名与自动下单实战 简介这是一份基于 Node.js 实现的京东商品到货监控与自动下单轻量级工具面向具备 JavaScript 基础的开发者适用于学习爬虫逻辑、登录鉴权扫码登录、库存轮询及电商接口模拟等实战场景。资源包共11个文件含5个核心 JS 脚本如 index.js 入口、utils.js 工具函数、log.js 日志模块、README.md 说明文档、LICENSE 协议文件、.gitignore 与 package.json 等工程配置以及展示流程的 jd-happy.gif 动图整体压缩包仅1.7MB结构简洁、依赖明确便于快速阅读与本地调试。已有819人学习下载可完整获取从浏览器初始化、页面抓取分析、参数提取到自动下单的全流程代码实现尤其适合理解扫码登录状态缓存、地区库存查询接口适配、抢购逻辑封装等典型 Node 爬虫工程实践细节。1. 为什么用 Node 做京东商品到货监控和自动下单不是“炫技”而是工程权衡下的务实选择你见过凌晨三点弹出的“库存已恢复”通知吗不是抢演唱会门票是抢一台缺货三个月的 RTX 4090 显卡、一款限量联名球鞋或者某款停产多年的工业传感器——这些商品在京东页面上长期显示“缺货”但实际库存可能每小时刷新数次且秒空。人工盯屏不仅低效更因响应延迟错失下单窗口。而 Python 爬虫常卡在京东滑块验证、h5st 签名、用户行为指纹三重防线Java 项目又太重为一个监控下单服务搭 Spring Boot Redis 定时任务启动耗时 8 秒资源开销大部署成本高。jd-happy 这个被标记为 [DEPRECATED] 的 Node 爬虫项目恰恰踩中了轻量、实时、可嵌入 CI/CD 流水线的临界点它用原生 Node.js非 Electron、非 Puppeteer 全浏览器完成登录态维持、h5st 动态签名生成、库存轮询与条件触发下单全程无 GUI、无 Chrome 实例、内存常驻低于 60MB。它适合中小团队做私有化部署比如放在青龙面板或树莓派也适合作为电商运营侧的自动化补货探针。注意这不是教你怎么绕过风控而是讲清楚——当京东反爬策略升级到 h5st v3.0 滑块设备指纹后Node 如何用最小依赖达成“能跑通、能维护、能降级”的生产级可用性。2. 从零搭建 jd-happy 运行环境Node 版本、核心依赖与京东登录态持久化机制2.1 选对 Node 版本为什么必须锁定 v16.20.2而非最新 LTSjd-happy 依赖crypto-js生成 h5st 签名该库在 Node v18 中因Buffer.from(string, binary)行为变更导致签名错位同时其 cookie 存储逻辑依赖tough-cookiev4.x而该版本在 Node v20 中因URLSearchParams的toString()输出格式变化使京东 Set-Cookie 解析失败。实测 v16.20.2 是兼容性最稳的版本——它既支持fetchAPI避免引入axios增加 bundle 体积又未启用 v18 引入的--experimental-permission权限模型会阻断fs.readFileSync读取本地 cookies.json。安装命令如下# 推荐用 nvm 管理避免全局污染 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install 16.20.2 nvm use 16.20.2 node -v # 必须输出 v16.20.2提示不要用nvm install --ltsLTS 当前是 v18.x会直接导致 h5st 签名失效。若已装错执行nvm uninstall 18.x后重装 v16.20.2。2.2 核心依赖精简清单只保留 4 个包拒绝“npm install 一整页”jd-happy 的设计哲学是“最小可行依赖”。它不使用 Puppeteer内存占用 300MB、不依赖 Selenium需额外安装 chromedriver、不引入 Express纯 CLI 工具无需 HTTP Server。真实依赖仅 4 个包名版本作用替代方案为何被弃用node-fetch^3.3.2发起带 cookie 的 fetch 请求axios体积大120KB且默认不透传credentials: includecrypto-js^4.1.1生成 h5st 签名所需 AES/CBC/HMACcrypto原生模块无法复现京东 JS 中CryptoJS.enc.Base64.parse()的 padding 行为tough-cookie^4.1.3解析并持久化京东 Set-Cookie 字段cookie库不支持Domain属性的严格匹配导致跨子域 cookie 丢失jsdom^20.0.3在无浏览器环境下模拟document和navigator对象仅用于生成navigator.userAgent和navigator.platform体积仅 15KB安装命令务必加--no-save避免写入 package.json 冲突npm install node-fetch3.3.2 crypto-js4.1.1 tough-cookie4.1.3 jsdom20.0.3 --no-save2.3 登录态持久化cookies.json 不是文件而是“状态快照”京东登录态由 3 层组成基础层pt_keypt_pin京东账号凭证有效期 30 天会话层wskeyWeb Session Key有效期 2 小时需定时续期风控层trackiduuid设备指纹绑定 IP 和 User-Agentjd-happy 不走扫码登录而是要求用户手动登录京东 PC 端然后导出cookies.json。这个文件不是简单复制浏览器 Cookie而是通过jsdom模拟运行京东登录页 JS提取document.cookie并用tough-cookie解析成结构化对象// utils/cookie-parser.js const { CookieJar } require(tough-cookie); const fetch require(node-fetch); async function parseCookiesFromJson(jsonPath) { const cookiesRaw JSON.parse(fs.readFileSync(jsonPath, utf8)); const jar new CookieJar(); // 关键京东 cookie 必须设置 domain 为 .jd.com 才能跨子域生效 for (const cookieStr of cookiesRaw) { const cookie CookieJar.parse(cookieStr); if (cookie cookie.domain .jd.com) { await jar.setCookie(cookie.toString(), https://www.jd.com); } } return jar; }参数说明cookies.json必须是数组格式每项为namevalue; Domain.jd.com; Path/; Expires...字符串。常见错误是导出时漏掉Domain.jd.com导致请求api.m.jd.com时 cookie 不携带。3. h5st 签名生成原理与 Node 实现绕过“前端加密黑匣子”不是破解而是复刻3.1 h5st 是什么京东的动态请求签名不是 token是“时间参数密钥”的三重哈希h5stHigh Security Token是京东在 2022 年全面启用的请求签名机制用于校验api.m.jd.com接口的合法性。它不是简单的 JWT而是基于以下输入生成的 48 位 hex 字符串时间戳毫秒级精确到 1ms误差 2s 则签名失效请求路径如/api/order/getOrderList请求参数按 key 字典序拼接keyvalue末尾加密钥硬编码在京东前端 JS 中的h5st密钥如a1b2c3d4e5f6随版本更新而轮换签名流程hmac-sha256(时间戳 路径 参数, 密钥)→ base64 → 截取前 24 字节 → hex 编码 → 拼接时间戳13 位 签名48 位→h5st_v3_1672531200000_xxx...3.2 Node 复刻 h5st v3.0用 crypto-js 精确还原前端 JS 行为京东前端使用CryptoJS.HmacSHA256其enc.Base64.parse()对字符串的 padding 处理与 Node 原生crypto.createHmac不同。必须用crypto-js保持字节级一致// utils/h5st-generator.js const CryptoJS require(crypto-js); function generateH5ST(timestamp, path, params, secretKey) { // 1. 参数按 key 字典序排序并拼接注意value 需 encodeURIComponent const sortedParams Object.keys(params) .sort() .map(key ${key}${encodeURIComponent(params[key])}) .join() ; // 2. 拼接原始字符串timestamp path sortedParams const rawString ${timestamp}${path}${sortedParams}; // 3. 使用 CryptoJS 生成 HMAC-SHA256并转为 Base64 const hmac CryptoJS.HmacSHA256(rawString, secretKey); const base64 CryptoJS.enc.Base64.stringify(hmac); // 4. 取前 24 字节48 hex 字符注意Base64 每 4 字符对应 3 字节 const hexSignature CryptoJS.enc.Hex.stringify( CryptoJS.enc.Base64.parse(base64).words.slice(0, 6) ); // 5. 拼接 h5st_v3_ 时间戳 _ 签名 return h5st_v3_${timestamp}_${hexSignature}; } // 示例调用 const h5st generateH5ST( Date.now(), /api/order/getOrderList, { page: 1, pageSize: 20 }, a1b2c3d4e5f6 // 实际密钥需从京东 JS 中提取 );参数说明secretKey必须从京东mhome.js或common.js中提取搜索h5st或hmacSha256即可定位。密钥通常以var h5stKey xxx形式存在每 2~3 个月轮换一次。这是 jd-happy 最易失效的环节——密钥过期会导致所有接口返回 403必须建立密钥自动抓取脚本见第 5 章。3.3 时间戳同步为什么Date.now()不够必须用 NTP 校准京东服务器时间与客户端时间误差超过 2000ms 即拒绝请求。Node 的Date.now()受系统时钟漂移影响Linux 服务器连续运行 7 天后误差可达 500ms。解决方案是调用公共 NTP 服务校准// utils/ntp-sync.js const { execSync } require(child_process); function syncTimeWithNTP() { try { // 使用 pool.ntp.org 公共服务器 execSync(sudo ntpdate -s time.nist.gov, { stdio: ignore }); console.log(✅ NTP time synced); } catch (e) { console.warn(⚠️ NTP sync failed, using local time); } }注意需赋予 Node 进程sudo权限sudo setcap cap_sys_timeep $(which node)否则ntpdate会 Permission Denied。4. 库存轮询与下单触发如何把“每秒查一次”变成“每秒省 90% 请求”4.1 智能轮询策略基于商品 SKU 的状态机而非暴力轮询暴力轮询如 setInterval 每 1s 请求一次会被京东风控识别为机器人触发滑块验证。jd-happy 采用三级状态机状态触发条件轮询间隔行为IDLE商品初始状态30s查询wareDetail接口检查stockState字段PENDINGstockState 33有货但未开放购买5s查询skuDetail接口检查buyButtonStatus是否为1READYbuyButtonStatus 1且canBuy true1s准备下单预加载地址、支付方式状态迁移代码// monitor/sku-monitor.js class SKUMonitor { constructor(skuId) { this.skuId skuId; this.state IDLE; this.interval null; } async checkStock() { const res await fetch(https://api.m.jd.com/api?functionIdwareDetailbody{skuId:${this.skuId}}, { headers: { Cookie: await this.getCookieHeader() } }); const data await res.json(); if (data.data.stockState 33) { this.setState(PENDING); this.startPolling(5000); } else if (data.data.buyButtonStatus 1 data.data.canBuy) { this.setState(READY); this.startPolling(1000); this.triggerOrder(); } } setState(newState) { if (this.state ! newState) { console.log(SKU ${this.skuId} state changed: ${this.state} → ${newState}); this.state newState; } } }4.2 下单服务跳过购物车直连submitOrder接口的 7 个必要参数京东下单不走addCart→getCart→submitOrder流程而是直接调用submitOrder需构造 7 个关键参数参数名来源说明skuId用户配置商品 SKUnum用户配置购买数量addressIdgetAddressList接口收货地址 ID需提前获取paymentType固定值4在线支付微信/支付宝payMoneygetSkuPrice接口实际支付金额含优惠yuShou固定值0非预售订单overseaPurchaseCookiesgetOverseaCookies接口海外购 cookie国内商品可为空下单请求示例// order/submit-order.js async function submitOrder(skuId, num, addressId) { const priceRes await fetch(https://api.m.jd.com/api?functionIdgetSkuPricebody{skuId:${skuId}}, { headers: { Cookie: await getCookieHeader() } }); const priceData await priceRes.json(); const orderData { skuId, num, addressId, paymentType: 4, payMoney: priceData.data.priceInfo.realPrice, yuShou: 0, overseasPurchaseCookies: }; const h5st generateH5ST(Date.now(), /api/order/submitOrder, orderData, h5stKey); const res await fetch(https://api.m.jd.com/api?functionIdsubmitOrder, { method: POST, headers: { Cookie: await getCookieHeader(), Content-Type: application/json, h5st: h5st }, body: JSON.stringify(orderData) }); return res.json(); }提示submitOrder接口返回resultCode: 0表示下单成功但需立即调用getOrderList确认订单号因为京东存在“下单成功但库存扣减失败”的异步场景。5. 避坑指南那些让 jd-happy 在凌晨 3 点静默崩溃的 4 个血泪问题5.1 现象h5st signature mismatch日志显示签名长度 47 而非 48原因crypto-js的enc.Hex.stringify()在处理奇数字节数组时自动补 0但京东前端 JS 的toString(CryptoJS.enc.Hex)不补。当 HMAC 结果最后一位是0时Node 端生成abc...047 位而京东期望abc...0048 位。解决强制补零至 48 位const hexSignature CryptoJS.enc.Hex.stringify( CryptoJS.enc.Base64.parse(base64).words.slice(0, 6) ).padEnd(48, 0); // 关键padEnd5.2 现象403 Forbidden但 h5st 正确、cookie 有效、时间同步原因京东风控升级后要求User-Agent必须包含Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36且Accept头必须为application/json, text/plain, */*。缺失任一字段即 403。解决在所有 fetch 请求中显式设置headers: { Cookie: cookieHeader, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: application/json, text/plain, */*, h5st: h5st }5.3 现象trackid失效连续 3 次请求返回{code:401,msg:Unauthorized}原因trackid绑定设备指纹当服务器 IP 变更如云服务器重启、或User-Agent字符串微调如多一个空格京东判定为新设备作废旧 trackid。解决每次请求前重新生成trackid从https://api.m.jd.com/client.action?functionIdgenTrackId获取async function getTrackId() { const res await fetch(https://api.m.jd.com/client.action?functionIdgenTrackId, { headers: { Cookie: await getCookieHeader() } }); return (await res.json()).trackid; }5.4 现象npm start后进程退出日志无报错原因Node 进程在 SSH 断开后被 SIGHUP 信号终止。Linux 默认行为是杀死前台进程组。解决用nohup启动并重定向日志nohup node index.js jd-happy.log 21 echo $! jd-happy.pid # 保存 PID 便于 kill进阶用pm2管理pm2 start index.js --name jd-happy支持自动重启、日志轮转、内存监控。6. 生产级加固密钥自动更新、失败重试熔断与微信消息推送6.1 h5st 密钥自动抓取用 Puppeteer 做“密钥守夜人”每天凌晨 2 点执行密钥轮换无通知人工更新必然滞后。我们用 Puppeteer 启动一个极简浏览器实例访问京东首页提取h5stKey// scripts/fetch-h5st-key.js const puppeteer require(puppeteer); async function fetchH5STKey() { const browser await puppeteer.launch({ headless: true, args: [--no-sandbox] }); const page await browser.newPage(); await page.goto(https://www.jd.com, { waitUntil: networkidle2 }); const key await page.evaluate(() { // 在京东页面 JS 上下文中执行 return window.h5stKey || document.querySelector(script).textContent.match(/h5stKey\s*\s*([^])/)?.[1]; }); await browser.close(); return key; } // 写入 config.json const fs require(fs); fetchH5STKey().then(key { const config JSON.parse(fs.readFileSync(config.json, utf8)); config.h5stKey key; fs.writeFileSync(config.json, JSON.stringify(config, null, 2)); console.log(✅ h5stKey updated: ${key}); });部署加入 crontab0 2 * * * cd /path/to/jd-happy node scripts/fetch-h5st-key.js每天凌晨 2 点执行。6.2 失败重试熔断3 次 403 后暂停 10 分钟避免触发永久封禁京东对高频 403 请求会临时封禁 IP。jd-happy 内置熔断器// utils/circuit-breaker.js class CircuitBreaker { constructor() { this.failureCount 0; this.lastFailureTime 0; this.cooldown 10 * 60 * 1000; // 10 分钟 } canProceed() { const now Date.now(); if (now - this.lastFailureTime this.cooldown) { this.failureCount 0; return true; } return this.failureCount 3; } recordFailure() { this.failureCount; this.lastFailureTime Date.now(); } } const breaker new CircuitBreaker(); // 在请求前调用 if (!breaker.canProceed()) { console.log(❌ Circuit breaker OPEN, skipping request); return; } try { const res await fetch(...); if (res.status 403) { breaker.recordFailure(); } } catch (e) { breaker.recordFailure(); }6.3 微信消息推送用 Server酱5 行代码接入下单成功后用 Server酱推送到微信比邮件快 10 倍// utils/wechat-notify.js async function sendWeChatNotify(title, content) { const SCKEY process.env.SCKEY; // Server酱 SCKEY if (!SCKEY) return; await fetch(https://sctapi.ftqq.com/${SCKEY}.send, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ title, desp: content }) }); } // 下单成功后调用 await sendWeChatNotify( ✅ 京东下单成功, SKU: ${skuId}\n订单号: ${orderNo}\n时间: ${new Date().toLocaleString()} );配置注册 Server酱sct.ftqq.com获取 SCKEY设为环境变量export SCKEYyour_sckey。我坚持把 jd-happy 当作一个“可丢弃的胶水服务”来维护它不追求 100% 成功率而是保证在 95% 的缺货场景下比人工快 3 秒完成下单它不试图对抗京东风控而是用最轻量的方式在规则缝隙里争取确定性。每次 h5st 密钥轮换我都花 10 分钟更新脚本而不是等它崩在关键节点——这种“小步快跑”的节奏才是工程落地的真实心跳。希望帮到你。本文还有配套的精品资源点击获取
返回列表