Python解析托业TOEIC词汇PDF:切分、SQLite落库与Anki复习
2026/9/17 13:31:17 网站建设 项目流程

简介:托业TOEIC词汇汇总PDF面向备考托业、商务英语或职场英语的学习者,帮助在有限时间内系统梳理考试高频词与商务场景用语。内容按主题分类编排,涵盖办公室事宜、人事及管理、业务拓展、财务及投资等模块,每单元以中英对照词表呈现,如appointment、attendance、schedule、bonus、deadline、merger、deficit等,便于按场景集中记忆与考前快速复习。压缩包内共1个PDF文件,约42KB,轻量便携,手机或电脑均可随时打开查阅。目前已有422人学习下载。读者可获得一份结构清晰、主题明确的托业核心词库,适合作为词汇书之外的补充清单,用于通勤、课间等碎片时间反复浏览,也可配合真题练习查漏补缺,逐步建立商务语境下的词汇框架。

1. 托业TOEIC词汇汇总.pdf 为什么值得先转成结构化数据

拿到一份几百页的《托业TOEIC词汇汇总.pdf》,多数人的第一反应是打印出来硬背。翻过十页就会发现,同一页里混着单词、音标、词性、两三行中文释义,还有页眉、页码和零星水印。想按首字母检索、想抽出一批商务场景词、想把没记住的挑出来反复看,靠翻页基本做不到。

从工程角度看,这份 PDF 本质是一张排版规整的“半结构化文本”。只要拆成word / phonetic / pos / definition / source_page五个字段,后面能做的事完全不同:按词性筛动词、按释义关键词找商务词、导进 Anki 做间隔重复、起个本地服务随手查词。下面从解析、切分、落库到复习调度走一遍完整链路,适合会写 Python 但没处理过 PDF 的读者。

2. 用 Python 拆开 托业TOEIC词汇汇总.pdf:库选型与最小可跑脚本

2.1 pdfplumber、PyMuPDF、pypdf 在词表类 PDF 上的取舍

词表 PDF 的难点不在文字量,而在排版:单词和音标之间常有几厘米空白,释义可能换行,左右分栏的版本还会交错。不同库对“同一行”的判断标准差别很大。

抽取内核500 页耗时安装依赖词表 PDF 表现
pdfplumberpdfminer.six,暴露字符坐标60~120 秒纯 Python,无编译好,可按坐标还原分栏
PyMuPDF自带 C 层解析内核5 秒以内二进制 wheel好,速度快,坐标 API 略糙
pypdf纯 Python,文本流拼接10~30 秒体积小一般,遇 CID 字体易出乱码

选型思路很直接:词表一次跑完就不动了,优先 pdfplumber,因为extract_text提供了x_tolerancey_tolerance这类细粒度参数,调参直观;文件超过一千页或者需要反复重跑,换 PyMuPDF。

2.2 用 pdfplumber 抽全文的最小可跑代码

import pdfplumber SRC = "托业TOEIC词汇汇总.pdf" with pdfplumber.open(SRC) as pdf: print("总页数:", len(pdf.pages)) page = pdf.pages[0] # x_tolerance 控制同一行内字符的水平合并阈值,越小越容易把一行拆成多行 text = page.extract_text(x_tolerance=1.5, y_tolerance=3) print(text[:600])

逻辑与参数:x_tolerance默认值是 3,词表里单词与音标之间空白很宽,调到 1.5 能让它们留在同一行而不断开;y_tolerance默认 3,决定“多低算换行”,行距紧的版本调到 2 反而更准。不要加layout=True,它按列宽保留空白,适合表格,词表会被撑出大量空格,后续正则要多吃一堆无意义字符。

2.3 逐页落盘成 JSONL,方便断点续跑

import pdfplumber, json SRC = "托业TOEIC词汇汇总.pdf" OUT = "raw_pages.jsonl" with pdfplumber.open(SRC) as pdf, open(OUT, "w", encoding="utf-8") as f: for i, page in enumerate(pdf.pages, start=1): try: txt = page.extract_text(x_tolerance=1.5, y_tolerance=3) or "" except Exception as e: txt = "" print(f"[warn] 第 {i} 页抽取失败: {e}") f.write(json.dumps({"page": i, "text": txt}, ensure_ascii=False) + "\n") if i % 50 == 0: print(f"已处理 {i} 页")

为什么要按页存:一页崩了不影响整体;后面改正则、改字段映射,都只重跑切分环节,不用再读一遍 PDF,几百页省下来的时间很可观。ensure_ascii=False让中文释义原样写入,否则全是\uXXXX,肉眼排查时代价很高。

2.4 三个高频坑:水印、分栏和扫描页

水印的特征是每页都出现同一串文字,通常是书名、网址或机构名。判断方式是统计行频次,出现在超过 30% 页面且长度大于 20 的行直接丢。

分栏的典型症状是行尾出现半截单词,比如一行以aband结束。出现交错就用page.crop((x0, top, x1, bottom))先切左右半页,再分别调用extract_text,最后按栏拼接。

扫描页整页抽出来只有空白或几个字符,page.images非空且文本极短就说明是图片,得走 OCR,不要在正则上继续投入时间。

3. 从纯文本还原托业词条:切分、去重与字段落库

3.1 先摸清排版:三种常见的词条行模式

raw_pages.jsonl的前 200 行打印出来,词条基本落在三种形态里。

模式样例特征处理方式
A 单行式abandon [əˈbændən] v. 放弃全字段同一行一条正则搞定
B 断开式abandon/[əˈbændən] v./放弃释义换行行首为单词且下一行以[开头则合并
C 多词性式account n. 账户/v. 解释一词挂多词性合并到同一 word,按词性拆记录

稳妥的顺序是先按 A 模式写正则,命中率打到 80% 以上,再针对 B、C 补规则。想一条正则吃下全部形态,结果通常是一条也调不动,因为你在同时优化三个互相矛盾的边界条件。

3.2 词条行的正则骨架

import re # 词条行:单词 + 可选音标 + 可选词性 + 释义 ENTRY = re.compile( r"^(?P<word>[A-Za-z][A-Za-z\-' ]{0,40}?)\s*" r"(?:\[(?P<phonetic>[^\]]{1,60})\])?\s*" r"(?P<pos>(?:n|vt|vi|v|adj|adv|prep|conj|pron|num|int|abbr)\.)?\s*" r"(?P<definition>[\u4e00-\u9fa5].*)$" ) def parse_line(line: str): m = ENTRY.match(line.strip()) if not m: return None return {k: (v or "").strip() for k, v in m.groupdict().items()}

逻辑说明:word用非贪婪加长度上限,避免整行被吞进第一组;音标用\[...\]包住,词表里的音标内部一般不会出现]definition锚定第一个汉字,这是最稳的边界,因为中文释义一定以汉字起头。词性枚举里必须把vtvi放在v前面,正则交替从左往右试,顺序反了vt只会被匹配成v,词性列就废了。

可调点:音标若用斜杠风格,把\[ \]换成/ /;释义里混英文短语时(如n. Online Shopping 网上购物),字符集要放宽成[\u4e00-\u9fa5A-Za-z],代价是误命中率上升,需要用 3.4 的抽检脚本兜住。

3.3 落库:SQLite 表结构与去重策略

CREATE TABLE IF NOT EXISTS entries ( id INTEGER PRIMARY KEY, word TEXT NOT NULL, word_lower TEXT NOT NULL, phonetic TEXT, pos TEXT, definition TEXT NOT NULL, source_page INTEGER, raw_line TEXT, UNIQUE(word_lower, pos, definition) ); CREATE INDEX IF NOT EXISTS idx_entries_word ON entries(word_lower); CREATE INDEX IF NOT EXISTS idx_entries_pos ON entries(pos);

不要用UNIQUE(word)去重。托业词汇里一个词挂多个义项很常见,account既是名词“账户”也是动词“解释”,唯一约束会把有用的义项一起删掉。用(word_lower, pos, definition)三元组约束,压掉完全重复的行,保留语义不同的。

-- 保留每组重复行里 id 最小的那条 DELETE FROM entries WHERE id NOT IN ( SELECT MIN(id) FROM entries GROUP BY word_lower, IFNULL(pos, ''), definition );

注意:执行前先SELECT COUNT(*)记个底,删完对比数量。如果一下少了两三成,多半是正则把释义边界切错,导致大量不同词条落成同样的 definition,这时候该回去改 3.2 而不是继续删。

3.4 抽检:写脚本给自己找错行

人工翻三千行不现实。三条规则就能捞出大部分问题:释义长度小于 2 或大于 40;单词里空格超过 2 个多半是连词成句;音标存在却不含任何元音字符,说明音标被截断。

import sqlite3, re conn = sqlite3.connect("toeic.db") cur = conn.cursor() for _id, word, ph, dfn in cur.execute( "SELECT id, word, phonetic, definition FROM entries"): if not (2 <= len(dfn) <= 40): print("释义长度异常", _id, word, dfn) if word.count(" ") > 2: print("疑似连词", _id, word) if ph and not re.search(r"[aeiouəæɑɔ]", ph.lower()): print("音标可疑", _id, word, ph)

把输出导成待修列表,人工过一遍一般在十分钟以内,比反复猜正则快得多。

4. 让托业TOEIC词汇汇总.pdf 变成能复习的系统:Anki 导入与 SQLite 检索

4.1 导出 Anki 可导入的 TSV

Anki 的导入对话框认制表符分隔的纯文本,字段顺序由你自己定义。三个字段够用:正面放单词,背面放音标加释义,第三个字段放词性,导入时映射成标签。

import csv, sqlite3 conn = sqlite3.connect("toeic.db") rows = conn.execute( "SELECT word, phonetic, pos, definition FROM entries ORDER BY word_lower" ).fetchall() with open("toeic_anki.tsv", "w", encoding="utf-8", newline="") as f: w = csv.writer(f, delimiter="\t", quoting=csv.QUOTE_NONE, escapechar="\\") for word, ph, pos, dfn in rows: back = f"{('[' + ph + '] ') if ph else ''}{pos or ''} {dfn}".strip() w.writerow([word, back, f"TOEIC {pos or 'other'}"])

参数说明:newline=""是 csv 模块在 Windows 上避免多出空行的标准写法;quoting=csv.QUOTE_NONEescapechar防止释义里的引号被包成双引号,各版本 Anki 对转义引号的处理并不统一。导入时字段分隔符选 Tab,关掉“允许 HTML”,否则释义里的<会被当作标签吃掉。

Anki 字段来源列导入后设置
正面word用作卡片模板的查询字段
背面音标 + 词性 + 释义允许换行,不启用 HTML
标签TOEIC {pos}映射为标签,便于按词性建子牌组

后续如果想用 Excel 核对词表,再导一份 CSV,别拿 TSV 直接双击打开,Excel 会把制表符折成空格。

4.2 按词性、首字母和释义关键词筛词

复习计划不该均匀铺开。想“这周只背动词”,或者“把释义里带合同、谈判的词挑出来集中过”,都是几行 SQL 的事。

-- 按词性统计,决定每天分多少张卡 SELECT pos, COUNT(*) FROM entries GROUP BY pos ORDER BY 2 DESC; -- 挑商务高频词:释义里含指定关键词 SELECT word, pos, definition FROM entries WHERE definition LIKE '%合同%' OR definition LIKE '%谈判%' ORDER BY word_lower LIMIT 50; -- 按首字母分批,每次只导 300 条 SELECT word, definition FROM entries WHERE word_lower LIKE 'a%' ORDER BY word_lower LIMIT 300;
复习目标用的列索引是否命中
按词性成批背pos命中 idx_entries_pos
按首字母刷词word_lower命中 idx_entries_word
释义关键词搜definition不命中,走全表扫描
按页回查原书source_page不命中,数据量小可接受

几千条量级下全表扫描是毫秒级,不必急着上 FTS5;到几万条再考虑CREATE VIRTUAL TABLE entries_fts USING fts5(word, definition, content='entries', content_rowid='id')

4.3 把复习记录写回数据库:一张 review 表跑通 SM-2

CREATE TABLE IF NOT EXISTS review ( entry_id INTEGER PRIMARY KEY REFERENCES entries(id), ease REAL DEFAULT 2.5, interval_days INTEGER DEFAULT 0, reps INTEGER DEFAULT 0, lapses INTEGER DEFAULT 0, due_date TEXT );

调度用最朴素的 SM-2 就够,质量分 0 到 5,低于 3 视为没记住。

from datetime import date, timedelta def sm2(ease, interval, reps, quality): if quality < 3: reps, interval = 0, 1 # 答错,间隔回到第一天 else: interval = 1 if reps == 0 else (6 if reps == 1 else round(interval * ease)) reps += 1 ease = ease + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)) return max(1.3, ease), interval, reps ease, interval, reps = sm2(2.5, 6, 2, 4) print(ease, interval, reps, (date.today() + timedelta(days=interval)).isoformat())

易错参数:ease下限必须卡在 1.3,否则连续答错几次后系数掉到 1 以下,间隔越算越短,等于永远在重复同一张卡。quality=3是分水岭,答得勉强要老实记 3,习惯性给 5 会让间隔一次翻倍,第二周直接积压。due_date存 ISO 格式字符串,日期比较可以直接用字符串排序,不必引入额外类型转换。

4.4 起一个本地网页随手查词

不想每次开数据库客户端,用 Flask 十几行就能起一个查词页,把entriesreviewjoin 起来,顺便显示下次复习日期。这类小工具的价值不在功能多,而在把“查一个词的成本”压到两秒以内。查得越顺手,越不容易在半路上放弃。

5. 校验、增量更新与几处容易踩偏的细节

5.1 词形还原:别把复数当成新词

entries里会散着accountsshipping这类变形。加一列lemma做统计合并,比在词表里手工归并省事。

import sqlite3 from nltk.stem import WordNetLemmatizer wnl = WordNetLemmatizer() conn = sqlite3.connect("toeic.db") conn.execute("ALTER TABLE entries ADD COLUMN lemma TEXT") for _id, word in conn.execute("SELECT id, word FROM entries").fetchall(): conn.execute("UPDATE entries SET lemma=? WHERE id=?", (wnl.lemmatize(word.lower()), _id)) conn.commit()

注意lemmatize默认按名词还原,shipping会还原成shipping而不是ship。要处理动名词得传pos="v",但词表本身没标注词形,强行猜错误率不低。建议只把lemma用于统计和分组,不要覆盖原始word列,否则卡片正面会变得和书上不一致。

5.2 换版本时只重跑变化页

《托业TOEIC词汇汇总.pdf》换了版本,不必整本重来。先对文件算一次摘要,和上次记录比对;不一致再逐页算哈希,只把变化页重新抽取、重新入表。

python3 - <<'PY' import hashlib h = hashlib.sha256(open("托业TOEIC词汇汇总.pdf", "rb").read()).hexdigest() print(h) PY

入库时以(word_lower, pos, definition)做 UPSERT,同一页重复跑不会产生副本,review表里的entry_id也能保持稳定,已积累的复习进度不会因为更新词表而清零。这是把资料做成长期资产和一次性导出之间最实际的区别。

5.3 三个容易忽略的边界

音标里的重音符号ˈˌ在部分字体下会被抽成?。落库前统一替换一次,别等导进 Anki 才发现满屏问号,那时候已经很难定位是哪一页出的问题。

释义跨页:一个词条被分页截断,前半页只剩单词和音标。按source_page排序后检查相邻两页的首尾行,能定位大部分这类断点,补一条“上一页最后一行只有单词、下一页第一行以[开头则合并”的规则即可。

几千条词表用 SQLite 足够,不要一上来就上服务端数据库。真正决定体验的是词条准不准、卡片调度合不合理,不是存储引擎选哪个。

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

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

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

立即咨询