ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从字幕到Anki卡片:构建英语学习自动化流水线

从字幕到Anki卡片:构建英语学习自动化流水线 最近在整理英语学习资料时我接触到了everyone-can-use-english这个开源项目。它并不是一个普通背单词软件而是一套把“看剧学英语”这件事系统化、工程化的学习闭环。项目名称翻译过来就是“每个人都能用英语”核心思路不是教你多少单词而是帮你把英语学习拆成每天能执行的小动作。本文不打算逐行翻译源码而是围绕这类项目的通用理念拆解从字幕文本到学习卡片、再到间隔复习的完整流程并给出可以直接运行的 Python 脚本。无论你是想提升英语的开发者还是对“工具驱动学习”感兴趣的程序员都可以按这套思路动手搭一套属于自己的学习流水线。1. 项目背景与核心理念1.1 everyone-can-use-english 是什么everyone-can-use-english是一个以真实语料为基础的英语学习开源项目。它把英语学习从“背单词书”切换到“刷真实句子”的模式核心主张是语言学习应该以句子为单位而不是以单词为单位。我们在传统英语教材里学的句子往往偏书面化和真实生活脱节。而欧美情景剧的台词包含大量口语表达、省略、连读、语气词是最接近日常交流的语料。项目把这些台词整理成可检索、可复习的句子库配合间隔重复SRS机制让学习者每天花少量时间学习新句子、复习旧句子从而逐步建立“英语语感”。从技术角度看这个项目涉及数据处理、文本清洗、音频切分、卡片生成等多个环节。这些环节之间没有高深算法但串联起来就是一个完整的“语料处理流水线”。1.2 它解决什么问题很多人学英语的痛点是“学了就忘”。背了一个星期单词下周再看完全不认识。原因在于单词脱离语境记忆没有锚点。复习没有规律学了新词忘了旧词。输入量足够但输出没有素材支撑。学习素材与真实生活脱节缺乏兴趣。everyone-can-use-english的思路是用真实美剧台词作为输入用间隔重复算法安排复习节奏用句子而非单词作为记忆单元。这样每个单词的“上下文”都被完整保留单词和场景绑定在一起记忆效率会提升很多。1.3 适用人群这个项目适合以下人群已经具备基础英语能力但开不了口的学习者。准备出国、外企面试、日常口语交流需要短期强化语感的人。喜欢美剧想通过兴趣保持学习动力的人。对效率工具有兴趣想自己写脚本处理学习数据的技术型学习者。如果英语基础接近零建议先掌握最基础的音标和 1000 个核心词汇再进入句子刷练否则挫败感会很强。2. 环境准备与工具链在动手搭建自己的英语学习流水线之前需要先准备好开发环境。本文以常见环境为例具体版本请根据你的实际系统调整。2.1 基础环境工具用途版本建议Python编写字幕解析、卡片生成脚本3.9 及以上Anki间隔复习客户端2.1.x 或最新稳定版ffmpeg音视频处理、截取音频片段最新稳定版genankiPython 生成 Anki 牌组按 pip 安装的最新版wordfreq英文词频统计辅助筛选生词按 pip 安装的最新版其中 Anki 是免费开源的记忆软件支持桌面端、移动端和 Web 同步。genanki是一个 Python 库可以让我们用代码直接生成.apkg牌组文件免去手动制作卡片的重复劳动。ffmpeg用于从视频中截取台词对应的音频片段这样复习时不但能看到句子还能听到原声。2.2 安装依赖建议先创建虚拟环境避免依赖冲突python3 -m venv english-env source english-env/bin/activate然后安装 Python 依赖pip install genanki wordfreq在 Ubuntu / Debian 系统上安装 ffmpegsudo apt update sudo apt install ffmpegmacOS 可以使用 Homebrewbrew install ffmpegWindows 可以从 ffmpeg 官网下载预编译版本并把ffmpeg.exe所在目录加入系统 PATH。2.3 准备字幕文件字幕文件可以从公开字幕网站下载也可以从 DVD 提取。注意字幕只能用于个人学习不应公开传播或用于商业用途。常见的字幕格式是 SRT1 00:00:01,000 -- 00:00:04,000 How you doin? 2 00:00:05,000 -- 00:00:07,500 Im fine, thank you.SRT 文件由序号、时间轴、字幕文本和空行组成。解析 SRT 是后续所有处理的基础。3. 核心学习理念拆解在写代码之前先把这套学习流水线的核心方法论讲清楚。理解了为什么这样做后面写脚本时才知道要处理什么数据。3.1 句子优先于单词传统背单词软件展示的是“单词 释义”而语言学习的记忆单元应该是句子。句子包含单词的搭配关系、语法结构、使用场景和语气信息。比如fair enough单独看单词都认识但不知道在什么场景下用等于没学。放到句子里A: Ill pay for the dinner tonight. B: Fair enough.一下就记住了“说得有道理 / 也行”这个语境含义。3.2 间隔重复SRS间隔重复是一种基于遗忘曲线设计的复习策略。新学的内容在快要遗忘的时候复习效率最高。Anki 和同类工具都实现了这个算法。一套完整的 SRS 流程是每天学习一定数量的新句子。当天复习之前学过但还没有达到长期记忆的句子。练习时先看英文句子尝试翻译或回忆场景再展开看答案。根据回忆难度给卡片打分Anki 自动决定下次复习时间。这里的关键是“每天新句数量”要克制。一次学 200 句看起来很爽但第二天要复习的量会很大挫败感会很强。建议每天新句控制在 10 到 20 句。3.3 影子跟读影子跟读Shadowing是指播放原声时以极短的时间间隔跟随朗读。这能同步训练听力、发音和口语流利度。在卡片设计中除了“看句子回忆释义”还可以加入音频播放原声让学习者模仿跟读。这就是为什么需要 ffmpeg 截取音频片段。3.4 真实语料与可理解输入语言学上有个概念叫“可理解输入”学习材料应该略高于当前水平但又不至于完全听不懂。美剧台词中大量出现的高频口语表达恰好处于这个区间。制作卡片时建议按句子长度和词频做初步筛选。太长的句子不适合作为初始学习材料太生僻的单词可以暂时跳过。4. 实战短视频字幕解析脚本为了便于理解我们先把流程拆成几步分阶段实现4.1 创建项目结构在本地创建如下目录结构english-tool/ ├── scripts/ │ ├── parse_srt.py │ ├── build_deck.py │ └── extract_audio.py ├── data/ │ ├── raw/ # 存放原始字幕 │ └── output/ # 输出结果 └── requirements.txt这个结构把“输入数据、脚本、输出结果”分开后面批量处理时不会把目录搞乱。4.2 编写 SRT 解析脚本先写一个通用的 SRT 解析函数。文件路径scripts/parse_srt.pyimport re from pathlib import Path TIME_PATTERN re.compile(r(\d{2}):(\d{2}):(\d{2}),(\d{3})) def parse_timecode(time_str: str): 把 00:00:01,000 转成秒数例如 1.0 match TIME_PATTERN.search(time_str) if not match: return None h, m, s, ms map(int, match.groups()) return h * 3600 m * 60 s ms / 1000 def clean_text(text: str) - str: 清理字幕文本中的标签和特殊符号 text re.sub(r[^], , text) text re.sub(r\{\\[a-z]\}, , text) text text.replace(amp;, ) text text.replace(nbsp;, ) return text.strip() def parse_srt(srt_path: str) - list: 解析 SRT 文件返回字幕记录列表 raw Path(srt_path).read_text(encodingutf-8) blocks re.split(r\n\s*\n, raw.strip()) records [] for block in blocks: lines block.splitlines() if len(lines) 2: continue if not lines[0].strip().isdigit(): continue start_str, end_str lines[1].split( -- )[:2] content .join(line.strip() for line in lines[2:]) content clean_text(content) if not content: continue records.append({ index: int(lines[0].strip()), start: parse_timecode(start_str), end: parse_timecode(end_str), text: content, }) return records if __name__ __main__: records parse_srt(data/raw/friends_s01e01.srt) print(f共解析到 {len(records)} 条字幕) for record in records[:5]: print(record)运行结果类似共解析到 620 条字幕 {index: 1, start: 0.0, end: 4.0, text: How you doin?} {index: 2, start: 5.0, end: 7.5, text: Im fine, thank you.}需要说明的是字幕文件编码可能有差异。如果utf-8读取报错可以先用下面的方式检测编码from pathlib import Path import chardet def detect_encoding(path): data Path(path).read_bytes() result chardet.detect(data) return result.get(encoding, utf-8)不过chardet需要额外安装pip install chardet在实际处理时建议先统一把所有字幕转成 UTF-8 编码再解析能省去很多麻烦。4.3 过滤英文句子字幕文件里可能包含声音描述、人名标识等非对话内容。我们需要把真正适合学习的英文句子过滤出来。一个简单策略是统计文本中英文字母的占比。如果大部分字符都是英文字母就认为是英文句子。文件路径scripts/parse_srt.py继续追加def is_english_sentence(text: str, threshold: float 0.8) - bool: 判断一句话是否以英文为主 letters [ch for ch in text if ch.isalpha()] if not letters: return False english_letters [ ch for ch in letters if a ch.lower() z ] return len(english_letters) / len(letters) threshold def filter_sentences(records: list) - list: return [r for r in records if is_english_sentence(r[text])]这里有一个细节Im、dont这类带撇号的句子不会被误杀因为撇号不是字母统计时会被忽略。isalpha()会把中文、法语字母、德语字母都当成“字母”而我们只统计 a-z 的英文字母因此能有效过滤掉包含大量中文翻译的字幕行。4.4 输出句子 CSV把过滤后的句子写入 CSV方便后续导入 Anki 或做数据分析。文件路径scripts/parse_srt.py继续追加import csv def save_sentences_to_csv(records: list, output_path: str) - None: with open(output_path, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([index, start, end, text]) for r in records: writer.writerow([ r[index], f{r[start]:.3f}, f{r[end]:.3f}, r[text], ]) if __name__ __main__: records parse_srt(data/raw/friends_s01e01.srt) english_records filter_sentences(records) save_sentences_to_csv(english_records, data/output/sentences.csv) print(f过滤后剩余 {len(english_records)} 条英文句子)这一步做完我们就拿到了结构化的句子数据。下一步是筛选“值得学”的句子。5. 实战用词频筛选生词与句子难度字幕里不是所有句子都值得制作成卡片。对于初学者来说一句台词里如果生词太多学习成本会很高。我们可以用wordfreq库统计每个单词的词频等级辅助决定句子的难度。5.1 安装与基础用法wordfreq库返回一个zipf_frequency值数值越大表示单词越常见。一般来说7.0 以上超级常见词the, and, of5.0 左右日常高频词4.0 以下相对生僻pip install wordfreq简单示例from wordfreq import zipf_frequency words [hello, serendipity, the, photovoltaic] for w in words: print(w, zipf_frequency(w, en))运行结果大致是hello 5.29 serendipity 3.13 the 7.82 photovoltaic 2.675.2 计算句子难度文件路径scripts/build_deck.pyimport re from wordfreq import zipf_frequency def extract_words(text: str) - list: 从句子中提取小写单词 return [w.lower() for w in re.findall(r[A-Za-z], text)] def sentence_difficulty(text: str) - dict: words extract_words(text) if not words: return {avg_zipf: 0, difficult_words: []} # 只保留真正的英文单词去掉类似 的符号 words [w.strip() for w in words if w.strip()] if not words: return {avg_zipf: 0, difficult_words: []} freq_list [zipf_frequency(w, en) for w in words] avg_zipf sum(freq_list) / len(freq_list) # 低于 4.0 的单词视为较生僻 difficult [w for w, f in zip(words, freq_list) if f 4.0] return {avg_zipf: round(avg_zipf, 2), difficult_words: difficult} if __name__ __main__: test_sentences [ How you doin?, She chased the serendipity through the photovoltaic corridor., ] for s in test_sentences: print(s, sentence_difficulty(s))运行结果How you doin? {avg_zipf: 4.92, difficult_words: []} She chased the serendipity through the photovoltaic corridor. {avg_zipf: 3.78, difficult_words: [se..., photovoltaic]}注意这里difficult_words会包含带撇号的词实际使用时可以进一步过滤。这个度量并不完美但它能帮我们快速“大致排序”把明显过难的句子往后排把高频常用句子往前放。6. 实战生成 Anki 牌组数据准备好了接下来把句子生成 Anki 可用的.apkg牌组文件。这里使用genanki库。6.1 设计卡片模板一张好的句子学习卡片应该包含正面英文句子背面中文释义、来源信息、音频按钮对应的 Anki 模板可以这样设计正面qfmt显示英文句子背面afmt显示中文释义、来源、音频下面是一个完整脚本。文件路径scripts/build_deck.pyimport genanki # 卡片模型 ID只要唯一即可建议用随机数 MODEL_ID 1607392319 DECK_ID 2059400112 my_model genanki.Model( MODEL_ID, Sentence Card, fields[ {name: Sentence}, {name: Translation}, {name: Source}, {name: Audio}, ], templates[ { name: Sentence Card, qfmt: {{Sentence}}, afmt: ( {{FrontSide}} hr idanswer div{{Translation}}/div div{{Source}}/div {{Audio}} ), } ], ) def build_deck(sentences, output_pathenglish_sentences.apkg): deck genanki.Deck(DECK_ID, EveryoneCanUseEnglish::Sentences) media_files [] for item in sentences: # item 需要包含 text, translation, source, audio_path note genanki.Note( modelmy_model, fields[ item[text], item.get(translation, ), item.get(source, ), f[sound:{item.get(audio_name, )}] if item.get(audio_name) else , ], ) deck.add_note(note) if item.get(audio_path): media_files.append(item[audio_path]) package genanki.Package(deck) if media_files: package.media_files media_files package.write_to_file(output_path) print(f已生成牌组: {output_path})使用genanki时需要注意Model和Deck的 ID 必须是整数且在当前牌组集合中尽量保持唯一。如果你复制了别人的代码一定要改掉 ID否则在不同牌组之间可能产生同步冲突。6.2 调用示例if __name__ __main__: sample_sentences [ { text: How you doin?, translation: 你好吗老友记经典台词, source: Friends S01E01 00:00:01, audio_path: data/output/audio/1.mp3, audio_name: 1.mp3, }, { text: Im fine, thank you., translation: 我很好谢谢。, source: Friends S01E01 00:00:05, audio_path: data/output/audio/2.mp3, audio_name: 2.mp3, }, ] build_deck(sample_sentences)运行后会在当前目录生成english_sentences.apkg。双击该文件即可导入 Anki。6.3 从 CSV 批量生成上面的示例是手工构造数据。实际项目中数据来自第 4 节的 CSV 输出。可以写一个加载 CSV 并逐行生成卡片的流程import csv def load_sentences_from_csv(csv_path: str) - list: sentences [] with open(csv_path, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: sentences.append({ text: row[text], translation: , source: fSRT index {row[index]}, audio_path: , audio_name: , }) return sentences这里的中文翻译暂时留空因为自动翻译质量不稳定。一个可选的方案是后续手动补翻译或者用机器翻译接口做初稿再人工校对。注意翻译接口可能产生费用生产环境要控制调用量。7. 实战用 ffmpeg 截取台词音频复习时如果只有文字卡片效果会打折扣。加上原声音频可以同步训练听力和发音。7.1 按时间戳截取音频文件路径scripts/extract_audio.pyimport subprocess from pathlib import Path def extract_audio(video_path: str, start: float, end: float, out_path: str) - None: 从视频中截取 start 到 end 秒的音频保存为 mp3 Path(out_path).parent.mkdir(parentsTrue, exist_okTrue) cmd [ ffmpeg, -y, -ss, str(start), -to, str(end), -i, video_path, -vn, -acodec, libmp3lame, -q:a, 2, out_path, ] subprocess.run(cmd, checkTrue, capture_outputTrue) if __name__ __main__: extract_audio( video_pathdata/raw/friends_s01e01.mp4, start1.0, end4.0, out_pathdata/output/audio/1.mp3, ) print(音频截取完成)这里有三点要说明-ss放在-i之前是快速 seek速度很快但时间精度可能差几帧。如果追求精确可以把-ss放到-i之后但解码速度会慢。个人学习场景下建议在视频中适当给前后留出 0.2 到 0.5 秒的余量避免音频开头和结尾突然被切断。7.2 结合 CSV 批量截取更好的做法是从第 4 节生成的 CSV 中读取每个句子的 start 和 end批量截取音频import csv from extract_audio import extract_audio VIDEO_PATH data/raw/friends_s01e01.mp4 with open(data/output/sentences.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: start float(row[start]) end float(row[end]) out_path fdata/output/audio/{row[index]}.mp3 extract_audio(VIDEO_PATH, start, end, out_path) print(批量音频截取完成)注意批量处理耗时可能较长建议先处理前 20 条验证效果再全量执行。中间的音频文件可以随时删除重建不影响原视频。8. 常见问题与排查思路实际操作时很多人会在环境或数据格式上卡住。下面汇总几个高频问题。问题现象常见原因解决思路读取 SRT 报UnicodeDecodeError字幕文件本身是 GBK 或 ANSI 编码用 chardet 检测编码先转成 UTF-8 再解析解析出来全是空行字幕文件不是标准 SRT 格式可能是 ASS/SSA先用工具转换成 SRT 格式或写对应解析器字幕文本包含{\an8}等标签ASS 字幕样式指令残留在文本中用正则\{\\[a-z]\}去除生成的 Anki 卡片没有声音media_files未关联或音频文件名与卡片不对应确认 genanki 的media_files包含所有音频路径ffmpeg 找不到文件视频路径相对路径错误或包含中文/空格用绝对路径确保路径中没有特殊字符一天学太多句子导致复习量爆炸新句子数量设置不克制每天新句控制在 10-20 句按复习压力动态调整机器翻译结果不准确单句脱离上下文自动翻译难以理解人工校对或结合整段台词上下文判断9. 最佳实践与工程建议9.1 先做单集闭环再批量扩展不要上来就解析 10 季字幕。建议先拿一集字幕跑通全流程解析字幕。过滤英文句子。生成 CSV。截取前 20 句音频。生成牌组并导入 Anki。实际学习几天验证流程是否顺手。确认没有问题后再批量处理更多剧集。9.2 卡片要有来源信息每张卡片至少记录句子所在剧集、集数和时间戳。这样复习时如果对某个表达有疑问可以回到原片再看上下文形成“卡片 → 原片场景”的闭环。在卡片模板中增加Source字段就是出于这个考虑。9.3 控制每日新句数量SRS 的核心价值在于“可持续”。每天学习 10 句新句子配上 20-40 句复习坚持 3 个月就能积累近 1000 个场景句。相比每天猛学 100 句然后放弃细水长流的收益更大。Anki 内置的复习量设置可以调整。建议把“新卡片/天”设在 10-20“复习上限/天”设在 100-150。如果某天复习排队太长优先保复习少开新卡。9.4 尊重素材版权美剧字幕和视频资源属于版权保护内容个人学习用途通常被视为合理使用但仍然不应该公开传播、打包出售或制作成盈利课程。使用开源项目时也请遵守项目仓库的 License 条款。9.5 脚本安全与数据保护批量处理脚本只应“读取原素材、生成新文件”不要设计删除原始文件的逻辑。如果需要对中间文件做清理先在临时目录测试确认无误并保留备份。对于学习数据卡片、复习记录建议定期从 Anki 导出备份到本地或私有网盘。9.6 日志与可观测性当处理多集字幕时简单加几个print不够。建议用 Python 内置的logging模块记录处理进度和失败条目import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) def process_one_episode(path): logging.info(Processing %s, path) try: records parse_srt(path) logging.info(Parsed %d records, len(records)) except Exception as e: logging.error(Failed on %s: %s, path, e)这样批量跑脚本时即使中间有失败任务也能快速定位到具体文件而不是在大量输出中翻找。10. 总结与后续学习方向围绕everyone-can-use-english的项目思路本文重点拆解了“字幕解析 → 句子筛选 → 音频截取 → 卡片生成 → 间隔复习”的完整学习闭环。你可以把它理解成一条学习流水线每个环节都可以用脚本独立处理也可以组合成全自动流程。如果你已经跑通了本文的脚本下一步建议往这几个方向继续深入语法标注用 spaCy 或 NLTK 对句子做词性标注标记词组和搭配。难度分级基于词频和句长对台词句子做难度排序从易到难顺序学习。发音对比用语音识别接口对你的跟读录音和原声做对比评分。复习数据统计分析 Anki 导出的复习记录找出自己的“易忘点”。英语学习没有银弹但好的工具和流程能把“坚持”变得更容易。动手跑通这套流水线然后选一部你真正喜欢的剧从第一集开始积累自己的句子库。如果本文对你有帮助可以先收藏备用实际使用中遇到什么问题也欢迎在评论区交流。
返回列表