☰
用抓包与SQLite把词达人词表变成本地可复习词库
2026/10/24 4:26:52 网站建设 项目流程

简介:词达人工具.zip 是一份面向英语词汇学习与网络调试场景的整合包,围绕词达人学习工具的运行与抓包分析展开,适合希望了解客户端与服务器数据交互、排查请求响应过程的学习者和开发者。压缩包共92个文件、约7.4MB,以36个dll动态库、13个exe可执行程序、14个dat数据文件及13个pdb调试符号为主,另含wav提示音、config配置、txt说明、ico图标和bat批处理等,目录中可见Fiddler主体程序、插件脚本、证书与导入导出模块,便于按功能定位所需组件。已有8938人学习下载,说明其在词汇学习工具调试与抓包入门方面具有一定参考价值。读者可从中获得可直接运行的词达人工具程序、Fiddler抓包环境及相关插件配置,用于观察单词查询、学习进度同步等请求流程,理解HTTP/HTTPS通信的捕获、查看与重放方法,并借此积累网络分析、证书配置和脚本扩展的实践思路。

1. 词达人工具不是背词 App 的替代品:它解决的是「词表不归你管」这件事

你在词达人里刷了两周,进度条只存在于别人的服务器上:换本书得重来,想导到本地复习没入口,想看看自己到底哪些词反复错也看不到原始记录。这个压缩包做的就是把这层断掉的数据链接回来——用抓包把你账号下已经产生的词表、音标、例句、掌握状态拉成本地文件,落进一个 SQLite 库,再按间隔重复的节奏重排成你自己的复习队列。它不是刷词器,也不是替你去点提交的脚本。适合把词表当长期资产管理的人,比如要跨学期备考、要把词汇并进自己 Anki 库、要按错词频次做专项训练的人;不适合想找个程序替自己把进度跑满的人——那条路在第 5 章会讲清楚为什么走不通,以及硬走会付出什么代价。

2. 拆开这个包:三层结构与本机跑通的最小环境

拿到一个带「抓包」字样的工具包,最容易犯的错是先把代理打开,然后才想清楚我要什么数据。正确的顺序其实是反的:先明确目标数据结构,再倒推需要哪几个请求。这个包的三层划分刚好对应这个顺序。

2.1 采集层、词库层、复习层,各管什么、为什么必须分开

采集层的活是把客户端和服务器之间的响应原样留下来,它天然不稳定——接口会改、字段会加、分页规则会调,所以这一层不能有任何业务逻辑,只管存。词库层的活是把存下来的东西解析成稳定的、可查询的结构,它要能长期放,所以主键必须选业务字段而不是自增 id。复习层的活是决定今天该背哪些词,它是最容易被替换的一层,今天用简化的 SM-2,明天想换 FSRS,换掉它不影响前两层。

分开的好处很直接:采集翻车了不用动词库,解析写错了不用重新抓一遍,复习算法不满意不用重导数据。我见过不少人把三步写成一个脚本,跑通了很开心,第二天接口一变字段,整个脚本从头改到尾,之前抓的数据因为没落原始档也一起废了。

层输入输出出问题时的典型表现可替换性
采集层客户端请求与响应原始 JSON / HAR只有第一页、字段缺失、乱码高,换抓包工具无影响
词库层原始 JSONSQLite 表重复词条、主键冲突、中文乱码中,换库要重跑解析
复习层词库 + 复习记录今日队列、下次复习日进度第二天重置、队列为空高,算法可整体替换

2.2 本机环境:解释器、依赖、端口三件事

先说解释器。包里的解析脚本用到了 dataclass 的 slots 和一些类型标注语法,3.9 以下会直接抛 TypeError,别在这上面浪费时间,装个 3.10 或更新的版本。虚拟环境一定要建,因为抓包工具链对 urllib3 的版本比较挑,和系统里其他项目的 requests 撞版本是很常见的事故。

# 3.10 以上,低版本在 dataclass(slots=True) 上会直接报错 python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate python -m pip install -U pip # 采集侧:负责把流量原样落成 HAR 或 flow 文件 pip install "mitmproxy>=10" # 解析与导出侧 pip install requests pandas openpyxl

三条命令的含义分别是:建隔离环境、升级包管理器、装两套依赖。mitmproxy 单独装是因为它的依赖树比较重,和 pandas 一起装容易在 urllib3 上打架,万一冲突了,把采集和解析放到两个环境里也完全没问题,它们之间只靠 HAR 文件通信。

端口这事值得单独提一句。8899 不是官方默认端口,是我故意挑的——8080、8888、1080 这几个端口在你机器上大概率已经被别的开发服务占了,代理起不来的时候报的错往往不是「端口被占用」,而是「连接被拒绝」,很容易往证书方向排查,白花半小时。

2.3 第一次跑通:从解压到手里有一份能看的 CSV

不要一上来就改配置。按下面四步先跑通一遍最小闭环,确认每一环都能出东西,再去调参数。

# 1) 起一个录制代理,端口 8899,流量落成 HAR # hardump 会在退出时统一写盘,中途别 kill -9 mitmdump -p 8899 --set hardump=./raw/session.har # 2) 让客户端走这个代理,然后正常操作一遍词表页面(翻几页、切本书) # 操作完回终端 Ctrl-C,HAR 才会落盘 # 3) 把 HAR 解析进本地词库,首次运行会自动建表 # 入口模块名以包内实际为准,这里统一写成 vocab python -m vocab.cli import --har ./raw/session.har --db ./data/vocab.db # 4) 先导一份 CSV 出来肉眼验,别急着进复习流程 python -m vocab.cli export --db ./data/vocab.db --format csv --out ./out/words.csv

第三步的--db指向的目录必须存在,工具不会帮你建父目录,这是第一次跑最常见的报错来源。第四步的 CSV 打开后重点看三列:词条本身、音标、释义条数。音标整列为空说明请求头没复刻对(见 3.3),释义只有一条说明多义项被拼成了一串,两种情况都得回头处理,不要带着脏数据往下走。

提示:第一次录制建议先不加过滤规则,全量录一遍。你不知道哪个请求里带了你需要的字段,提前过滤等于提前把线索删掉。

3. 抓包链路怎么搭:代理、证书、过滤规则和四个必须调对的参数

抓包这件事的难点从来不是「抓不到」,而是「抓到了但不是你要的那份」。链路上有三个环节会让结果悄悄变形:证书没装好会直接断,过滤写早了会漏,请求头不一致会让服务端返回一份字段更少的版本。这三个都不是报错,都是静默降级,所以排查的时候不能只看有没有数据。

3.1 代理与证书:链路断了先看这三处

# 带 Web 界面的录制代理,confdir 指定配置目录,方便重复使用同一套证书 mitmweb -p 8899 --set confdir=./mitm-conf

第一次启动会在 confdir 下生成一张 CA 证书文件。这张证书要装进当前这台机器的受信任根证书区,不是装进浏览器设置里的「网站证书」,也不是双击打开看一眼就算装了。装错位置的表现是:浏览器能正常上网,但录制列表里一条 HTTPS 都是灰的或者干脆没有。

第二处要看的是「代理是否真的生效」。判断方法很简单,在客户端里随便开一个页面,回到 mitmweb 的流列表看有没有新记录。没有记录就是流量压根没走代理,和证书无关,去查客户端的代理配置和端口是否一致。

第三处是移动端。Android 7 之后,应用默认不信任用户手动安装的证书,链路会直接断在 TLS 握手阶段。这属于客户端侧的证书固定策略,不是配置问题,调参数解决不了,硬啃的性价比很低。我的做法是直接换桌面端或网页端,同一套接口,字段基本一致,省下来的时间够你把解析脚本写完。

现象大概率原因处理方向
流列表为空客户端没走代理 / 端口不一致查代理配置,别动证书
HTTPS 全是灰色根证书没装进受信任区重装 CA,注意装的位置
移动端握手失败客户端不信任用户证书换桌面端或网页端
抓到的响应不可读压缩未解 / 编码不是 UTF-8检查解压与响应头

3.2 过滤规则:把埋点、图片、字体挡在外面

全量录一次、确认目标请求长什么样之后,再上过滤。过滤的价值不在于省磁盘,而在于让你在流列表里一眼看到那几条关键请求,不至于在一堆埋点里翻。

# -f 是过滤表达式:~u 匹配 URL 正则,~m 匹配方法,& 表示同时满足 # 这里的路径片段要换成你自己在流列表里看到的实际路径 mitmdump -p 8899 \ -f '~u "/api/(word|vocab|book|study)" & ~m GET' \ --set hardump=./raw/word.har

表达式里的正则不要写太死。比如把版本号写进去,接口一升级过滤就全空,而你会以为是代理挂了。稳妥的写法是只匹配业务路径里的名词部分,版本段用.*放过。另外~m GET这个条件建议保留——写操作类的请求不该出现在你的采集范围里,这既是数据干净的问题,也是边界问题。

3.3 参数:超时、重试、请求头、限速

这一段是整篇里最值得抄的部分。四个参数调不对,表现分别是:超时太短导致慢接口被判失败、没退避导致 429、请求头不一致导致字段变少、不限速导致后半段全是空响应。

import time import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry s = requests.Session() # 连接超时和读超时必须分开给。连接 3 秒足够,读给 15 秒, # 因为词表接口偶尔要联查,给 5 秒会把正常请求误判成失败 s.mount("https://", HTTPAdapter(max_retries=Retry( total=3, backoff_factor=0.6, # 重试间隔 0.6s、1.2s、2.4s status_forcelist=[429, 500, 502, 504], allowed_methods=["GET"], # 只对读请求重试,写请求重试会出脏数据 ))) HEADERS = { # 这条最关键:请求头不一致时服务端会返回精简字段,音标和例句直接没有 "User-Agent": "<复刻你在流列表里看到的原值>", "Accept-Language": "zh-CN,zh;q=0.9", "Referer": "https://<你的目标站点>/", } def get_json(url: str, **params): time.sleep(0.8) # 限速:实测低于 0.5s 时后半段响应会开始变空 r = s.get(url, headers=HEADERS, params=params, timeout=(3.05, 15)) r.raise_for_status() return r.json()

参数逐个说清楚:timeout传元组是「连接超时, 读超时」,写成单个数字等于两者取同一个值,是很多超时误报的根源。backoff_factor是退避基数,实际间隔是它乘以 2 的幂次,0.6 意味着三次重试总共等不到 5 秒,够用又不至于把整体拖太久。status_forcelist里放 429 是必须的,不放的话第一次被限流就整个任务中断。allowed_methods限制成 GET 是防止写请求被自动重试,这个坑很深,重试一次可能就多提交一条记录。

分页参数单独说。page_size有个服务端上限,超了不会报错,会按默认值返回,你会以为分页失效了。offset的起点是 0 还是 1 每个系统不一样,判断方法很土但有效:把第一页和第二页的请求参数做 diff,看哪个数字变了、变成多少,照着抄就行。

4. 从原始响应到可背词库:字段映射、去重与导出

抓下来的 JSON 是一坨嵌套结构,直接拿去背是不可能的。这一章做的事是把它摊平成一张宽表,并且保证这张表在反复重导的时候不会膨胀、不会把复习进度冲掉。

4.1 先落原始档,再谈解析

解析脚本一定会改。字段名会猜错,多义项会漏拆,词性归一化会写反。如果每次改解析都要重新抓一遍,你很快就会放弃这个项目。所以第一件事是把每份响应按内容哈希存成独立文件。

import json import hashlib import pathlib def save_raw(flow_id: str, payload: dict, root: str = "./raw/payloads") -> str: """把单条响应原样存档,返回内容指纹""" d = pathlib.Path(root) d.mkdir(parents=True, exist_ok=True) body = json.dumps(payload, ensure_ascii=False, sort_keys=True) # 用内容哈希命名:同一份响应重复抓到会自然合并,不会堆重复文件 fp = hashlib.sha1(body.encode("utf-8")).hexdigest()[:16] (d / f"{flow_id}-{fp}.json").write_text(body, encoding="utf-8") return fp

sort_keys=True不能省。同样的数据如果键顺序不同,哈希就不一样,去重会失效,你会得到两份内容相同名字不同的文件,后面排查时非常迷惑。ensure_ascii=False保证中文原样落盘,方便直接用编辑器打开核对。

有了这层存档,解析层就是个纯函数:输入是目录里的 JSON 文件,输出是数据库记录。改解析逻辑只要重跑一遍,不用碰网络。这就是我说的后悔药。

4.2 字段映射表与两种导出格式

原始结构里字段名五花八门,先定一张映射表贴在代码里,比边写边猜靠谱得多。表里留空的几列要显式写成 None,不要用空字符串代替——空字符串在去重和统计里会变成一个真实的取值,后面很难清。

原始字段(以实际为准)本地列类型处理方式
word / spelllemmaTEXT保留原始大小写,另存一列小写用于匹配
ukphone / usphonephonetic_uk / phonetic_usTEXT缺失留 None,不要自己补
trans[].tranCnsense_cnTEXT多义项拆成多行,不要拼成一串
trans[].posposTEXT归一成 n / v / adj / adv 四类
sentence.sContentexampleTEXT只取第一条,避免行数爆炸
bookId + unitbook_id / unitINT组合成来源标签

导出分两条路。给 Excel 看的那份一定要用utf-8-sig,带 BOM,否则在 Windows 上双击打开就是一片乱码,这个坑每年都有人踩。给 Anki 的那份用制表符分隔,不要用逗号——释义里带逗号是常态,用逗号分隔会把一列劈成两列。

import csv import pathlib def export_csv(rows: list[dict], out: str = "./out/words.csv") -> None: pathlib.Path(out).parent.mkdir(parents=True, exist_ok=True) cols = ["lemma", "pos", "sense_index", "sense_cn", "phonetic_uk", "phonetic_us", "example", "book_id", "unit"] # utf-8-sig 带 BOM,Excel 直接双击打开不乱码 with open(out, "w", encoding="utf-8-sig", newline="") as f: w = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore") w.writeheader() for r in rows: w.writerow({k: ("" if r.get(k) is None else r[k]) for k in cols})

newline=""也是必须的,不加的话在 Windows 上每行之间会多出一个空行。extrasaction="ignore"让上游多出来的字段不至于直接抛异常,代价是可能悄悄丢掉有用字段,所以第一次跑完要拿行数对一下。

4.3 去重与主键:三个容易写错的边界

第一,大小写和全角。同一个词条在不同接口里可能一会儿全角一会儿半角,一会儿首字母大写。归一化要做,但只用于生成匹配键,展示用的原值必须另存一列。

第二,词形还原不要直接合并。把 used 合并到 use 看起来很美,实际上 used 作为形容词是独立词条,合并完你会发现例句全对不上。想做词形归并,另开一列存 lemma 原形,主键里不要用它。

第三,同词不同义项必须分行。一个词三个词性、五个义项,如果用词条做主键,写进去的就是互相覆盖,最后只剩一条,而且剩哪条取决于写入顺序,非常玄学。

import re import unicodedata def norm(s: str) -> str: """只用于生成匹配键:全角转半角、合并空白、去首尾""" s = unicodedata.normalize("NFKC", s or "") return re.sub(r"\s+", " ", s).strip() def key_of(row: dict) -> tuple: """业务主键:词 + 词性 + 义项序号 + 来源书 绝不要用自增 id —— 重新导入时 id 会变,复习记录就挂不住了""" return ( norm(row["lemma"]).lower(), (row.get("pos") or "").lower(), int(row.get("sense_index") or 0), int(row.get("book_id") or 0), ) def dedup(rows: list[dict]) -> list[dict]: seen, out = {}, [] for r in rows: k = key_of(r) if k not in seen: # 先到的保留,后到的丢弃 seen[k] = True out.append(r) return out

sense_index这个字段如果没有现成的,就在解析时按数组下标自己编号,只要同一份数据每次编号规则一致,主键就稳定。稳定主键是复习进度不丢的唯一前提,后面第 5 章会有一条坑专门讲这个。

5. 避坑与排查:六条踩过的坑,按现象对号入座

下面这六条都是我自己或者身边人真实撞过的,按现象写,方便你对着症状找原因。

5.1 采集阶段的三个坑

坑一:流列表里全是记录,但导进去一条词都没有。现象是导入命令跑完不报错,词库里 0 行。原因通常是过滤链路没对上——录的是 CDN 上的静态资源和你自己的埋点,真正的数据请求压根没录进去,因为过滤器里那个路径正则写死了版本号。解决办法是把过滤条件放宽,先只留~m GET,全量导一次,看哪几条响应里有你要的字段,再把正则收紧。

坑二:只抓到第一页,后面全是重复内容。现象是词条数刚好等于一页的条数,而且所有记录长得一模一样。原因是分页参数藏在请求体里而不是 query 上,你复刻 URL 的时候只抄了 query,page 那部分丢了,服务端一直返回默认第一页。解决办法是把第一页和第二页的完整请求(含 body)做 diff,把变化的那个字段提到参数里。

坑三:音标整列为空,释义只有一条。这个最隐蔽,因为它不报错,数据看起来也是对的,只是「不全」。原因是请求头没复刻:UA 或 Accept-Language 和客户端不一致时,服务端会走精简分支,把音标、例句、多义项都裁掉。解决办法是把你的请求头和流列表里那条原始请求逐字段对齐,尤其是 UA,一个字都不能差。

注意:不要写自动提交类的脚本。一是这类操作违反服务的使用规则,二是服务端对提交行为有独立校验,你在本地看到的成功不代表记录真的进去了,三是这些假数据会污染你自己的复习队列,最后受害的是你的复习计划。

5.2 解析与复习阶段的三个坑

坑四:CSV 用 Excel 打开中文全是乱码。现象很直观,词条变成一堆问号或方块。原因是写文件时用了utf-8而不是utf-8-sig,缺少 BOM,Excel 默认按本地编码解析。解决办法就是把编码改成utf-8-sig,注意不是加个参数就完事,用 pandas 的话要显式传encoding="utf-8-sig"。

坑五:第二天打开,复习进度全没了。现象是昨天标过的「已掌握」全部回到初始状态,词库行数还翻了一倍。原因是主键用了自增 id,重新导入时 id 重新分配,复习记录表里存的旧 id 找不到对应词条;顺带因为去重键也跟着变了,同一批数据又插了一遍。解决办法是换成 4.3 那套业务主键,并且在导入前先跑一次「按业务主键更新而非插入」的逻辑。

坑六:请求跑到一半开始返回空数组,也不报错。现象是前几百条正常,后面全是[],HTTP 状态码还是 200。原因是没做限速,触发了服务端的软限流——它不给你 429,直接返回空结果。解决办法是加固定间隔(0.8 秒起步)、把 429 放进重试白名单、并且在解析层加一条断言:连续 N 条空结果就主动停下报错,别让它一路空跑到底。

6. 进阶:把词库跑成复习计划,参数怎么定

词库躺在那儿是不会自己变成记忆的。最后一层是调度,我用的是简化的 SM-2,够用、可解释、参数少。核心只有三个量:难度因子 EF、连续答对次数 reps、上次间隔。EF 初始 2.5,下限 1.3,答得差就往下压,压到 1.3 就不再降——再降间隔会退化成每天一次,人会崩。

def next_interval(ef: float, reps: int, last: int) -> int: if reps == 0: return 1 # 第一次见面,明天再见 if reps == 1: return 6 # 第二次,隔 6 天 return max(1, round(last * ef)) # 之后按难度因子滚 def review(ef: float, reps: int, last: int, quality: int): """quality 取 0-5,低于 3 视为没记住,间隔清零重来""" new_ef = max(1.3, ef + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))) if quality < 3: return new_ef, 0, 1 return new_ef, reps + 1, next_interval(new_ef, reps, last)

参数怎么定,我给一张自己长期在用的表:

参数我的取值调整建议
每日新词上限20超过 30 时第二天积压明显,容易断更
每日复习上限200只影响队列截断,不影响调度计算
EF 初始值2.5词表整体偏难可降到 2.3
EF 下限1.3不建议改,改了间隔会失控
quality 判定4=想起,3=勉强,2=没想起宁可严一点,别自我宽容

真正有用的验证方法不是看「今天背了多少」,而是看「7 天后还在不在」。我的做法是每周日抽一次历史记录,把 7 天前那批词的 quality 拉出来算平均,低于 3.5 就说明新词上限给高了,或者释义拆得不够细——很多时候背不下来不是记性问题,是一个词条里塞了五个义项,大脑根本没把它当成一个整体。

血泪经验是这条:解析脚本改完以后,一定先用--dry-run之类的开关把「会插入多少行、会更新多少行、会跳过多少行」打印出来,对着原始档的文件数核一遍,再去写库。我吃过一次亏,一个正则写错把多义项拆成了二十行,第二天打开复习队列直接 4000 条,那一周的节奏全乱了。从那以后我每次重导词库都强制走一遍 dry-run 加行数核对,宁可多花两分钟。

包名就是压缩包上的那串,搜一下基本只有这一个结果,下完先别急着改参数,把第 2 章那四条命令跑通,看着 CSV 里出现第一行音标,再往下折腾。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询