Python实现考纲词汇PDF转Anki卡片,自动统计掌握率
2026/9/18 12:07:16 网站建设 项目流程

简介:北京成人本科学位英语考试大纲词汇总表是为成人本科学位英语考生整理的备考核心资料,以单个PDF文件发布,文件大小仅307KB。该考试重在检验成人高等教育学习者的英语综合应用能力,而这份词表正对应考纲要求,按A到Z顺序收录了abandon、ability、absolute、accept等基础高频词,也收入accelerate、academic、accomplish等学术类和书面表达词汇;每个单词配有中英文双语释义,方便不同水平的成人学习者对照记忆和查漏补缺。除单词外,表中还融入adjective、adverb、agreement、analysis等语法关键概念,帮助考生理解句子结构与语法规则,进而在语法填空和完形填空中减少失分;对于阅读和听力,考生也能借助这些词汇快速定位主旨、抓取细节,提高理解速度与准确度。这份资料体量小,使用方便,目前已有260人浏览学习,适合需要系统背词、巩固语法并提升综合语言能力的成人考生反复研读和自测。

1. 为什么“大纲词汇总表.pdf”不是拿来背的,而是拿来解析的

拿到“北京成人本科学位英语考试大纲词汇总表.pdf”,多数人会直接打开 PDF,翻到词表页,一边划线一边背。这个动作本身没有错,但对 IT 从业者来说效率太低。这份文档真正值钱的是它定义好的“考纲词汇边界”和相对固定的词条排版:一个词一行,后面跟着音标或释义。这种结构一旦被抽成结构化数据,就能做出去重、标记已掌握、批量生成 Anki 卡片、统计复习进度等一系列操作。反过来,如果一直停留在 PDF 阅读器里,检索只能靠 Ctrl+F,复习只能靠眼睛翻页,本质上还是在用处理普通文档的方式处理词汇数据。

这篇文章要解决的就是一件事:把“北京成人本科学位英语考试大纲词汇总表.pdf”从“能看的文档”变成“能跑的词汇数据源”。按抽取、清洗、生成卡片、进度复查四步走,全部操作在本地完成,普通 Windows 或 macOS 机器都能跑通。

2. 用 PyMuPDF 把“大纲词汇总表.pdf”抽成结构化词条

2.1 先判断 PDF 有没有文本层

PDF 分两类。文本型 PDF 的文字本身就是可复制的字符,用脚本直接提取即可;扫描型 PDF 只有图片,必须先过 OCR。判断方法很直接:用 PyMuPDF 读一页,看get_text()返回的长度。长度接近 0,说明这页没有文字层。

import fitz doc = fitz.open("北京成人本科学位英语考试大纲词汇总表.pdf") for i in range(min(3, len(doc))): text = doc[i].get_text("text") print(f"第 {i + 1} 页,文本长度 {len(text)}") print(text[:80].replace("\n", " "))

fitz.open()打开本地文件路径,不会发起任何外部请求。get_text("text")按页面阅读顺序返回纯文本,len(text)是判断是否有文字层的核心指标。如果前三页长度都在几十以内,大概率是扫描版,跳到 2.4 节处理;如果文本长度成百上千,说明是文本型,可以直接走下面的解析流程。

2.2 词条行的三种常见排版形态

考纲词汇表的排版不会只有一种。我处理过几类不同来源的 Word 转 PDF 词表,常见形态如下,你需要先抽取几页文本对照观察,再决定规则:

排版形态示例抽取策略
单词和注释在同一行abandon [ə'bændən] vt. 抛弃一行正则直接匹配
单词单独一行,释义在下一行abandon换行[ə'bændən] vt. 抛弃缓存上一行,下一行作为释义合并
两栏排版,按行交错左栏单词、右栏释义,文本层顺序乱按坐标排序后用(x0, y0)分组

前两种情况占大多数,本文的代码按这两种设计。两栏排版相对少见,如果遇到,用page.get_text("dict")取出每个字符的坐标,按x0坐标分列重建行顺序,原理不复杂但代码量会翻倍,一般考试大纲词汇表很少用这种版式。

2.3 抽取脚本:行匹配加状态缓存

import fitz import re doc = fitz.open("北京成人本科学位英语考试大纲词汇总表.pdf") word_re = re.compile(r"^[A-Za-z][A-Za-z'\-]*$") entry_re = re.compile( r"^(?P<word>[A-Za-z][\w'\-]*)" r"(?:\s*(?P<phonetic>\[[^\]\n]*\]|/[^/\n]*/))?" r"(?:\s*(?P<meaning>\S.*))?$" ) records = [] pending = None for page in doc: for raw in page.get_text("text").splitlines(): line = raw.strip() if not line: continue if re.fullmatch(r"[\d\s]+", line): continue m = entry_re.match(line) if m and (m.group("meaning") or m.group("phonetic")): records.append({ "word": m.group("word"), "phonetic": m.group("phonetic") or "", "meaning": m.group("meaning") or "", }) pending = None elif word_re.match(line): pending = line elif pending: records.append({ "word": pending, "phonetic": "", "meaning": line, }) pending = None print(f"共抽取 {len(records)} 条记录")

entry_re有三个分组:word匹配单词本体,phonetic匹配方括号或斜杠包裹的音标并允许为空,meaning匹配剩余释义部分。word_re只匹配“纯单词行”,用于处理单词和释义分行的场景。pending变量作为状态缓存:如果当前行是纯单词,先不落库,等下一行进来自动合并。re.fullmatch(r"[\d\s]+", line)过滤页码和纯数字行,避免把页脚当成词条。

这段脚本跑完,records就是初步的结构化词条。打印前 20 条检查一下,如果发现把目录页、页眉也抽进来了,增加过滤条件:跳过包含“目录”“大纲”“考试”等标题词的行,或者从正文页码开始遍历。

2.4 扫描版 PDF 走 OCR 兜底

如果确认 PDF 是图片扫描版,就不能用文本抽取了。常见做法是安装paddleocrpaddlepaddle,逐页识别后输出文本,再回到 2.3 的正则逻辑处理。PaddleOCR 默认按检测框输出文字,识别结果的顺序不见得和阅读顺序一致,需要按box[0][1](即 y 坐标)排序后再拼接行。

from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False) result = ocr.ocr("page_001.png", cls=True)

use_angle_cls=True让方向分类器处理旋转文本,lang="ch"指定中英文混合识别。OCR 的精度受扫描质量和字体影响,音标部分尤其容易出错。识别完成后把文本交给 2.3 的解析逻辑,音标字段如果出现乱码,先原样保留,不要急着删,后面清洗阶段再处理。

3. 词条清洗与去重:让一份考纲词汇只保留一份记录

3.1 先统计重复词条,再决定合并策略

从 PDF 抽出来的数据大概率有脏数据。最常见的是同一个单词在不同页重复出现,或者大小写不一致导致被当成两个词。先用 Counter 统计一遍,心里有数再清洗。

from collections import Counter words = [r["word"].strip().lower() for r in records] counter = Counter(words) dups = {w: n for w, n in counter.items() if n > 1} print(f"总数 {len(words)},去重后 {len(counter)},重复词条 {len(dups)} 个") print(list(dups.items())[:10])

这里先把单词统一转小写再计数,避免Abandonabandon被当成两个词。但注意:转小写只用于统计,原始单词要保留一份,因为部分专有名词在考试中会以大写形式出现。

3.2 清洗规则表:先看全再动手

清洗不是简单的strip()lower()。PDF 文本层经常混入全角空格、不间断空格、BOM 字符、以及音标字体映射失败产生的乱码。常见做法是按下面这张表逐项处理,每处理完一步打印几条结果验证:

处理对象问题表现处理方式示例
空白字符\u3000\xa0混入统一替换为普通空格a\u3000ba b
大小写同词不同大小写统一小写入索引,保留原始拼写Abandon索引为abandon
音标乱码PDF 字体子集不完整保留原字符,标记pending列待人工复核[ə'bændən][????]
重复词条同一词出现在多页同词头合并释义,用分号连接vt. 抛弃+n. 抛弃合并
派生词economiceconomical易混不自动合并,保留独立词条两者都保留

合并重复词条时,释义不要直接覆盖。同一单词在不同单元可能标注不同词性或义项,把多个释义用分号连接反而更完整。

from collections import defaultdict merged = defaultdict(lambda: {"phonetic": "", "meanings": []}) for rec in records: word = rec["word"].strip().lower() entry = merged[word] if rec.get("phonetic"): entry["phonetic"] = entry["phonetic"] or rec["phonetic"] if rec.get("meaning"): entry["meanings"].append(rec["meaning"]) clean_records = [ { "word": w, "phonetic": v["phonetic"], "meaning": ";".join(v["meanings"]), } for w, v in merged.items() ]

defaultdict的默认值是一个包含phoneticmeanings列表的字典。entry["phonetic"] = entry["phonetic"] or rec["phonetic"]表示只有当前词条还没有音标时才填充,避免后出现的空字符串覆盖已有值。";".join(v["meanings"])用中文分号连接多释义,Anki 正面和背面都能直接显示。

3.3 把清洗结果导出成 TSV

清洗后的数据结构化程度足够,最适合导出为制表符分隔的 TSV 文件,这是 Anki 和大多数本地工具都能消费的格式。

import csv with open("words_clean.tsv", "w", encoding="utf-8", newline="") as f: writer = csv.writer(f, delimiter="\t") writer.writerow(["word", "phonetic", "meaning"]) for rec in clean_records: writer.writerow([rec["word"], rec["phonetic"], rec["meaning"]]) print(f"清洗完成,共 {len(clean_records)} 个词条,已写入 words_clean.tsv")

delimiter="\t"指定字段分隔符为制表符。newline=""防止 Windows 环境下 csv 模块写出多余空行。注意如果释义里本身包含 Tabs 或换行,csv.writer会按规则加引号转义,Anki 导入时也能正确识别。导出后直接对文件执行wc -lhead -5验证一下,比在脚本里盲目自信靠谱得多。

4. 把清洗后的大纲词汇批量生成 Anki 背词卡组

4.1 字段设计与卡片模板

Anki 的关键是把信息拆成最小字段,而不是把整条释义塞进一个问题里。针对“北京成人本科学位英语考试大纲词汇总表.pdf”的复习场景,建议三个字段:wordphoneticmeaning

字段正面显示背面显示
word是(大号加粗)
phonetic是(正面的小字)
meaning是(通过{{FrontSide}}带出,再追加释义)

模板不用写得太复杂,越简单加载越快。Anki 默认提供的 Basic 模型改三个字段名就能用,或者直接用 genanki 在生成卡组时定义模型,见 4.3。

4.2 用 TSV 导入 Anki 的最短路径

如果不想写额外代码,直接把第 3 章导出的words_clean.tsv导入 Anki 也可以。操作顺序是:Anki 中点“导入文件”,文件类型选“文本分隔符文件”,字段分隔符选“制表位”,然后按顺序映射字段到wordphoneticmeaning

head -5 words_clean.tsv

先看一眼文件前几行确认没有出现错列,再进 Anki 导入。head -5在 Windows PowerShell 下可以写成Get-Content words_clean.tsv -TotalCount 5。这一步不需要写脚本,适合一次性导入的场景。

4.3 用 genanki 直接生成 .apkg

TSV 导入适合快速起步,但每次增删卡片都要重新导入,麻烦。更省事的方式是用genanki库直接生成.apkg文件,双击就能装进 Anki。

import genanki model = genanki.Model( 1607390001, "考纲词汇 Word/Meaning", fields=[ {"name": "Word"}, {"name": "Phonetic"}, {"name": "Meaning"}, ], templates=[ { "name": "Card 1", "qfmt": "<div style='font-size:32px'>{{Word}}</div>" "<div style='font-size:20px'>{{Phonetic}}</div>", "afmt": "{{FrontSide}}<hr id='answer'>{{Meaning}}", } ], ) deck = genanki.Deck(2057390001, "北京成人本科学位英语考纲词汇") for rec in clean_records: if not rec["meaning"]: continue note = genanki.Note(model=model, fields=[rec["word"], rec["phonetic"], rec["meaning"]]) deck.add_note(note) genanki.Package(deck).write_to_file("beijing_degree_words.apkg")

genanki.Model的第一个参数是模型 ID,第二个是模型名。ID 可以填任意整数,只要不和已有卡组冲突。qfmtafmt是 Anki 卡片正反面的 HTML 模板,{{Word}}会被替换为对应字段内容,{{FrontSide}}表示把正面内容原样带到背面。genanki.Deck同理,任意整数作 ID。if not rec["meaning"]跳过空释义的记录,避免生成一面只有单词没有任何答案的废卡。跑完后双击beijing_degree_words.apkg就能导入。

4.4 复习参数怎么设才有参考意义

卡组生成不是结束,复习参数才是决定背不背得完的关键。考纲词汇量比四级少,比高考多,属于“集中突击型”词汇,参数建议如下:

Anki 参数建议值理由
新卡/天20~30总量有限,每天 30 新卡,两个月内能过完整轮
复习上限100~150防止积压,碎片时间能刷完
初始间隔1 天第一遍只要求认得出
简单间隔4 天与初始间隔拉开差距,避免无意义重复
成熟间隔21 天覆盖完整记忆周期
搁置新卡10 分钟同一卡片单日尽量少见面

这些参数在新版本 Anki 中都能在“牌组选项”里直接改。重点不是抄数值,而是理解原则:考纲词表有明确边界,适合“全部过一遍再复刷”,所以初始间隔可以激进一点,成熟间隔拉长,不让已经记住的词占满每日复习配额。

5. 用 30 行脚本把考纲词表变成本地掌握率看板

5.1 已掌握词对比脚本

背了一段时间后,最想知道的不是“今天复习了几张卡”,而是“大纲词汇我到底掌握了多少”。Anki 自带统计能看到卡片数量,但看不到考纲覆盖度。把已掌握的单词手动导出到一个learned_words.txt,一行一个词,再写个脚本和words_clean.tsv对比即可。

from pathlib import Path master = [] with open("words_clean.tsv", encoding="utf-8") as f: for line in f: word = line.split("\t")[0].strip().lower() if word: master.append(word) learned = { line.strip().lower() for line in Path("learned_words.txt").read_text(encoding="utf-8").splitlines() if line.strip() } unique = list(dict.fromkeys(master)) unlearned = [w for w in unique if w not in learned] rate = (len(unique) - len(unlearned)) / len(unique) print(f"大纲词表总量:{len(unique)}") print(f"已标记掌握:{len(unique) - len(unlearned)}") print(f"掌握率:{rate:.1%}") print(f"未掌握词条数:{len(unlearned)}") print("按首字母分布:")

dict.fromkeys(master)在保留顺序的同时去重,比 set 更稳定。learned是从已掌握文件读出的集合,用于 O(1) 查询。line.split("\t")[0]只取每行第一个字段,也就是单词,避免把释义误当成单词统计。

要快速生成learned_words.txt,写一个简单脚本读 Anki 的导出文件即可,不依赖任何插件。

5.2 分组统计薄弱区

在 5.1 的脚本末尾追加一个按首字母分组的统计,能看到哪个字母区间还没过完。对成人考生来说,这比“今天复习了 50 张卡”更有参考价值,因为考试没有词汇大纲之外的超纲词,覆盖度是硬指标。

from collections import Counter unlearned_letters = Counter(w[0].upper() for w in unlearned if w) for letter in sorted(unlearned_letters): print(f"{letter}: {unlearned_letters[letter]:3d}")

输出到终端不够直观,可以加一个参数重定向到文件:python check_progress.py > study_report.txt,Windows 的cmd和 PowerShell 都支持这个写法。报告文件积累起来后,直接比较前后两次的掌握率就能看到趋势。

通常的方法是把这个脚本放在词表同级目录,每次背完一组新词就更新learned_words.txt并重跑一遍。如果你希望自动化,Windows 可以用任务计划程序,macOS 用launchd,把python check_progress.py设为每天固定时间执行一次,输出追加到study.log。这样考纲词表的进度就不再依赖记忆力,而是落在脚本输出的数字上。

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

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

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

立即咨询