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

PDF转SQLite:用Python构建考研词汇间隔重复训练系统

PDF转SQLite:用Python构建考研词汇间隔重复训练系统 ★ FEATURED ARTICLE
简介这份PDF资料面向备战考研英语的考生及英语教学者核心解决词汇记忆零散、长难句理解吃力、语法要点难以系统掌握等问题。全书精选考研真题中100个典型句子通过核心词讲解、同义词与常考搭配归纳串联起约5500个考研单词并逐一剖析句子语法结构帮助读者在语境中记词、在回译中理解句子。资源包共1个PDF文件大小约3.05MB内容完整、便于打印或移动端阅读。目前已有756人学习下载适合需要系统攻克考研词汇与阅读长难句的中高级英语学习者。读者可借助书中句子反复进行英译中、中译英的回译训练对照原句发现理解盲区逐步掌握句子结构、语法要点与阅读长难句的方法从而提升考研英语的词汇量与阅读速度。1. 从一份 PDF 到一套可复现的词汇训练系统一份名为「100个句子记完5500个考研单词.pdf」的资料核心思路并不神秘用 100 个高度浓缩的长难句把考研大纲里约 5500 个词全部覆盖进去。它的价值不在于「100 句」这个数字本身而在于它把「背单词」从孤立记忆变成了语境记忆——每个词都挂在一个有语法结构的句子里你记的不是词义而是词的用法。但问题也恰恰出在这里PDF 是静态的句子是固定的而每个人的遗忘曲线、薄弱词分布、复习节奏完全不同。直接拿 PDF 从头读到尾大概率是前 20 句滚瓜烂熟后 80 句永远停在收藏夹里。我见过太多人把这类资料当成「读完就完事」的读物结果三个月后只记得 abandon。真正能让这份 PDF 产生复利的方式是把它拆成可检索、可标注、可间隔重复的数据层再套一个轻量的本地训练流程。这篇文章要讲的就是怎么把一份静态 PDF 变成一套你能每天跑、能看见进度、能针对薄弱词加练的词汇系统。适合已经有一份类似资料、但苦于「背了忘、忘了背」的考研人也适合想用脚本把纸质资料数字化的开发者。2. 把 PDF 拆成句子、词表与索引数据层怎么建2.1 先判断 PDF 是文本型还是扫描型动手之前必须确认一件事你手里的 PDF 能不能直接提取文字。文本型 PDF 用pdftotext或 Python 的pdfplumber就能拿到干净字符串扫描型 PDF 本质是图片必须先走 OCR否则后面所有步骤都是空中楼阁。判断方法很简单用pdftotext跑一下看输出文件大小和内容。# 文本型 PDF输出会有可读文字文件大小通常几十 KB 以上 pdftotext -layout 100个句子记完5500个考研单词.pdf raw.txt wc -c raw.txt # 如果 raw.txt 只有几百字节或全是乱码基本就是扫描型 head -c 500 raw.txt逻辑说明-layout参数保留原始排版对后续按行切分句子很关键。如果输出为空或乱码说明是扫描件需要换 OCR 路线常见做法是用ocrmypdf先做一层文字层再走同样的提取流程。参数上-layout比默认模式更适合句子类资料因为它不会把多列内容混成一行。2.2 用正则把 100 个句子切出来拿到raw.txt后下一步是定位句子边界。这类资料通常有固定格式句子编号 英文原句 中文翻译 词汇注释。不同版本排版有差异但编号模式一般逃不出「数字 点/、/空格」这几种。我一般先用正则粗切再人工校验前 10 条。import re with open(raw.txt, r, encodingutf-8) as f: text f.read() # 匹配形如 1. 2、 03 的句子编号开头 pattern re.compile(r(?m)^\s*(\d{1,3})\s*[.、\s]\s*(.?)(?^\s*\d{1,3}\s*[.、\s]|\Z), re.S) matches pattern.findall(text) sentences [] for idx, body in matches: body body.strip() if len(body) 20: # 过滤掉页码、页眉等短噪声 sentences.append({id: int(idx), raw: body}) print(f切出 {len(sentences)} 条) for s in sentences[:3]: print(s[id], s[raw][:80])逻辑说明(?m)开启多行模式^才能匹配每行开头(?...)是前瞻断言用来在下一个编号出现前截断避免把两句粘在一起。re.S让.能跨行匹配因为一个长难句可能占多行。参数上len(body) 20这个阈值需要根据实际排版调太小会混入页码太大会漏掉短句。切完后一定要打印前几条肉眼确认正则切分翻车是常态。2.3 从句子中抽词并去重建立词表索引句子切好后接下来要把每句里的考研词抽出来。这一步不需要完美分词因为考研词表是已知的——你可以先准备一份大纲词表常见做法是从公开大纲整理成words.txt每行一个词然后用集合匹配的方式标注每个句子覆盖了哪些词。import re from collections import defaultdict # 加载大纲词表统一小写 with open(words.txt, r, encodingutf-8) as f: vocab set(w.strip().lower() for w in f if w.strip()) def tokenize(s): return re.findall(r[a-zA-Z][a-zA-Z\-], s.lower()) word_to_sent defaultdict(list) sent_to_words {} for s in sentences: tokens set(tokenize(s[raw])) hit tokens vocab sent_to_words[s[id]] sorted(hit) for w in hit: word_to_sent[w].append(s[id]) # 输出覆盖统计 covered len(word_to_sent) print(f词表 {len(vocab)} 词被句子覆盖 {covered} 词覆盖率 {covered/len(vocab):.1%}) # 找出没被任何句子覆盖的词这些是后续要补的缺口 missing vocab - set(word_to_sent.keys()) print(f未覆盖 {len(missing)} 词示例{list(missing)[:10]})逻辑说明tokenize用正则抓英文词保留连字符和撇号避免把well-known拆成两个词。word_to_sent是倒排索引后面做「按词查句」和「按句查词」都靠它。覆盖率统计是关键指标——如果覆盖率低于 90%说明要么句子没切全要么词表版本和资料不匹配。未覆盖的词单独存成missing.txt这是你后续加练的重点。2.4 落库用 SQLite 存成可查询的三张表数据量不大SQLite 足够。建三张表sentences存句子words存词表links存句子和词的关联。这样后面做查询、标记掌握度、导出 Anki 都方便。import sqlite3 conn sqlite3.connect(vocab.db) cur conn.cursor() cur.executescript( CREATE TABLE IF NOT EXISTS sentences ( id INTEGER PRIMARY KEY, raw TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS words ( word TEXT PRIMARY KEY, status INTEGER DEFAULT 0 -- 0 未学 1 模糊 2 掌握 ); CREATE TABLE IF NOT EXISTS links ( sentence_id INTEGER, word TEXT, PRIMARY KEY (sentence_id, word) ); ) cur.executemany(INSERT OR REPLACE INTO sentences(id, raw) VALUES (?, ?), [(s[id], s[raw]) for s in sentences]) cur.executemany(INSERT OR IGNORE INTO words(word) VALUES (?), [(w,) for w in vocab]) cur.executemany(INSERT OR IGNORE INTO links(sentence_id, word) VALUES (?, ?), [(sid, w) for sid, ws in sent_to_words.items() for w in ws]) conn.commit() conn.close() print(入库完成)逻辑说明status字段是后面间隔重复的基础0/1/2 三档足够轻量。links表的主键设计防止重复插入。入库后你可以直接用 SQL 查「包含某个词的所有句子」比如SELECT s.raw FROM sentences s JOIN links l ON s.id l.sentence_id WHERE l.word abandon;。这一步做完PDF 就从一个死文件变成了可编程的数据源。3. 用间隔重复把 100 句跑成每日任务调度层怎么做3.1 为什么不用现成背词 App 的调度现成 App 的调度算法是黑匣子你无法控制「哪些词优先」「句子什么时候出现」。而这份资料的核心资产是句子不是词。你需要的是「以句子为最小复习单元但按词掌握度决定句子优先级」的调度。常见做法是自己写一个简化版 SM-2只保留三个状态和两个间隔参数够用且透明。3.2 调度器的核心逻辑与代码import datetime import sqlite3 conn sqlite3.connect(vocab.db) cur conn.cursor() def due_sentences(today, limit10): # 优先取包含「未学/模糊」词最多的句子 cur.execute( SELECT s.id, s.raw, COUNT(*) AS weak_cnt FROM sentences s JOIN links l ON s.id l.sentence_id JOIN words w ON l.word w.word WHERE w.status 2 GROUP BY s.id ORDER BY weak_cnt DESC, RANDOM() LIMIT ? , (limit,)) return cur.fetchall() def update_word(word, quality): # quality: 0 忘记 1 模糊 2 掌握 cur.execute(SELECT status FROM words WHERE word ?, (word,)) row cur.fetchone() if not row: return old row[0] if quality 0: new 0 elif quality 1: new max(0, old) # 保持或降级 else: new min(2, old 1) cur.execute(UPDATE words SET status ? WHERE word ?, (new, word)) conn.commit() today datetime.date.today().isoformat() tasks due_sentences(today, limit10) for sid, raw, cnt in tasks: print(f[{sid}] 薄弱词 {cnt} 个) print(raw[:120]) print(---)逻辑说明due_sentences的排序策略是「薄弱词密度优先」这样你每天先啃最不熟的句子而不是随机翻。update_word里 quality 为 0 时直接打回未学为 2 时升一级最多到 2。参数limit10是每日句子量建议从 5 开始稳定后再加到 10。这个调度器不存复习时间戳因为句子级复习靠的是词状态驱动不需要额外的时间维度简化后反而更容易坚持。3.3 每日流程读句、标词、更新状态实际使用时流程是跑脚本拿到今日 10 句 → 逐句精读遇到标注的词回忆词义 → 在命令行输入每个词的质量评分 → 脚本更新状态。为了减少输入负担可以做一个交互式循环。for sid, raw, cnt in tasks: print(f\n 句子 {sid} ) print(raw) cur.execute( SELECT w.word FROM links l JOIN words w ON l.word w.word WHERE l.sentence_id ? AND w.status 2 , (sid,)) weak_words [r[0] for r in cur.fetchall()] for w in weak_words: q input(f {w} [0/1/2]: ).strip() if q in (0, 1, 2): update_word(w, int(q))逻辑说明只对status 2的词提问已掌握的词不再打扰。输入 0/1/2 对应忘记/模糊/掌握回车跳过。这个循环每天花 15 到 25 分钟100 句大约 10 到 15 天能过完第一轮之后进入薄弱词驱动的第二轮。关键是update_word的降级逻辑要保守——模糊不降级只有明确忘记才打回 0否则状态会在 0 和 1 之间反复横跳产生挫败感。4. 避坑与排查这套流程最容易翻车的五个地方4.1 正则切分把两句粘成一句现象打印句子时发现第 5 句和第 6 句连在一起中间没有编号。原因原文里编号可能用了全角空格或特殊符号正则没覆盖。解决把pattern里的[.、\s]扩展为[.、\s\u3000]并在切分后加一条校验——如果某条raw长度超过 800 字符大概率是粘连单独打印出来人工处理。4.2 词表匹配把变形词漏掉现象句子里明明有running但词表里只有run导致这个词没被标注。原因正则抓的是原形没有做词形还原。解决常见做法是引入nltk的WordNetLemmatizer对每个 token 做一次还原再匹配。如果不想装依赖可以维护一张小的变形映射表覆盖-ing、-ed、-s这几类高频变形。4.3 SQLite 并发写入报 database is locked现象一边跑调度脚本一边手动改状态偶尔报锁错误。原因SQLite 默认的写锁是排他的两个进程同时写会冲突。解决在连接时加timeout参数sqlite3.connect(vocab.db, timeout10)让第二个写入等待而不是直接失败。更稳妥的做法是调度脚本和更新脚本不要同时跑。4.4 每日任务量设太大导致放弃现象第一天设了 30 句第二天就不想打开了。原因句子是长难句精读一句平均 2 到 3 分钟30 句就是一个半小时不可持续。解决把limit降到 5稳定两周后再加到 8 或 10。这套系统的目标是「每天都能跑」不是「三天跑完」。4.5 覆盖率统计虚高但实际没记住现象脚本显示覆盖率 98%但做题时还是不认识。原因覆盖率只说明词在句子里出现过不代表你记住了。解决把status为 2 的词单独导出定期用SELECT word FROM words WHERE status 2抽查随机抽 20 个自测如果正确率低于 80%说明状态更新太宽松需要把 quality 为 1 也纳入降级。5. 进阶把词表导出成可打印的薄弱词清单与自测卷跑完几轮后你会积累一批status为 0 和 1 的词。这些词才是你真正的提分空间。我一般会写一个导出脚本生成两份东西一份是按句子分组的薄弱词清单用于精读一份是纯词表自测卷用于快速筛查。import sqlite3 conn sqlite3.connect(vocab.db) cur conn.cursor() # 导出薄弱词清单按句子分组 cur.execute( SELECT s.id, s.raw, GROUP_CONCAT(w.word, , ) FROM sentences s JOIN links l ON s.id l.sentence_id JOIN words w ON l.word w.word WHERE w.status 2 GROUP BY s.id ORDER BY s.id ) with open(weak_by_sentence.md, w, encodingutf-8) as f: for sid, raw, words in cur.fetchall(): f.write(f### 句子 {sid}\n\n{raw}\n\n薄弱词{words}\n\n) # 导出纯词表自测卷每行一词右侧留空写词义 cur.execute(SELECT word FROM words WHERE status 2 ORDER BY RANDOM()) with open(self_test.txt, w, encodingutf-8) as f: for (w,) in cur.fetchall(): f.write(f{w}\t________________\n) print(导出完成)逻辑说明GROUP_CONCAT把同一句的薄弱词拼成一行方便打印后对照。自测卷用RANDOM()打乱顺序避免按字母序产生位置记忆。\t加下划线是给手写留空间打印出来就是一张可随身带的筛查表。这两份文件配合使用先做自测卷把仍然不认识的词在数据库里标回 0再回到weak_by_sentence.md里精读对应句子。一个我自己的习惯是每周日晚上跑一次导出把自测卷打印出来周一早上通勤时过一遍把不认识的词用红笔圈出来晚上回来批量更新状态。这样一周的薄弱词不会超过两页 A4心理负担很小。这套流程从 PDF 提取到每日调度核心代码不到 200 行但能把一份静态资料变成跟着你走的训练系统。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站