从核专业英语词汇.doc到SQLite FTS5术语库:解析、清洗与检索
简介这份《核专业英语词汇》文档面向核物理、核工程、放射化学及核医学等方向的学习者与科研人员帮助解决英文文献阅读与专业交流中术语不熟、表达不准的问题适合本科高年级、研究生及核电行业从业者作为随身词汇手册使用。资源包含1个doc文件压缩包约60KB体量轻便便于在电脑或移动端随时查阅。文档按主题分节整理涵盖核物理基本概念、放射性、核反应等模块收录元素、粒子、离子、原子核、质子、中子、核子、化学键、化合物等基础词条以及能级、裂变、聚变、衰变、链式反应、辐射、超铀元素等核心术语并附原子序数、质量数、同位素、结合能、质量亏损、截面、屏蔽、光电效应等延伸词汇中英对照、条目清晰。目前已有60人学习下载适合用于课前预习、文献速查与考试复习也便于按章节构建个人术语表。1. 一份核专业英语词汇.doc为什么值得当成数据资产处理手上有一份核专业英语词汇.doc多半是这么来的项目结题、老工程师退休、外包翻译交付文件在群里转了七八手打开一看是两栏中英对照有的用制表符有的用破折号还有几页是手画的表格。它被当作附件躺了很多年只有在写英文标书或者翻反应堆系统描述时才被翻出来 CtrlF。问题就在这个 CtrlF 上。老式 .doc 是 OLE 复合二进制很多工具连正文都读不全更别说按字段检索词条里混着全角括号、希腊字母、同位素下标和简繁两套写法同一个英文词在不同页里翻成不同中文。真正想解决的是三件事把词条变成结构化数据、让查询能容忍拼写变体和词形变化、再把它回灌到写作和翻译流程里做一致性校验。下面这套流程不依赖任何商业术语管理软件全部用命令行加 Python 跑通适合两类人需要维护核领域术语库的文档/翻译工程师以及只想把这份 .doc 变成自己能查的本地工具的开发。核工程、辐射防护、核燃料循环这些方向的词汇都在射程内。2. 解析核专业英语词汇.doc从 OLE 二进制到结构化词条解析的第一步不是写代码而是确认文件格式。后缀名在 .doc 这个年代几乎没有可信度很多人当年把 .docx 直接改名成 .doc 发出去用 Word 打开正常用 Python 打开就报 PackageNotFoundError。搞清楚格式之后转换成现代格式再解析比硬啃二进制要省事得多。2.1 先确认文件是真 .doc 还是改了后缀的 .docx用file看魔数是最快的判断方式不需要装 Office# 看文件真实类型而不是看后缀 file -b 核专业英语词汇.doc # 老式二进制 docComposite Document File V2 Document, ... # 其实是 docxMicrosoft Word 2007 或 Zip archive data # 如果输出里有 Zip直接按 zip 解一下看内部结构 unzip -l 核专业英语词汇.doc | head -20 # 看到 word/document.xml 就说明它是 docx判断结果决定后面用哪条路径真 .doc 必须先转换伪装的 .docx 可以直接mv成 .docx 交给 python-docx。注意有些文件带密码保护file会显示 CDF 但 python-docx 会抛异常这种情况先让文件提供方解密不要试图绕过。2.2 用 LibreOffice headless 把 .doc 批量转成 .docx服务器上没有 Word最稳的转换器是 LibreOffice 的命令行模式。它保留表格结构比 antiword 纯文本抽取更适合词条场景# 单文件转换输出到 converted 目录 soffice --headless \ -env:UserInstallationfile:///tmp/lo_profile \ --convert-to docx:MS Word 2007 XML \ --outdir ./converted \ ./核专业英语词汇.doc # 目录里有一批 doc 时一条命令全转 find ./raw -name *.doc -print0 | xargs -0 -I{} soffice --headless \ -env:UserInstallationfile:///tmp/lo_profile \ --convert-to docx --outdir ./converted {}--headless表示不开图形界面-env:UserInstallation指定独立的用户配置目录这是并发或反复调用时最容易踩的坑——不隔离的话第二个进程会连到已有实例上直接退出输出目录空空如也。--outdir必须提前存在LibreOffice 不会自动建目录。提示转换后一定要抽查页数。老 .doc 里的文本框和嵌入对象在转换中可能丢字词条密度高的表格尤其明显抽样对比几页再往下走。2.3 python-docx 抽取表格词条与段落词条同一份词汇表里通常有两种形态并存一部分是标准表格一部分是老工程师手打的「英文 — 中文」段落。两种都要覆盖from docx import Document import json, re doc Document(converted/核专业英语词汇.docx) rows [] # 形态一表格一行为一条词 for table in doc.tables: for row in table.rows: cells [c.text.strip() for c in row.cells] if len(cells) 2 and cells[0]: rows.append(cells) # 形态二段落「英文 — 中文」分隔符可能是破折号、冒号或制表符 TERM re.compile( r^(?Pen[A-Za-z][A-Za-z0-9\-\(\)/ ,\.]{1,80}?) r\s*[—\-–:\t]\s* r(?Pzh[\u4e00-\u9fa5].*)$ ) for p in doc.paragraphs: m TERM.match(p.text.strip()) if m: rows.append([m.group(en), m.group(zh)]) with open(terms_raw.jsonl, w, encodingutf-8) as f: for r in rows: f.write(json.dumps({en: r[0], zh: r[1]}, ensure_asciiFalse) \n) print(抽取词条数:, len(rows))TERM里中文部分强制以汉字开头[\u4e00-\u9fa5]可以挡掉「reactor coolant — the fluid used to...」这种英文释义行被误当成译文{1,80}限制英文长度防止跨行贪婪匹配把整段吞进来ensure_asciiFalse让 JSONL 里保留可读中文方便肉眼比对。如果抽出来的条数明显偏少先打印几十行doc.paragraphs的原文确认分隔符是不是全角竖线或者连续空格。2.4 落盘 JSONL 的字段设计与抽样校验原始抽取结果只保证「有没有」不保证「对不对」字段设计要预留清洗空间字段含义来源备注en英文术语原形表格/段落保留原始大小写后续统一小写做键zh中文译名表格/段落保留原始写法简繁待归一abbr缩写括号内容如 RPV、LOCA可能为空note备注/限定第二个括号如「压力容器内构件」src来源文件名行号出问题时能回溯到原文抽样校验的做法是固定随机种子抽 30 条人工过一遍统计错误率。错误率超过 5% 就不要继续往下清洗回去调 §2.3 的正则否则脏数据会被后面的归一化放大。3. 核专业英语词条清洗上下标、同位素写法与同词多译清洗阶段的目标不是把词条改得好看而是让同一个概念只有一种可比较的表示。核领域的特殊性在于大量符号损耗同位素下标、希腊字母、上下标单位、以及简繁两套译名。这些字符在 Word 里靠富文本格式呈现一旦导出成纯文本就会丢或者变成难以识别的 Unicode 码位。3.1 中英混排里最容易丢信息的四类字符脏数据类型典型例子处理方式同位素下标U-235 与 U₂₃₅、²³⁵U先替换下标数字再归一化希腊字母混用α、β、γ 与 alpha、beta、gamma统一为希腊字母另存拼写别名全角括号/逗号 与 () ,NFKC 归一化简繁混排反應器 与 反应堆OpenCC 转简体后比对这四类里最麻烦的是同位素。NFKC 会把下标字符 ₂U2082直接压成普通数字 2一旦压掉就再也分不清 U-235 里的 235 是下标还是普通数字而术语库里的检索恰恰依赖这个区别。3.2 用一条正则切分「英文-缩写-中文-备注」清洗前先把词条拆成字段正则里给每个可选部分留命名组拆不动就整条进隔离文件不要偷偷丢弃import re # 例core barrel 堆芯吊篮RPV 内构件 # LOCA (loss-of-coolant accident) 失水事故 ENTRY re.compile( r^(?Pen[A-Za-z][^(\n]{1,60}?) r(?:\s*[(]\s*(?Pabbr_or_en[A-Za-z][A-Za-z0-9\-\./ ]{0,30})\s*[)])? r\s*[—\-–:\t]*\s* r(?Pzh[\u4e00-\u9fa5][^(\n]{0,60}) r(?:[(](?Pnote[^)\n]{0,60})[)])?\s*$ ) def parse(line: str): m ENTRY.match(line.strip()) if not m: return None d m.groupdict() # 括号里全大写且不含空格才认作缩写 raw (d[abbr_or_en] or ).strip() d[abbr] raw if raw and raw.isupper() else None return dabbr的判定用isupper()而不是长度是因为核领域缩写基本全大写RPV、LOCA、BWR而括号里出现小写多词时通常是英文全称或补充说明应并入 note。zh段限制 60 字符超过的多半是整句话宁可进隔离文件人工判断。3.3 NFKC 归一化的顺序陷阱正确的顺序是先保留下标语义再做 NFKC。顺序反了同位素信息就永久丢失import unicodedata from opencc import OpenCC cc OpenCC(t2s) # 繁体转简体 SUB {chr(0x2080 i): str(i) for i in range(10)} # ₀-₉ SUP {chr(0x2070 i): str(i) for i in range(10)} # ⁰ ¹ ² ³部分码位不连续 def protect_isotope(s: str) - str: 把下标数字显式写成 _235 形式保住同位素语义 out [] for ch in s: if ch in SUB: out.append(_ SUB[ch]) elif ch in SUP: out.append(^ SUP[ch]) else: out.append(ch) return .join(out) def norm(s: str) - str: s protect_isotope(s) # 第 1 步先固化上下标 s unicodedata.normalize(NFKC, s) # 第 2 步全角转半角 s s.replace(\u2014, -).replace(\u2013, -) # 破折号当连字符 s re.sub(r\s, , s).strip() return cc.convert(s) # 第 3 步繁转简NFKC会顺带把罗马数字、连字、全角空格一并处理掉这是它的价值代价就是必须放在上下标保护之后。OpenCC的t2s只做字形转换不处理词汇差异比如「質子」到「质子」没问题但地区用词差异它不管术语库里的地区用词差异需要单独维护一张映射表。3.4 同英文多中文合并而不是删除核领域一个英文词对应多个中文译名是常态尤其涉及港台资料和不同院所习惯时。处理原则是全部保留用一个英文键挂多个中文值查询时一起返回from collections import defaultdict by_en defaultdict(set) for t in terms: key norm(t[en]).lower() by_en[key].add(norm(t[zh])) # 只想看冲突清单 conflicts {k: v for k, v in by_en.items() if len(v) 1} print(同词多译条数:, len(conflicts))冲突清单不要自动合并它本身就是有价值的信息。工程上的做法是给每个中文值加一个preferred标记默认取出现频次最高的那个其余作为别名参与检索但不参与输出这样既不影响召回也不会让导出的术语表出现两个答案。4. 用 SQLite FTS5 把核术语库做成可检索服务词条数量在几千到几万这个量级SQLite 完全够用而且单文件、零部署适合塞进项目仓库或者随文档一起分发。关键是别用LIKE %moderator%硬扫那既慢又查不出词形变化用 FTS5 建全文索引把前缀匹配和列限定交给它。4.1 term 表与 FTS5 外部内容表主表存事实FTS5 建外部内容表避免同一份数据存两遍CREATE TABLE term ( id INTEGER PRIMARY KEY, en TEXT NOT NULL, zh TEXT NOT NULL, abbr TEXT, alias TEXT, -- 同一中文的其他译名用 | 分隔 domain TEXT, -- reactor / radiation / fuel-cycle src TEXT, updated TEXT DEFAULT (datetime(now)) ); CREATE UNIQUE INDEX ux_term_en_zh ON term(en, zh); CREATE INDEX ix_term_abbr ON term(abbr) WHERE abbr IS NOT NULL; CREATE VIRTUAL TABLE term_fts USING fts5( en, zh, abbr, contentterm, content_rowidid, tokenizeunicode61 remove_diacritics 2 );contentterm声明这是外部内容表FTS5 不重复存储正文只存索引磁盘占用大概降到一半。unicode61 remove_diacritics 2让带变音符号的拉丁字母如 français、Ångström在检索时忽略重音差异。中文检索如果希望支持任意子串匹配把tokenize换成trigram更合适但它要求 SQLite 3.34 以上先执行sqlite3 --version确认版本trigram 的代价是索引体积明显变大且查询串必须至少 3 个字符。4.2 导入、触发器同步与增量更新用 Python 的 sqlite3 模块导入配合触发器让索引自动跟着主表走CREATE TRIGGER term_ai AFTER INSERT ON term BEGIN INSERT INTO term_fts(rowid, en, zh, abbr) VALUES (new.id, new.en, new.zh, new.abbr); END; CREATE TRIGGER term_ad AFTER DELETE ON term BEGIN INSERT INTO term_fts(term_fts, rowid, en, zh, abbr) VALUES (delete, old.id, old.en, old.zh, old.abbr); END;import sqlite3, json con sqlite3.connect(nuclear_terms.db) con.executescript(open(schema.sql, encodingutf-8).read()) with open(terms_clean.jsonl, encodingutf-8) as f: for line in f: t json.loads(line) con.execute( INSERT OR IGNORE INTO term(en, zh, abbr, domain, src) VALUES (?, ?, ?, ?, ?), (t[en], t[zh], t.get(abbr), t.get(domain), t.get(src)), ) con.commit() con.execute(INSERT INTO term_fts(term_fts) VALUES (rebuild)) # 首次全量重建 con.commit()INSERT OR IGNORE依赖ux_term_en_zh唯一索引重复导入同一份 .doc 不会产生重复词条这比每次先清库再导要安全。rebuild命令只在首次导入或索引损坏时执行日常增量导入由触发器负责重复执行 rebuild 会白白重扫全表。4.3 前缀、短语与列限定查询怎么写FTS5 的查询语法里*是前缀匹配双引号是短语匹配列名 :把匹配限定到某一列查询场景MATCH 表达式说明查 moderator 开头的词en : moderat*前缀匹配一次带出 moderator/moderating查固定短语control rod词序必须一致只查中文列zh : 冷却剂排除英文列干扰查缩写abbr : LOCA缩写列独立建索引多条件组合zh : 冷却剂 AND en : pump两个条件同时满足def search(q: str, limit: int 20): sql (SELECT t.en, t.zh, t.abbr, t.domain FROM term_fts f JOIN term t ON t.id f.rowid WHERE term_fts MATCH ? ORDER BY rank LIMIT ?) return con.execute(sql, (q, limit)).fetchall() print(search(zh : 冷却剂)) print(search(en : moderat*))ORDER BY rank是 FTS5 的默认相关性排序按 BM25 变体打分不需要额外建索引。用 trigram 分词器时MATCH 里直接写子串即可不要加*加了反而不匹配。4.4 rapidfuzz 兜住拼写变体与词形变化全文索引解决不了拼错、词序颠倒、单复数混用的问题这些交给模糊匹配补from rapidfuzz import process, fuzz terms con.execute(SELECT en, zh FROM term).fetchall() en_list [t[0] for t in terms] def fuzzy_lookup(q: str, cutoff: int 85, limit: int 3): hits process.extract( q, en_list, scorerfuzz.WRatio, # 综合词序、长度、部分匹配 score_cutoffcutoff, limitlimit, ) return [(h[0], terms[h[2]][1], round(h[1], 1)) for h in hits] print(fuzzy_lookup(loss of coolant accident)) # [(loss-of-coolant accident, 失水事故, 96.0), ...]score_cutoff85是核术语场景下比较稳的阈值低于 80 会出现大量同族词误报coolant 和 coolant pump高于 92 又会漏掉连字符和单复数差异。WRatio比ratio更适合术语因为它会自动尝试词序归一化和部分匹配。实际服务里先用 FTS5 拿精确结果命中为空再走模糊匹配两条路径都空才提示「术语库中未收录」这样既保证速度也保证召回。5. 把核术语库接进文档流程TBX 导出与术语一致性校验5.1 导出 TBX 给 CAT 工具术语库只有被工具消费才有价值。Trados、memoQ、OmegaT 这类工具都能读 TBX导出时用最小可用结构即可import xml.etree.ElementTree as ET root ET.Element(martif, {type: TBX-Basic, xml:lang: en}) body ET.SubElement(root, text) body_el ET.SubElement(body, body) for en, zh, abbr in con.execute(SELECT en, zh, abbr FROM term): entry ET.SubElement(body_el, termEntry, {id: en}) for lang, text in ((en, en), (zh-CN, zh)): ls ET.SubElement(entry, langSet, {xml:lang: lang}) nt ET.SubElement(ls, ntig) ET.SubElement(nt, termGrp).text text ET.ElementTree(root).write(nuclear_terms.tbx, encodingutf-8, xml_declarationTrue)typeTBX-Basic是兼容性最好的方言xml:lang用zh-CN而不是zh部分 CAT 工具只认带地区的语言标签。缩写不建议塞进 termGrp工具里会当成第二个译名显示需要的话用descrip typeabbreviation挂载。5.2 Aho-Corasick 做英文稿术语一致性扫描写完英文标书或论文后用术语库反扫一遍比人眼靠谱。几万条词做多模匹配正则逐个拼太慢用 Aho-Corasickimport ahocorasick, sqlite3 A ahocorasick.Automaton() for en, zh in con.execute(SELECT en, zh FROM term WHERE length(en) 3): A.add_word(en.lower(), (en, zh)) A.make_automaton() def scan(text: str) - dict: hits {} lower text.lower() for _, (en, zh) in A.iter(lower): hits[en] zh return hits print(scan(The control rod drive mechanism and the reactor coolant pump ...))length(en) 3过滤掉 of、in、rod 这类过短的词否则一篇英文稿会误报成百上千次统一lower()保证大小写不敏感。想再严格一些可以在命中后回查原文字符两侧是否为字母把子串误报滤掉。扫描结果和译文里的中文对照就是一份需要人工确认的差异清单。5.3 三条 SQL 验证术语库质量导入完成后别急着交付先跑三条查询检查项SQL 片段判读同英文多中文SELECT en, COUNT(*) c FROM term GROUP BY en HAVING c 1数量大说明译名未统一空译名/疑似解析残留SELECT * FROM term WHERE zh OR length(zh) 2逐条回原文核对从未被引用的领域SELECT domain, COUNT(*) FROM term GROUP BY domain分布严重倾斜说明抽取漏页-- 同英文多中文的完整清单按英文分组 SELECT en, group_concat(zh, | ) AS variants FROM term GROUP BY en HAVING COUNT(*) 1 ORDER BY COUNT(*) DESC LIMIT 50;把这条 SQL 的输出贴进 issue让懂核工程的人逐条判哪个译名是单位里通用的比任何自动归一化策略都靠谱——术语的对错最终是工程共识问题不是字符串距离问题。本文还有配套的精品资源点击获取