ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CFA核心词汇PDF自动化解析:PyMuPDF+SQLite+Anki实战

CFA核心词汇PDF自动化解析:PyMuPDF+SQLite+Anki实战 简介CFA核心词汇PDF文档是一份面向金融从业者、投资者及备考CFA考生的专业术语整理材料系统梳理了金融、会计、投资、证券、保险等领域的核心词汇帮助读者快速理解并掌握常见英文术语。这份PDF为单个文件大小约3.29MB方便下载保存不受网络限制即可随时离线查阅尤其适合通勤或碎片时间学习。目前已有七百三十人学习下载受到不少金融学习者的关注与好评。内容采用中英对照形式逐条列出加快折旧、应收账款、天灾债券、活动收入、收购溢价、代理问题等三十余个核心词条并配有中文翻译、详细解释与实际例子从词源应用到投资场景一目了然。预览中还涵盖格林斯潘、美国存托股份、反摊薄条款、美式期权、年金、反收购措施等补充术语可帮助读者夯实金融英语基本功提升专业阅读、投资分析与CFA应试能力是备考与日常充电的实用工具。1. CFA核心词汇PDF与其背不如先解析拿到一份 CFA核心词汇.pdf下意识动作是翻开背。但如果你平时的工作是写代码我建议换个思路先把它当成一个数据处理问题。PDF 只是容器里面真正值钱的是一组结构化的词条——英文词汇、中文释义、例句、可能还有音标。把这些内容从排版噪声中抽出来再交给 SQLite、Anki 这类工具整个学习流程就自动化了。这份 PDF 通常不只是一个单词表。它可能夹着前言、页码、题注、双栏排版甚至扫描件。如果手动复制粘贴几千条词会让你花掉一个周末反过来花半小时搭一条解析流水线之后每次修订词表只要重跑一遍就行。这套方案适合两类人一是在备考 CFA 的工程师二是在做教育数据抽取的开发者。接下来的五步就是我实际会执行的全过程抽取、清洗、入库、导出、验证。2. 用 PyMuPDF 抽取 CFA 核心词汇 PDF 的文本层2.1 为什么选 PyMuPDF 而不是 pdfplumber 或 pdfminer.six处理 PDF 文本抽取业内常用三个库pdfminer.six、pdfplumber、PyMuPDFfitz。我不打算全面对比只说选择逻辑。如果你要保存表格结构pdfplumber 的 extract_tables 确实好用但 CFA 核心词汇多是一侧英文、一侧释义的排列只有少数是严格表格用纯文本抽取就够。PyMuPDF 的 get_text(text) 在提取纯文本时速度快内存命中低几千页也能扛得住。pdfminer.six 也常被提起但它的 API 偏底层要进入 layout 分析才能拿到正文代码明显更长。PyMuPDF 胜在能直接处理文字坐标后续如果要裁剪双栏区域fitz.Rect 随处可用。一句话以词条抽取为目标的脚本PyMuPDF 是最省事的选择。如果你要的是带坐标的块信息它也有 blocks 和 words 两种输出后续定位行首词也方便。2.2 最小抽取脚本一个函数拿到全部文本先把整个 PDF 的文本层拼出来看看里面到底是什么。新建 extract_pdf.pyimport fitz # PyMuPDF def extract_text_from_pdf(pdf_path): doc fitz.open(pdf_path) all_lines [] for page in doc: # 按页面顺序追加文本保留换行 all_lines.append(page.get_text(text)) return \n.join(all_lines) raw_text extract_text_from_pdf(CFA核心词汇.pdf) print(len(raw_text))这段代码里page.get_text(text) 返回的是页面文本块经过排序后的纯文本包含换行符把所有页拼接成一个字符串。print 出来的字符数很重要如果只有几十说明这不是内置文本而是扫描件文本层不存在需要走 OCR。如果字符数正常再打印前 50 行基本能看出排版样式。python extract_pdf.py运行后输出的字符数配合下面这段代码观察结构print(\n.join(raw_text.splitlines()[:50]))我会利用这 50 行判断两件事PDF 是否是双栏词条是否按“英文 空格 释义”排列。这两个判断直接影响后续裁剪和清洗策略。2.3 处理双栏排版与扫描版时的参数调整很多词汇 PDF 为了让页数少采用左右双栏。直接 get_text 的后果是左栏内容接到右栏词条和释义被打散。常见做法是按页面几何区域裁剪分别提取两栏再合并。PyMuPDF 的 clip 参数可以指定矩形范围def extract_two_column_page(page): w page.rect.width h page.rect.height left page.get_text(text, clipfitz.Rect(0, 0, w * 0.5, h)) right page.get_text(text, clipfitz.Rect(w * 0.5, 0, w, h)) return left, right这里 fitz.Rect(x0, y0, x1, y1) 的四个参数分别对应左边界、上边界、右边界、下边界用 w * 0.5 把页面切成左右两半。提取后按左栏在前、右栏在后的顺序拼回内部词序就恢复原样。这个比例不是万能如果 PDF 用两根竖线分成三栏就要按 x 坐标动态切分但大部分 CFA 核心词汇 PDF 是两栏。扫描版没有文本层得先用 ocrmypdf 把 PDF 转成带 OCR 文本的版本ocrmypdf --language engchi_sim CFA核心词汇.pdf CFA核心词汇_ocr.pdfOCR 后的 PDF 再用 PyMuPDF 抽取。注意 OCR 会有 2% 到 5% 的字符错误后续清洗时要把 “1、2、3” 这类序号和 “l、O、0” 的混淆列进替换规则。还有一种情况PDF 有文本层但某些页是图片嵌入文本层里只有图片注释。遇到这种情况PyMuPDF 的 page.get_images() 能列出图片再用 page.get_image_rects() 定位配合 Tesseract 单独识别图片区域。2.4 get_text 三种输出模式的适用差异PyMuPDF 的 get_text 有几种输出模式通常只用 text但偶尔需要 blocks 或 words 来定位某个词条。模式返回值适用场景text纯文本字符串常规全文抽取blocks按块返回文本和矩形坐标双栏区域过滤words逐词返回坐标与内容行首词定位我一般先用 text 跑通遇到定位需求再切换到 blocks。处理双栏时blocks 模式能直接判断每个块的中心 x 坐标小于页宽一半从而更准确地归类避免只靠 page.rect.width 的硬编码。如果你后续要修复 OCR 产生的错位词条words 模式会返回每个词的 (x0, y0, x1, y1) 和文本内容方便按坐标排序。3. 正则清洗与词形归一捞出 CFA 核心词汇候选词3.1 先看结构再选抽取策略文本层拿到之后清洗的优先级高于词频统计。如果 PDF 是标准的“词条 释义”结构每一行都可能有意义如果是从讲义里抽出来的自由文本就必须按词频和停用词过滤。我的做法是先把前 50 行打出来人工判断属于哪种。常见结构有两种。行首是英文词条后面跟着中文释义两者之间用空格或制表符分隔。整个 PDF 是连续的句子或段落核心词汇夹杂在阅读材料里。这两种情况处理方式不同。第一种适合按“行首词”抽取第二种需要把全文拆成单词再用词频排序。由于标题叫 CFA核心词汇.pdf更常见的是词表型 PDF但我不会提前假设先观察再决定。观察时还要留意词条之间的分隔符是制表符、多空格还是换行加序号这决定后续正则的写法。3.2 正则规则表去页码、去行号、去标点观察完成后先做一轮粗清洗。常见的噪声包括页码、页眉页脚、词条前的序号、空格制表符、以及 OCR 产生的多余符号。我维护一张规则表按顺序执行目标正则说明去掉数字开头行^\s*\d[\s\.\)、]页码或序号去掉首尾空白^\s\s$合并多空格[ \t]避免词条和释义断裂拆出英文单词[A-Za-z][A-Za-z\-’]*保留连字符和撇号过滤中文释义中的标点[。“”‘’]不影响英文词条这一轮不追求完美目标是把明显噪声去掉保留英文正文。在 Python 里用 re.sub 依序执行例如import re def clean_noise(text): lines text.splitlines() cleaned [] for line in lines: line re.sub(r^\s*\d[\s\.\)、], , line).strip() line re.sub(r[ \t], , line) if line: cleaned.append(line) return \n.join(cleaned)这段代码先处理行首的数字序号再把空白压缩最后去掉空行。注意re.sub不会删除正文里的数字金融词汇里的 “60%”“EPS 1.2” 会保留因为我们只删除行首的序号而不是全局删数字。如果 PDF 的序号是 “1.” “2、” 这类需要把规则里的[\s\.\)、]按实际分隔符补全。3.3 用 Counter 统计词频并做 lemmatization如果是自由文本型 PDF第二步就是词频统计。我用 collections.Counter 把清洗后的文本切分成词过滤掉停用词然后做词形还原避免 stocks 和 stock 被当成两个词。import re from collections import Counter def tokenize_count(text): words re.findall(r[A-Za-z][A-Za-z\-’]*, text.lower()) stopwords {the, a, an, of, in, to, for, and, or, is, are} counter Counter(w for w in words if w not in stopwords) return counter这里用小写化降低重复度用 stopwords 过滤高频虚词。但 CFA 核心词汇里有大量金融术语比如 futures、equity、derivative这些必须还原成原形。可以用 nltk 的 WordNetLemmatizerfrom nltk.stem import WordNetLemmatizer lemmatizer WordNetLemmatizer() def normalize_token(word): return lemmatizer.lemmatize(word) normalized Counter(normalize_token(w) for w in counter.elements())nltk 的 lemmatizer 默认把词当作名词处理动词和形容词需要指定词性。比如 hedging 需要还原为 hedge默认结果可能是 hedging。CFA 词汇里动词变形的词不少所以更稳妥的做法是先用简单 POS tagger 判断词性再传参。如果你不想为这个步骤引入太多依赖也可以用 spaCy 的token.lemma_。import spacy nlp spacy.load(en_core_web_sm) def normalize_with_spacy(text): tokens nlp(text) return [token.lemma_.lower() for token in tokens if token.is_alpha]WordNetLemmatizer 需要下载 nltk_dataspaCy 需要语言模型。对一份不会频繁变化的 PDF跑一次就行这点开销完全可接受。实际处理时我更倾向先用 Counter 跑词频观察头 20 个高频词是不是真的有价值。如果排在前面的都是 market、risk、capital 这类说明词频统计方向正确如果是一堆 company、business 这类通用词就需要加大停用词表范围。4. 用 SQLite 建可检索的 CFA 核心词汇库4.1 表结构与索引设计清洗后的词条不应该只存在内存里。把结果写进 SQLite后续查询、导出、去重都方便。表结构我会这样设计CREATE TABLE vocab ( id INTEGER PRIMARY KEY AUTOINCREMENT, term TEXT NOT NULL, pos TEXT, -- 词性可空 definition TEXT, -- 释义 source_line TEXT, -- 来自 PDF 的原始行 freq INTEGER DEFAULT 1, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE UNIQUE INDEX idx_vocab_term ON vocab(term COLLATE NOCASE);这张表的关键是 term 字段加上 COLLATE NOCASE 的唯一索引。CFA 词表里 GAAP 和 gaap 不应同时存在NOCASE 让索引在比较时忽略大小写。definition 字段保留中文释义source_line 保留原始行方便回溯验证。id 作为主键freq 为词频。注意如果是短语型核心词汇比如 capital asset pricing model把整段作为 term 会破坏唯一索引的效果。我的做法是保留两种形式短术语直接用原始文本长短语在清洗阶段额外拆成单子词在另一张表 term_ngram 里记录。这样单一 term 的搜索不会漏掉短语短语整体搜索也有对口索引。4.2 从清洗结果批量导入清洗完的词条以列表形式存在批量插入用 executemany 比逐条 execute 快得多import sqlite3 conn sqlite3.connect(cfa_vocab.db) conn.execute(PRAGMA journal_modeWAL;) conn.execute(BEGIN;) data [(equity, n, 股票股本, equity n. 股票股本, 12), (futures, n, 期货, futures n. 期货, 8)] conn.executemany( INSERT OR IGNORE INTO vocab (term, pos, definition, source_line, freq) VALUES (?,?,?,?,?), data ) conn.commit()插入前打开 WAL 模式再用 BEGIN 包住整个事务能显著减少磁盘写入次数。INSERT OR IGNORE 配合唯一索引重复词条直接跳过不会中断批次。如果你要保留重复来源的合并信息可以把 IGNORE 改成冲突更新例如ON CONFLICT(term) DO UPDATE SET freq freq excluded.freq。这样同一词条多次出现时freq 会累加而不是覆盖。4.3 一个不用打开数据库的查询命令库建好之后我通常不想再打开 DB Browser直接在终端查询。写一个小的 CLI 脚本import sqlite3 import sys def search_vocab(db_path, keyword): conn sqlite3.connect(db_path) conn.row_factory sqlite3.Row cur conn.cursor() sql SELECT term, pos, definition, freq FROM vocab WHERE term LIKE ? OR definition LIKE ? ORDER BY freq DESC LIMIT 20 pattern f%{keyword}% cur.execute(sql, (pattern, pattern)) return cur.fetchall() if __name__ __main__: rows search_vocab(cfa_vocab.db, sys.argv[1]) for row in rows: print(f{row[term]}\t{row[pos]}\t{row[definition]}\tfreq{row[freq]})使用参数化查询避免了用 f-string 拼 SQL 的注入风险。LIKE 匹配 term 和 definition 两个字段freq 排序会把高频词排前面这对背单词的优先级很有用。实际查询效果例如python search_vocab.py equity返回包含 equity 的所有词条按出现频率排序。这个命令在日常复习时比打开 PDF 查找快得多。如果你需要看某个词条在原始 PDF 的哪一行可以再补一条 SQL 按 source_line 过滤。4.4 给前端或脚本导出 JSON除了终端查询词库经常要供其他工具使用。SQLite 直接导出 JSON 很常见import sqlite3 import json conn sqlite3.connect(cfa_vocab.db) conn.row_factory sqlite3.Row rows conn.execute(SELECT term, pos, definition, freq FROM vocab).fetchall() data [dict(row) for row in rows] with open(cfa_vocab.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)这里 ensure_asciiFalse 是关键否则中文会变成 \uXXXX。dict(row) 把 sqlite3.Row 转换成普通字典json.dump 能直接序列化。导出后的 JSON 可以给 Anki 脚本、Python 背词脚本或者一个轻量前端页面用。5. 生成 Anki 导入卡并与原 PDF 抽样核对5.1 三字段 CSV英文、释义、例句有了 SQLite 词库下一步把它变成 Anki 能吃的文件。Anki 导入最稳定的格式是 UTF-8 编码的 CSV字段顺序必须在导入时明确。我生成的 CSV 只有三个字段英文单词、中文释义、例句。例句如果没有用 PDF 原始行代替。import csv import sqlite3 conn sqlite3.connect(cfa_vocab.db) cur conn.cursor() cur.execute(SELECT term, definition, source_line FROM vocab ORDER BY freq DESC) with open(cfa_deck.csv, w, encodingutf-8, newline) as f: writer csv.writer(f, delimiter,, quotingcsv.QUOTE_ALL) for term, definition, source in cur.fetchall(): example source if source else writer.writerow([term, definition, example])csv.writer 设置 QUOTE_ALL保证中文顿号、逗号、双引号不会破坏字段边界。newline 是 Python 写 CSV 时避免出现空行的必要参数。如果例句太长可以在写入前做截断但最好保留原始行Anki 前端可以用卡片样式自行折叠。5.2 Anki 导入时的字段映射与分隔符设置Anki 桌面版导入这一步很多人会卡住。打开 Anki选择“导入文件”选中 cfa_deck.csv然后在导入对话框中把“分隔符”改为逗号“字母”设为 UTF-8并把字段映射设置成 1-英文、2-中文、3-例句。如果 CSV 包含表头则勾选“第一个字段包含字段名”否则不要勾。Anki 设置项推荐值失败时的表现分隔符逗号整行变成一个字段字符集UTF-8中文乱码字段映射1 词汇, 2 释义, 3 例句卡片正反面错位允许 HTML勾选源文本中的换行丢失导入之前最好先备份 collection.anki21因为导入后的内容无法一键回滚。这一点新手特别容易忽视。Anki 的字段映射如果选错反复调整反而会浪费时间。导入完成后先打开一张卡看中文是否正常再批量浏览几条高频词确认排序和释义没有串位。5.3 用 20 条抽样验证准确率最后也是最容易被跳过的步骤验证。生成完 CSV 后随机抽 20 条和原始 PDF 比对计算准确率。抽样代码可以这样写import csv import random with open(cfa_deck.csv, r, encodingutf-8) as f: reader list(csv.reader(f)) random.seed(42) sample random.sample(reader, min(20, len(reader))) for term, definition, source in sample: print(f{term}\t{definition}\t{source})这里的 random.seed(42) 让抽样可复现方便下次对比。准确率按“term 单词正确且释义不含乱字符”计算。如果 20 条里准确率低于 70%先回到第 3 章检查清洗规则如果只有个别条目错误就在生成 CSV 时对 source_line 做二次校验比如过滤掉非 ASCII 的超长内容。抽样验证这步直接决定你导入到 Anki 的卡片到底有没有学习价值。没有经过与原 PDF 比对的词库再精美的工具也只是一个可靠性的黑盒。我一般会保留验证脚本每改一次清洗逻辑就重跑一遍抽样直到准确率稳定在 90% 以上再放心导入 Anki。本文还有配套的精品资源点击获取
返回列表