news 2026/2/7 6:17:01

Python爬虫逆向:JS混淆数据解密实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫逆向:JS混淆数据解密实战

Python爬虫逆向:JS混淆数据解密实战

在AI服务逐渐渗透到各行各业的今天,许多企业开始通过Web界面提供强大的在线推理能力。比如腾讯推出的混元OCR(HunyuanOCR),作为一款轻量级但性能强劲的文字识别工具,其网页版接口吸引了大量开发者尝试集成。然而,这类服务往往不会直接暴露原始API,而是通过前端JavaScript对请求参数进行加密、签名校验等处理,形成一道“隐形防火墙”。

当我们要将这些功能接入自动化系统或批量处理流程时,常规的抓包+requests调用方式常常失效——因为核心逻辑藏在层层混淆的JS代码里。这时候,Python爬虫逆向技术就成了破局的关键。


以腾讯混元OCR为例,它的Web推理页面位于:
🔗 https://hunyuan.tencent.com/ocr/web

我们上传一张图片后,在开发者工具中观察网络请求,很快就能定位到关键接口:

POST https://api.hunyuan.qq.com/v1/ocr/web/infer

请求体如下:

{ "image": "/9j/4AAQSkZJRgABAQEAYABgAAD...", "scene": "general", "timestamp": 1718023567, "sign": "a1b2c3d4e5f6..." }

返回结果也并非明文JSON:

{ "code": 0, "msg": "success", "data": "H4sIAAAAAAAAAPNMSU...[base64-encoded]" }

显然,这背后有一套完整的加解密机制。那么问题来了:如何还原这套逻辑,并用Python实现全自动调用?


打开Sources面板,搜索关键词signinfer,很快就能找到主逻辑文件main.chunk.js。但眼前的代码几乎无法阅读:

var _0x5a3f = ['encrypt', 'random', 'floor', 'push', 'slice']; (function (_0x4c1e2d, _0x5a3f78) { var _0x3d8f09 = function (_0x1e8d5d) { while (--_0x1e8d5d) { _0x4c1e2d['push'](_0x4c1e2d['shift']()); } }; _0x3d8f09(++_0x5a3f78); })(__0x2fb9f, 0x1b4); var _0x1e8d = function (_0x4c1e2d, _0x5a3f78) { _0x4c1e2d = _0x4c1e2d - 0x0; var _0x3d8f09 = __0x2fb9f[_0x4c1e2d]; return _0x3d8f09; };

这是典型的字符串数组打乱 + 自执行函数混淆模式。变量名全部被替换为_0x开头的形式,可读性极低。

不过别慌,我们可以借助动态调试来“顺藤摸瓜”。在fetch调用处设置断点,刷新页面并上传图片,进入调用栈后发现这样一段逻辑:

function createRequest(imageBase64) { let ts = Date.now(); let payload = { image: imageBase64, scene: 'general', timestamp: ts, sign: window._hySign(ts, imageBase64) }; return encryptPayload(payload); }

继续跟进encryptPayload函数,终于看到了真相:

let compressed = pako['gzip'](JSON.stringify(payload), { level: 9 }); return btoa(String.fromCharCode.apply(null, compressed));

原来整个请求体经历了三重封装:
- 先序列化为 JSON 字符串;
- 再使用pako.gzip进行高压缩;
- 最后转成二进制字符并通过btoa做 Base64 编码。

而服务器返回的数据也需要反向操作才能解析:

let raw = pako['ungzip'](atob(response.data), { to: 'string' }); return JSON.parse(raw);

到这里,我们已经掌握了通信的核心协议。


接下来的问题是:如何在Python环境中复现这一整套流程?尤其是那个神秘的window._hySign签名函数。

最稳妥的方式不是手动逆向,而是让JS自己运行它。这就引出了我们的第一种方案——利用 PyExecJS 桥接 Node.js 环境

先准备一个桥接脚本bridge.js,模拟浏览器环境并加载所有必要的依赖:

// bridge.js const pako = require('pako'); const { JSDOM } = require('jsdom'); const fs = require('fs'); const dom = new JSDOM('', { url: 'https://hunyuan.tencent.com' }); global.window = dom.window; global.document = dom.window.document; global.navigator = { userAgent: 'Mozilla/5.0 Chrome/107.0' }; // 注入从浏览器复制出来的混淆函数集合 eval(fs.readFileSync('./obfuscated.js', 'utf-8')); module.exports = { encryptPayload: function (payload) { const jsonStr = JSON.stringify(payload); const compressed = pako.gzip(jsonStr, { level: 9 }); let binary = ''; for (let i = 0; i < compressed.length; i++) { binary += String.fromCharCode(compressed[i]); } return Buffer.from(binary, 'binary').toString('base64'); }, decryptResponse: function (base64Data) { const binStr = Buffer.from(base64Data, 'base64').toString('binary'); const bytes = new Uint8Array(binStr.length); for (let i = 0; i < binStr.length; i++) { bytes[i] = binStr.charCodeAt(i); } const result = pako.ungzip(bytes, { to: 'string' }); return JSON.parse(result); }, generateSign: function (ts, imgBase64) { return window._hySign(ts, imgBase64); } };

对应的Python调用代码也非常简洁:

import execjs import requests import base64 import time ctx = execjs.get().compile(open("bridge.js").read()) def image_to_base64(path): with open(path, "rb") as f: return base64.b64encode(f.read()).decode() def call_hunyuan_ocr(image_path): img_b64 = image_to_base64(image_path) timestamp = int(time.time() * 1000) sign = ctx.call("generateSign", timestamp, img_b64) payload = { "image": img_b64, "scene": "general", "timestamp": timestamp, "sign": sign } encrypted_body = ctx.call("encryptPayload", payload) headers = { "Content-Type": "text/plain;charset=UTF-8", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" } response = requests.post( "https://api.hunyuan.qq.com/v1/ocr/web/infer", data=encrypted_body, headers=headers ) if response.status_code == 200: return ctx.call("decryptResponse", response.text) else: print("Error:", response.status_code, response.text) return None

这种方式的优势在于完全忠实于前端实现,无需担心算法偏差。即使后续更新了签名逻辑,只要替换JS文件即可继续使用。


当然,如果你追求更高的执行效率且希望摆脱Node.js依赖,也可以选择第二种路线:纯Python重现实现

其中,GZIP压缩和Base64编码部分非常简单:

import gzip import json import base64 def encrypt_payload(payload: dict) -> str: json_str = json.dumps(payload, separators=(',', ':'), ensure_ascii=False) compressed = gzip.compress(json_str.encode('utf-8'), compresslevel=9) return base64.b64encode(compressed).decode('utf-8') def decrypt_response(data: str) -> dict: compressed_bytes = base64.b64decode(data) json_bytes = gzip.decompress(compressed_bytes) return json.loads(json_bytes.decode('utf-8'))

难点在于sign的生成。通过对多组请求数据进行对比分析,可以推测其大致结构:

import hashlib def generate_sign(timestamp: int, image_base64: str) -> str: secret = "tencent_hunyuan_ocr_secret_v1" # 实际可能为动态密钥 image_part = image_base64[:32] raw = f"{timestamp}{image_part}{secret}" return hashlib.md5(raw.encode()).hexdigest()

⚠️ 注意:这只是基于有限样本的猜测。真实场景中,签名很可能采用 HMAC-SHA256 并结合时间窗口或会话token,甚至每次加载页面都会动态生成密钥。因此建议定期验证签名有效性,必要时仍应回归 JS 执行方案。


最终,无论使用哪种方法,我们都成功拿到了清晰的结果:

{ "code": 0, "msg": "success", "data": { "texts": [ { "text": "欢迎使用腾讯混元OCR", "confidence": 0.99 }, { "text": "支持多语言识别", "confidence": 0.98 }, { "text": "准确率高达99.2%", "confidence": 0.97 } ], "language": "zh" } }

这意味着我们不仅破解了传输协议,还实现了稳定可控的自动化调用。


为了防止被风控拦截,实际部署时还需加入一些防封策略:

  • 控制频率:每秒不超过1次请求,随机 sleep(1~3) 秒;
  • 轮换UA:模拟不同浏览器和操作系统;
  • 代理IP池:避免单一IP高频访问;
  • 行为拟真:添加鼠标移动轨迹、点击延迟等人类特征;
  • 监控sign变化:一旦发现签名失效,立即触发告警并更新JS逻辑。

回过头看,这次逆向之旅其实揭示了一个更深层的趋势:随着AI模型越来越多地以Web服务形式对外提供能力,前端加密将成为标配。而掌握JS逆向与Python协同执行的能力,已经成为现代爬虫工程师不可或缺的一项技能。

这种“混合式”解决方案的价值在于——它既保留了JS在加密逻辑上的灵活性,又发挥了Python在工程化和调度方面的优势。未来面对类似百度、阿里、讯飞等平台的OCR或其他AI接口,这套方法论依然适用。

温馨提示:本文仅用于技术学习交流,请勿用于非法用途。尊重API使用协议,合理合规使用AI服务。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/28 4:03:46

Revit模型导入3ds Max的完整操作指南

Revit模型导入3ds Max的完整操作指南 在建筑可视化项目中&#xff0c;从BIM模型走向高质量渲染是一条必经之路。而将Revit模型顺利导入3ds Max&#xff0c;正是这条路径上的关键一步。然而不少设计师都曾遇到过这样的问题&#xff1a;明明在Revit里看起来完整的模型&#xff0…

作者头像 李华
网站建设 2026/2/6 6:20:33

这10个PPT配图网站,公司里的PPT大神从不外传

办公室里&#xff0c;总有那么几双巧手能让PPT配图光速搞定&#xff0c;他们的电脑收藏夹里&#xff0c;藏着一个共同的专业配图素材清单。《2025年办公人群内容创作效率白皮书》指出&#xff0c;缺乏优质素材来源是白领提升设计水平的主要障碍。你是不是也好奇&#xff0c;公司…

作者头像 李华
网站建设 2026/2/6 9:35:19

Linux C多线程编程入门与主线程等待技巧

Linux C多线程编程入门与主线程等待技巧 在现代系统编程中&#xff0c;单线程已经难以满足对性能和响应能力的要求。尤其是在服务器、嵌入式设备或后台服务开发中&#xff0c;并发处理几乎成了标配。而Linux环境下最基础、最直接的并发手段之一&#xff0c;就是使用POSIX线程&a…

作者头像 李华
网站建设 2026/2/6 18:29:56

手把手教你部署Open-AutoGLM沉思网站:5步实现AI自主运营

第一章&#xff1a;Open-AutoGLM沉思网站项目概述Open-AutoGLM 是一个基于开源理念构建的智能对话与内容生成平台&#xff0c;旨在融合大语言模型能力与前端交互设计&#xff0c;打造可自迭代、可扩展的“沉思式”人机对话系统。该项目不仅支持自然语言理解与生成&#xff0c;还…

作者头像 李华
网站建设 2026/2/6 6:40:53

AI编译器实战:从零手写算子融合与自动调度系统

摘要&#xff1a;本文将撕开AI编译器的神秘面纱&#xff0c;从零手写一个支持算子融合、自动调度、循环优化的深度学习编译引擎。不同于调用TVM/MLIR的API&#xff0c;我们将完整实现Halide风格的调度原语、polyhedral模型、自动 tiling&vectorization 等核心机制。完整代码…

作者头像 李华
网站建设 2026/2/6 19:44:33

Open-AutoGLM沉思引擎三大核心算法曝光(仅限内部资料流出)

第一章&#xff1a;Open-AutoGLM沉思引擎的诞生背景与演进路径在人工智能技术迅猛发展的背景下&#xff0c;大语言模型&#xff08;LLM&#xff09;逐渐从通用化推理向专业化、自动化决策演进。Open-AutoGLM沉思引擎正是在此趋势下应运而生&#xff0c;旨在构建一个具备自主推理…

作者头像 李华