从PDF词典到可查询数据库:英语姓名数据抽取与SQLite检索实战
2026/9/18 14:12:44 网站建设 项目流程

简介:这份资源是外研社出版、李慎廉等编著的《英语姓名词典》PDF电子版,面向语言学者、翻译人员、英语教师及中高阶学习者,用于查证英语姓名的来源、含义、变体与国别使用差异,解决姓名文化背景理解与跨文化交际中的释义需求。压缩包内仅含1个PDF文件,约53KB,以字母为序编排,从A到Z逐条解释姓名,内容涵盖词源、历史背景、性别区分及在英格兰、苏格兰、爱尔兰等地的使用情况。以R部为例,Rab、Rabb、Rabbit、Rabbitt、Raby等条目不仅标注昵称与父名来源,还说明“忠告+信息”“猎犬误作兔”等词义演变,并梳理同一姓名在发音、拼写与流行度上的地域差异。目前已有393人学习下载,适合作为英语姓名研究、翻译查证与课堂教学的案头参考工具。

1. 从一本 PDF 词典到可查询的姓名数据:这件事到底难在哪

很多人第一次拿到《英语姓名词典》这类 PDF 时,想法都很朴素:不就是个词典吗,Ctrl+F 不就行了。真上手才发现,几百页的 PDF 里,姓名条目、音标、词源、变体、汉译全挤在一起,Ctrl+F 只能定位到某一页,想批量比对「Smith 和 Smyth 是不是同源」「某个名字的性别倾向」几乎没法做。更麻烦的是,扫描版 PDF 里的文字是图像,连搜索都搜不到。

这个标题背后真正要解决的问题,是把一本结构化的英语姓名工具书,从「只能人眼翻」变成「能程序化查询」的数据资产。适合三类人:做国际化产品、需要处理用户英文名的工程师;做语料、词源、命名研究的同学;以及手头有一堆 PDF 工具书、想统一转成可检索格式的人。核心链路就四步:判断 PDF 类型、抽取文本、解析成结构化字段、落到可查询的存储里。下面按这条链路一层层拆。

2. 先判断 PDF 是文本层还是扫描件,再决定抽取路线

2.1 用 pdffonts 和 pdftotext 快速体检

拿到 PDF 第一件事不是写代码,是判断它有没有文本层。扫描件和原生电子版的处理路线完全不同,选错了后面全是白干。最省事的办法是用 poppler 工具集里的两个命令。

# 看 PDF 里嵌入了哪些字体,有字体基本说明有文本层 pdffonts "英语姓名词典.pdf" # 直接尝试抽第一页文本,能出字就是文本层 PDF pdftotext -f 1 -l 1 "英语姓名词典.pdf" - | head -50 # 看每页尺寸和页数,估算工作量 pdfinfo "英语姓名词典.pdf"

pdffonts输出里如果有Type 1TrueType这类字体记录,说明文字是矢量绘制的,pdftotext大概率能直接抽出可读文本。如果字体列表是空的,或者pdftotext抽出来全是空白、乱码,那基本就是扫描件,得走 OCR。pdfinfo给出的Pages用来估算后面 OCR 的耗时,几百页扫描件用 CPU 跑 OCR 可能要几十分钟到几小时。

提示:先抽 3 到 5 页样本人工核对,别一上来就全量跑。词典类 PDF 经常有双栏排版,pdftotext默认按阅读顺序输出,双栏会被打乱成左右交错,这个坑后面单独说。

2.2 文本层 PDF 用 pdfplumber 做版面感知抽取

pdftotext快,但它不理解版面。词典条目通常有缩进、悬挂、多列,用 Python 的 pdfplumber 能拿到每个字符的坐标,自己按坐标重建阅读顺序。

import pdfplumber with pdfplumber.open("英语姓名词典.pdf") as pdf: page = pdf.pages[10] # 抽第 11 页做样本 # extract_words 返回每个词的坐标,便于按栏切分 words = page.extract_words(keep_blank_chars=False, use_text_flow=False) for w in words[:20]: print(round(w["x0"], 1), round(w["top"], 1), w["text"])

extract_wordsuse_text_flow=False表示按坐标而不是 PDF 内部流顺序返回,这样双栏排版时能靠x0把左右两栏分开。keep_blank_chars=False会丢掉纯空格,减少噪声。拿到坐标后,常见做法是按页面宽度中位数切左右栏,再各自按top排序拼回文本。这一步做对了,后面解析条目才不会被串行。

2.3 扫描件走 OCR:tesseract 与 PaddleOCR 的取舍

扫描件没有文本层,只能 OCR。英文为主的词典,tesseract 配英文语言包就够用,胜在轻量、命令行友好;如果页面里有大量中文释义,PaddleOCR 对中英混排的识别更稳。

# 先把 PDF 每页转成 300dpi 的 PNG,OCR 对分辨率敏感 pdftoppm -r 300 -png "英语姓名词典.pdf" page # tesseract 识别单页,-l eng 指定英文,--psm 6 假设是统一文本块 tesseract page-011.png stdout -l eng --psm 6

-r 300是分辨率,低于 200dpi 小字号音标容易糊,高于 400dpi 收益递减还拖慢速度。--psm 6告诉 tesseract 把整页当成一个均匀文本块,适合词典这种规整排版;如果页面有分栏,用--psm 1让它自动做版面分析。OCR 结果一定要留原始输出,别直接覆盖,后面校对时还要回溯。

3. 把连续文本解析成「词条-音标-释义」结构化记录

3.1 先定义字段模型,再写解析规则

抽取出来的是一大段文本,要变成能查询的数据,得先想清楚字段。英语姓名词典的典型条目包含:词条名、音标、性别倾向、语源、变体拼写、汉译。字段定得越早,解析规则越好写。

字段含义示例是否必填
headword姓名主词条Smith
phonetic音标/smɪθ/
gender性别倾向m/f/unisex
origin语源Old English
variants变体拼写Smyth, Smithe
translation汉译史密斯

字段模型确定后,解析就变成「用正则从文本块里抠出每一段」。词条名通常顶格或加粗,音标被斜杠或方括号包住,变体常以var.also引出。这些规律因书而异,所以第一步永远是人工看 20 条样本,把模式总结出来再写正则。

3.2 用正则和状态机切分条目

词典条目之间靠排版分隔,纯文本里往往表现为空行或特定符号。下面这段用状态机思路,遇到疑似词条行就开新记录,否则追加到当前记录的释义里。

import re # 词条行:行首大写字母开头,后面可能跟音标 HEADWORD = re.compile(r"^([A-Z][a-zA-Z'\-]+)\s*(/[^/]+/|\[[^\]]+\])?") PHONETIC = re.compile(r"/([^/]+)/|\[([^\]]+)\]") VARIANT = re.compile(r"(?:var\.|also)\s*[::]?\s*([A-Za-z,\s'\-]+)") def parse_entries(text): entries, cur = [], None for line in text.splitlines(): line = line.strip() if not line: continue m = HEADWORD.match(line) # 命中词条行且当前行较短,判定为新条目 if m and len(line) < 60: if cur: entries.append(cur) cur = {"headword": m.group(1), "raw": line} elif cur: cur["raw"] += " " + line if cur: entries.append(cur) # 二次抽取音标和变体 for e in entries: ph = PHONETIC.search(e["raw"]) e["phonetic"] = (ph.group(1) or ph.group(2)) if ph else None va = VARIANT.search(e["raw"]) e["variants"] = va.group(1).strip() if va else None return entries

HEADWORD用行首大写加可选音标来识别新条目,len(line) < 60是个经验阈值,防止把长释义行误判成词条。状态机的好处是容错:某一行没匹配上,不会丢数据,只是并进上一条。二次抽取把音标和变体从raw里单独拎出来,方便后面建索引。跑完一定要统计条目数,和 PDF 目录页标注的条目量对一下,差太多说明切分规则有问题。

3.3 用 difflib 做变体归并和重复检测

姓名词典里同一个名字常有多种拼写,解析完要能把它们关联起来。Python 标准库的 difflib 就能做近似匹配,不用引第三方依赖。

from difflib import SequenceMatcher def similar(a, b): return SequenceMatcher(None, a.lower(), b.lower()).ratio() # 找出相似度高于 0.85 的词条对,人工确认是否为变体 heads = [e["headword"] for e in entries] pairs = [(a, b) for i, a in enumerate(heads) for b in heads[i+1:] if similar(a, b) > 0.85] print(len(pairs), pairs[:10])

SequenceMatcher.ratio()返回 0 到 1 的相似度,0.85 是个偏保守的阈值,宁可少合并也别错合并。输出的是候选对,最终是否算变体还得人工过一遍,因为 Smith 和 Smyth 相似度高但语源关系需要词典本身确认。这一步的产物是一张变体映射表,后面查询时能把一个名字的所有拼写都召回。

4. 落到 SQLite 做可查询,再包一层命令行检索

4.1 建表与批量导入

结构化数据量不大,几百到几千条,SQLite 完全够用,单文件、零部署,适合当个人词典库。建表时把变体单独放一张关联表,避免主表里塞逗号分隔字符串。

CREATE TABLE names ( id INTEGER PRIMARY KEY, headword TEXT NOT NULL, phonetic TEXT, gender TEXT, origin TEXT, translation TEXT ); CREATE TABLE variants ( name_id INTEGER, variant TEXT, FOREIGN KEY (name_id) REFERENCES names(id) ); CREATE INDEX idx_headword ON names(headword); CREATE INDEX idx_variant ON variants(variant);

idx_headwordidx_variant是必须的,否则每次按名字查都要全表扫。导入用 Python 的sqlite3模块,配合executemany批量插入,几千条秒级完成。导入前把headword统一转小写存一份,查询时也转小写,避免大小写导致的漏查。

4.2 用 FTS5 做模糊和前缀检索

普通LIKE '%smith%'走不了索引,数据量一大就慢。SQLite 自带的 FTS5 全文索引能解决前缀和模糊匹配。

CREATE VIRTUAL TABLE names_fts USING fts5( headword, translation, content='names', content_rowid='id' ); -- 把已有数据灌进全文索引 INSERT INTO names_fts(rowid, headword, translation) SELECT id, headword, translation FROM names; -- 前缀查询:找所有以 Sm 开头的名字 SELECT * FROM names_fts WHERE names_fts MATCH 'Sm*';

content='names'表示 FTS5 表是外部内容表,不重复存数据,只存索引,省空间。MATCH 'Sm*'里的星号是前缀通配,查「以某串开头」特别顺手。注意 FTS5 默认对英文做分词,中文释义要单独处理,简单做法是把中文按字拆开存进另一个字段。

4.3 命令行检索脚本与参数说明

最后包一个命令行入口,日常查名字不用开数据库客户端。

import sqlite3, sys def search(kw, limit=20): conn = sqlite3.connect("names.db") cur = conn.cursor() # 先精确匹配,再前缀匹配,最后变体匹配 cur.execute("SELECT headword, phonetic, translation FROM names " "WHERE lower(headword)=? LIMIT ?", (kw.lower(), limit)) rows = cur.fetchall() if not rows: cur.execute("SELECT headword, phonetic, translation FROM names " "WHERE headword LIKE ? LIMIT ?", (kw + "%", limit)) rows = cur.fetchall() conn.close() return rows if __name__ == "__main__": for r in search(sys.argv[1]): print(r)

查询顺序是精确、前缀、变体,逐级放宽,保证最相关的结果排前面。limit默认 20,防止一次刷屏。sys.argv[1]是命令行传入的关键词,用法就是python query.py Smith。如果要做成常驻服务,把search挂到 Flask 或 FastAPI 的一个路由上即可,逻辑不用改。

5. 校对、增量更新与几个容易翻车的细节

5.1 用抽样比对控制 OCR 与解析错误率

OCR 和正则解析都会出错,关键是量化错误率。做法是从解析结果里随机抽 50 条,和 PDF 原页人工比对,统计字段级准确率。音标和汉译是最容易错的字段,因为符号多、字体特殊。准确率低于 95% 就回去调 OCR 分辨率或正则,别急着上线。校对结果存成一张corrections表,下次重新解析时优先用人工修正值覆盖,避免重复劳动。

5.2 增量更新:新版本 PDF 来了怎么合并

词典出修订版时,不要全量重跑覆盖。先对新 PDF 跑一遍抽取解析,得到新条目集,再和旧库按headword做 diff:新增的插入,消失的标记为废弃而不是删除,字段有变化的记录旧值。这样历史查询结果可追溯。合并脚本里用一个updated_at字段记录每条的最后变更时间,配合source_version标记来自哪一版,排查数据来源时很有用。

5.3 双栏串行、连字符断行、音标符号丢失三个坑

双栏串行前面提过,靠坐标切栏能解决,但页眉页脚会混进正文,得按top坐标过滤掉页面上下 5% 的区域。连字符断行是英文 PDF 的老问题,行尾的-要判断是真正的连字符还是断行符,常见做法是看-后面是否紧跟小写字母且下一行首也是小写,是则合并。音标符号丢失多发生在 OCR 阶段,/ːə这些字符识别率低,解决办法是 OCR 时用--psm 6加自定义字符白名单,或者干脆对音标区域单独用更高分辨率重跑一次。这三个坑处理完,整条链路才算真正稳。

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

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

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

立即咨询