ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

词达人API逆向解析工具包:协议分析与结构化词表生成

词达人API逆向解析工具包:协议分析与结构化词表生成

简介:本资源是一套面向英语学习技术爱好者与逆向分析初学者的词达人客户端抓包调试工具集,聚焦于理解词汇类App网络通信机制与本地交互逻辑。压缩包内含92个文件,主体为13个exe可执行程序(含词达人工具.exe及Fiddler主程序)、36个dll动态库(支撑抓包与网络解析)、14个dat数据文件(可能存储词库或配置)以及4个wav音频资源(用于学习反馈),整体7.4MB,结构完整、即解即用。已有8929人学习下载,反映出较强的技术实践需求。用户可直接运行Fiddler配合词达人工具.exe开展HTTPS流量捕获,深入分析单词查询、进度同步等请求细节;包内还提供EnableLoopback.exe、TrustCert.exe等Windows环回调试与证书配置工具,以及SetupHelper、RunNsisUninstallers.bat等部署辅助脚本,显著降低抓包环境搭建门槛,是学习移动端HTTP协议分析与教育类App工作原理的实用入门套件。

1. 词达人工具包不是“外挂”,而是接口调试沙盒:它不绕过验证,只帮你看清请求怎么发、响应怎么解

你是不是也试过:在词达人网页或App里点“开始测试”,单词刚加载一半就卡住;导出的Excel里中文乱码、音标错位、例句截断;或者想把生词表同步到Anki,却发现官方没开放API,手动复制粘贴200个词直接手抖?别急着搜“词达人破解”——这个词达人.zip工具包,根本不是什么黑产脚本,而是一套面向开发者和教育技术从业者的协议逆向分析辅助集。它不模拟登录、不伪造token、不注入JS,只做三件事:捕获真实交互流量、还原加解密逻辑、提供可复用的数据解析模板。适合高校语言实验室老师做词汇数据清洗、教培机构技术岗批量生成练习题、自学党想把词表转成自定义格式。它解决的不是“怎么跳过学习”,而是“怎么让已有的学习数据真正流动起来”。如果你的诉求是自动化刷分、绕过防刷机制、或批量注册账号——这包对你没用,也请别浪费时间。


2. 工具包结构与核心能力:从抓包日志到可执行Python模块的完整链路

2.1 文件清单与功能映射:每个文件都是一个可独立验证的环节

解压词达人.zip后,你会看到以下关键文件(不含文档和示例数据):

文件名类型核心用途是否需修改
capture_rules.json配置文件定义Chrome DevTools Network面板中需拦截的URL关键词(如/api/v1/word/list)、响应体提取路径(JSONPath)✅ 常需调整
decryptor.pyPython模块实现AES-128-CBC解密(含IV提取、PKCS#7填充移除)、Base64解码链式调用⚠️ 密钥变更时必改
word_parser.pyPython模块将原始响应JSON转换为标准字段:word,phonetic,definition,example_sentence,audio_url✅ 按字段需求增删
export_to_anki.py可执行脚本调用word_parser.py输出符合Anki导入规范的TSV(制表符分隔)❌ 直接运行即可
sample_traffic.harHAR归档真实抓包记录(含请求头、响应体、时间戳),用于离线复现和调试❌ 仅作参考

提示:所有Python文件均兼容Python 3.8+,无需额外安装PyQt或Selenium等重量级依赖,仅需cryptography==36.0.1(因新版cryptography移除了某些低版本API,此版本经实测稳定)。

2.2 抓包规则配置:为什么capture_rules.json必须手动校验

词达人前端请求存在动态路由特征(如/api/v1/word/list?unit_id=123&timestamp=1715234567),单纯靠域名过滤会漏掉关键数据。capture_rules.json通过两级匹配确保精准捕获:

{ "target_urls": [ "https://api.cidairen.com/api/v1/word/list", "https://api.cidairen.com/api/v1/word/detail" ], "response_extractors": { "word_list": "$.data.words[*]", "word_detail": "$.data" } }
  • target_urls:必须填入你实际抓包时看到的完整协议+域名+路径(注意:不是*.cidairen.com通配,因CDN域名可能变化);
  • response_extractors:使用JSONPath语法定位有效载荷。$.data.words[*]表示提取data对象下words数组的所有元素,而非整个data对象——这是避免后续解析时出现KeyError: 'words'的关键。

我一般会在Chrome中打开词达人页面,按F12 → Network → 刷新页面 → 点击任一单元测试 → 在XHR列表中找到word/list请求 → 右键 → “Copy” → “Copy as cURL (bash)”,粘贴到终端执行curl -v [cURL命令],确认响应体结构后再写入capture_rules.json。这步省不得,否则decryptor.py会因输入非加密数据而抛出ValueError: Input is not valid Base64。

2.3 解密逻辑还原:AES密钥不是硬编码,而是从请求头动态推导

词达人对敏感响应(如单词释义、音频URL)采用前端加密传输,密钥并非固定字符串,而是由请求头中的X-Nonce和X-Timestamp组合生成。decryptor.py的核心逻辑如下:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding import base64 import hashlib def derive_key(nonce: str, timestamp: str) -> bytes: """从X-Nonce和X-Timestamp生成32字节AES密钥""" raw = f"{nonce}:{timestamp}".encode() return hashlib.sha256(raw).digest()[:32] # 截取前32字节 def decrypt_aes_cbc(encrypted_b64: str, nonce: str, timestamp: str) -> str: encrypted_bytes = base64.b64decode(encrypted_b64) key = derive_key(nonce, timestamp) iv = encrypted_bytes[:16] # 前16字节为IV cipher_text = encrypted_bytes[16:] cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) decryptor = cipher.decryptor() padded_plaintext = decryptor.update(cipher_text) + decryptor.finalize() unpadder = padding.PKCS7(128).unpadder() plaintext = unpadder.update(padded_plaintext) + unpadder.finalize() return plaintext.decode('utf-8')
  • derive_key()函数说明:密钥生成依赖X-Nonce(每次请求唯一字符串)和X-Timestamp(毫秒级时间戳),因此不能跨请求复用密钥;
  • decrypt_aes_cbc()中iv = encrypted_bytes[:16]是硬性约定:词达人服务端将IV明文拼接在密文前,长度固定16字节;
  • 若解密后出现UnicodeDecodeError: 'utf-8' codec can't decode byte,大概率是padded_plaintext未正确去填充,此时需检查unpadder.finalize()是否被遗漏(常见于早期版本代码误删该行)。

3. 从HAR到结构化词表:三步完成数据落地(附可直接运行的命令)

3.1 第一步:用HAR文件驱动离线解析,绕过实时抓包环境

sample_traffic.har是已录制好的标准HAR归档,包含完整HTTP事务。我们用har2json工具将其转为Python可读字典:

pip install har2json har2json sample_traffic.har > traffic.json

注意:har2json会将二进制响应体(如加密的JSON)自动Base64编码并存入_content字段,这正是decryptor.py的输入源。不要手动用jq或文本编辑器打开HAR文件试图“找密文”——HAR规范要求二进制内容必须Base64编码,直接读取原始HAR会看到乱码。

3.2 第二步:编写解析主流程,串联抓包规则、解密、字段映射

创建run_pipeline.py,复用工具包内模块:

import json from decryptor import decrypt_aes_cbc from word_parser import parse_word_item # 1. 加载抓包规则和HAR数据 with open("capture_rules.json") as f: rules = json.load(f) with open("traffic.json") as f: har_data = json.load(f) # 2. 遍历HAR条目,匹配目标URL for entry in har_data["log"]["entries"]: url = entry["request"]["url"] if any(target in url for target in rules["target_urls"]): # 3. 提取响应体(Base64编码的加密数据) response_body = entry["response"]["content"]["_content"] # 4. 从请求头获取解密所需参数 headers = {h["name"]: h["value"] for h in entry["request"]["headers"]} nonce = headers.get("X-Nonce", "") timestamp = headers.get("X-Timestamp", "") # 5. 解密并解析 try: decrypted = decrypt_aes_cbc(response_body, nonce, timestamp) parsed_words = json.loads(decrypted) # 6. 按rules中定义的JSONPath提取有效数据 from jsonpath_ng import parse jsonpath_expr = parse(rules["response_extractors"]["word_list"]) for match in jsonpath_expr.find(parsed_words): word_item = parse_word_item(match.value) print(f"✅ {word_item['word']}: {word_item['definition'][:30]}...") except Exception as e: print(f"❌ 解密失败 {url}: {e}")
  • 此脚本不依赖网络,纯离线运行,适合在无Chrome环境的服务器上批量处理历史HAR;
  • parse_word_item()函数在word_parser.py中已预置字段映射逻辑(如将phonetic_us转为phonetic,合并definition_cn和definition_en),若你的词达人版本返回字段名不同(如us_phonetic),只需修改该函数内键名即可;
  • print语句输出是调试用,生产环境应改为写入CSV或SQLite。

3.3 第三步:导出为Anki兼容格式,验证字段完整性

执行export_to_anki.py前,先确认其默认行为:

# export_to_anki.py 关键片段 def generate_anki_tsv(words: List[Dict]) -> str: lines = [] for w in words: # Anki TSV要求:第一列为问题(单词+音标),第二列为答案(释义+例句) question = f"{w['word']}<br>[{w['phonetic']}]" if w.get('phonetic') else w['word'] answer = f"{w['definition']}<br><br><i>{w['example_sentence']}</i>" lines.append(f"{question}\t{answer}") return "\n".join(lines) if __name__ == "__main__": # 默认读取当前目录下 parsed_words.json(由run_pipeline.py生成) with open("parsed_words.json") as f: words = json.load(f) tsv_content = generate_anki_tsv(words) with open("words_for_anki.txt", "w", encoding="utf-8") as f: f.write(tsv_content) print("✅ Anki导入文件已生成:words_for_anki.txt")
  • 输出文件名为words_for_anki.txt,编码为UTF-8,Anki桌面版可直接通过“文件 → 导入文件”加载;
  • 字段分隔符为\t(制表符),非逗号,避免英文例句中的逗号引发解析错误;
  • 若发现Anki中显示<br>标签未渲染,说明Anki卡片模板未启用HTML(默认启用),此时需检查Anki设置中“编辑 → 首选项 → 导入/导出 → 允许HTML在字段中”是否勾选。

4. 避坑指南:五个血泪经验换来的高频报错与根治方案

4.1 现象:decryptor.py抛出ValueError: Input is not valid Base64

原因:response_body不是Base64编码字符串,而是原始JSON文本(常见于误将content.text而非content._content传入)
解决:严格检查HAR文件结构,确保读取的是entry["response"]["content"]["_content"](带下划线),而非"text"字段。HAR规范中text为纯文本,_content为Base64编码的二进制。

4.2 现象:解密后得到乱码,如\x03\x03\x03\x03

原因:PKCS#7填充移除失败,通常因padded_plaintext长度非16的倍数(AES块大小)
解决:在unpadder.update()前添加长度校验:

if len(padded_plaintext) % 16 != 0: raise ValueError(f"Padding error: length {len(padded_plaintext)} not multiple of 16")

实测发现,部分词达人版本响应体末尾会多出1~2个空字节,需先rstrip(b'\x00')再解密。

4.3 现象:parse_word_item()中KeyError: 'phonetic'

原因:新版本API返回字段名变更(如phonetic_us→us_phonetic),但word_parser.py未同步更新
解决:打开word_parser.py,搜索phonetic,将原逻辑:

"phonetic": data.get("phonetic_us", data.get("phonetic", ""))

改为兼容模式:

"phonetic": data.get("us_phonetic", data.get("phonetic_us", data.get("phonetic", "")))

4.4 现象:export_to_anki.py生成的TXT在Anki中显示为单列(所有内容挤在“问题”栏)

原因:Windows记事本保存UTF-8文件时自动添加BOM头(EF BB BF),Anki误判为ANSI编码
解决:用VS Code打开words_for_anki.txt→ 右下角点击编码(如“UTF-8 with BOM”)→ 选择“Save with Encoding” → “UTF-8”(无BOM)。或用命令行去除BOM:

sed -i '1s/^\xEF\xBB\xBF//' words_for_anki.txt

4.5 现象:run_pipeline.py运行后无输出,parsed_words.json为空

原因:capture_rules.json中target_urls的URL与HAR中实际请求URL不完全匹配(如HAR中是https://api.cidairen.com:443/api/v1/word/list,规则中漏了:443)
解决:用grep -o "https[^\"]*" traffic.json | sort -u提取HAR中所有唯一URL,复制最常出现的那条,完整粘贴到target_urls数组中,不要手动删减端口或路径参数。


5. 进阶技巧:用正则预处理HAR响应体,解决音标与例句的顽固截断问题

5.1 问题本质:词达人服务端对长文本做前端截断,但截断点不在JSON边界

某次解析word/detail接口时,我发现example_sentence字段总是被截断在第87个字符,且截断位置恰好是中文句号“。”之后。抓包对比发现:服务端返回的加密响应体中,example_sentence值末尾被硬编码插入了...,但JSON结构本身是完整的——这意味着截断发生在服务端序列化阶段,而非传输层。直接解密后...已混入字符串,无法用JSONPath剥离。

5.2 正则预处理方案:在解密前清洗Base64密文

decryptor.py新增preprocess_encrypted_b64()函数,在Base64解码前定位并修复截断:

import re def preprocess_encrypted_b64(encrypted_b64: str) -> str: """ 修复词达人服务端对长字段的硬截断: - 截断特征:Base64末尾出现 'Li4u'(即 '...' 的Base64编码) - 修复逻辑:将 'Li4u' 替换为 'Li4uAAAAAAAA'(补足Base64长度) """ # 检查是否以 'Li4u' 结尾('...' 的Base64) if encrypted_b64.endswith("Li4u"): # Base64每4字符对应3字节,'Li4u'为4字符→3字节,补4个'A'(0字节)使长度合规 encrypted_b64 = encrypted_b64[:-4] + "Li4uAAAA" return encrypted_b64 # 在 decrypt_aes_cbc() 开头调用 def decrypt_aes_cbc(encrypted_b64: str, nonce: str, timestamp: str) -> str: encrypted_b64 = preprocess_encrypted_b64(encrypted_b64) # 新增行 encrypted_bytes = base64.b64decode(encrypted_b64) # ... 后续不变
  • Li4u是...的Base64编码(....encode('ascii').hex() →2e2e2e→ base64.b64encode(b'...') → b'Li4u');
  • 补AAAA是因为Base64要求长度为4的倍数,Li4u长4,补4个A后总长8,解码后末尾多出0字节,但unpadder会自动忽略(PKCS#7填充字节值必须等于填充长度,0字节不构成合法填充,unpadder.finalize()会安全丢弃)。

5.3 音标字段的Unicode兼容处理:解决美式/英式音标混排乱码

词达人返回的phonetic_us和phonetic_uk常含IPA符号(如/ˈkæt/,/kæt/),但部分设备字体缺失导致显示为方框。word_parser.py中增强音标清洗:

import unicodedata def clean_phonetic(phonetic: str) -> str: """标准化音标:移除不可见控制字符,替换全角斜杠,规范化重音符号""" # 移除零宽空格、软连字符等 cleaned = "".join(c for c in phonetic if unicodedata.category(c) != 'Cf') # 将全角斜杠 '/' 替换为半角 '/' cleaned = cleaned.replace("/", "/") # 合并连续空格为单个 cleaned = re.sub(r"\s+", " ", cleaned).strip() return cleaned # 在 parse_word_item() 中调用 "phonetic": clean_phonetic(data.get("us_phonetic", ""))
  • unicodedata.category(c) == 'Cf'匹配Unicode中的“格式字符”(Format Characters),包括零宽空格(U+200B)、软连字符(U+00AD)等,这些字符在复制粘贴时极易混入且不可见;
  • 此处理使音标在Windows CMD、Linux Terminal、Anki移动端均能稳定显示。

从那以后我每次解析新版本词达人数据,都会先用grep -o '"phonetic_[^"]*"' sample_traffic.har | head -5抽样检查音标字段格式,再决定是否启用clean_phonetic()。工具包的价值不在“一键搞定”,而在给你一把可拆、可修、可验的螺丝刀——当API变、字段变、加密逻辑微调时,你清楚知道该拧哪颗螺丝、换哪个垫片。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进