首页 / 资讯中心 / 文章详情

用抓包与SQLite把词达人词表变成本地可复习词库

用抓包与SQLite把词达人词表变成本地可复习词库 ★ FEATURED ARTICLE
简介词达人工具.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(slotsTrue) 上会直接报错 python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate python -m pip install -U pip # 采集侧负责把流量原样落成 HAR 或 flow 文件 pip install mitmproxy10 # 解析与导出侧 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-CHAR 才会落盘 # 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_retriesRetry( total3, backoff_factor0.6, # 重试间隔 0.6s、1.2s、2.4s status_forcelist[429, 500, 502, 504], allowed_methods[GET], # 只对读请求重试写请求重试会出脏数据 ))) HEADERS { # 这条最关键请求头不一致时服务端会返回精简字段音标和例句直接没有 User-Agent: 复刻你在流列表里看到的原值, Accept-Language: zh-CN,zh;q0.9, Referer: https://你的目标站点/, } def get_json(url: str, **params): time.sleep(0.8) # 限速实测低于 0.5s 时后半段响应会开始变空 r s.get(url, headersHEADERS, paramsparams, 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(parentsTrue, exist_okTrue) body json.dumps(payload, ensure_asciiFalse, sort_keysTrue) # 用内容哈希命名同一份响应重复抓到会自然合并不会堆重复文件 fp hashlib.sha1(body.encode(utf-8)).hexdigest()[:16] (d / f{flow_id}-{fp}.json).write_text(body, encodingutf-8) return fpsort_keysTrue不能省。同样的数据如果键顺序不同哈希就不一样去重会失效你会得到两份内容相同名字不同的文件后面排查时非常迷惑。ensure_asciiFalse保证中文原样落盘方便直接用编辑器打开核对。有了这层存档解析层就是个纯函数输入是目录里的 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(parentsTrue, exist_okTrue) cols [lemma, pos, sense_index, sense_cn, phonetic_uk, phonetic_us, example, book_id, unit] # utf-8-sig 带 BOMExcel 直接双击打开不乱码 with open(out, w, encodingutf-8-sig, newline) as f: w csv.DictWriter(f, fieldnamescols, extrasactionignore) w.writeheader() for r in rows: w.writerow({k: ( if r.get(k) is None else r[k]) for k in cols})newline也是必须的不加的话在 Windows 上每行之间会多出一个空行。extrasactionignore让上游多出来的字段不至于直接抛异常代价是可能悄悄丢掉有用字段所以第一次跑完要拿行数对一下。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 outsense_index这个字段如果没有现成的就在解析时按数组下标自己编号只要同一份数据每次编号规则一致主键就稳定。稳定主键是复习进度不丢的唯一前提后面第 5 章会有一条坑专门讲这个。5. 避坑与排查六条踩过的坑按现象对号入座下面这六条都是我自己或者身边人真实撞过的按现象写方便你对着症状找原因。5.1 采集阶段的三个坑坑一流列表里全是记录但导进去一条词都没有。现象是导入命令跑完不报错词库里 0 行。原因通常是过滤链路没对上——录的是 CDN 上的静态资源和你自己的埋点真正的数据请求压根没录进去因为过滤器里那个路径正则写死了版本号。解决办法是把过滤条件放宽先只留~m GET全量导一次看哪几条响应里有你要的字段再把正则收紧。坑二只抓到第一页后面全是重复内容。现象是词条数刚好等于一页的条数而且所有记录长得一模一样。原因是分页参数藏在请求体里而不是 query 上你复刻 URL 的时候只抄了 querypage 那部分丢了服务端一直返回默认第一页。解决办法是把第一页和第二页的完整请求含 body做 diff把变化的那个字段提到参数里。坑三音标整列为空释义只有一条。这个最隐蔽因为它不报错数据看起来也是对的只是「不全」。原因是请求头没复刻UA 或 Accept-Language 和客户端不一致时服务端会走精简分支把音标、例句、多义项都裁掉。解决办法是把你的请求头和流列表里那条原始请求逐字段对齐尤其是 UA一个字都不能差。注意不要写自动提交类的脚本。一是这类操作违反服务的使用规则二是服务端对提交行为有独立校验你在本地看到的成功不代表记录真的进去了三是这些假数据会污染你自己的复习队列最后受害的是你的复习计划。5.2 解析与复习阶段的三个坑坑四CSV 用 Excel 打开中文全是乱码。现象很直观词条变成一堆问号或方块。原因是写文件时用了utf-8而不是utf-8-sig缺少 BOMExcel 默认按本地编码解析。解决办法就是把编码改成utf-8-sig注意不是加个参数就完事用 pandas 的话要显式传encodingutf-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.3EF 下限1.3不建议改改了间隔会失控quality 判定4想起3勉强2没想起宁可严一点别自我宽容真正有用的验证方法不是看「今天背了多少」而是看「7 天后还在不在」。我的做法是每周日抽一次历史记录把 7 天前那批词的 quality 拉出来算平均低于 3.5 就说明新词上限给高了或者释义拆得不够细——很多时候背不下来不是记性问题是一个词条里塞了五个义项大脑根本没把它当成一个整体。血泪经验是这条解析脚本改完以后一定先用--dry-run之类的开关把「会插入多少行、会更新多少行、会跳过多少行」打印出来对着原始档的文件数核一遍再去写库。我吃过一次亏一个正则写错把多义项拆成了二十行第二天打开复习队列直接 4000 条那一周的节奏全乱了。从那以后我每次重导词库都强制走一遍 dry-run 加行数核对宁可多花两分钟。包名就是压缩包上的那串搜一下基本只有这一个结果下完先别急着改参数把第 2 章那四条命令跑通看着 CSV 里出现第一行音标再往下折腾。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站