十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

机械词汇表docx解析:python-docx抽取、FTS5检索与反写

机械词汇表docx解析:python-docx抽取、FTS5检索与反写 简介这是一份面向机械工程专业学生、技术翻译、设备维护与制造从业者的中英机械词汇速查文档用于解决阅读英文图纸、工艺文件或备考专业英语时术语陌生、对照困难的问题。资源包仅含1个docx文件大小约46KB文档以笔画或字母顺序编排词条覆盖面包括手工工具、金属与非铁金属材料、力学与力矩、电子元件、工程制图、机床工件以及CPU等计算机基础术语并配有简明释义。内容预览显示螺丝批、二极管、力偶、电路断路器、工字梁、中密度纤维板、中碳钢等条目均有中英对照与解释适合课堂学习、车间实操和资料翻译时快速查阅。目前已有127人学习下载可作为机械专业入门阶段的随身词汇手册也便于后续按主题整理笔记与复习。1. 机械类专业词汇表.docx 为什么不能直接当词典用同事甩过来一个《机械类专业词汇表.docx》说「能不能做成接口输入中文出英文」。打开一看三列表格中间夹着跨页重复的表头术语里混着 H7/g6、Ra1.6、GB/T 1184 这类符号英文列半角和全角括号混着用还有合并单元格留下的空位。它对人友好对程序极其不友好——python-docx 读出来是一堆 Paragraph 和 Cell字段边界全靠肉眼判断。要解决的是整条链路把 docx 里的机械术语抽成结构化数据落进能查的库再能反写回一份格式合规的词汇表。做术语管理、翻译记忆库、CAD 插件术语提示、工业知识库里那套检索的工程师都会撞上这个文件。下面按「先拆结构、再抽字段、再落库、最后反写校验」的顺序走一遍。2. 用 python-docx 拆开机械类专业词汇表.docx 的词条结构2.1 最小可跑的读取脚本先别急着写解析器把文档的骨架打出来看。python-docx 的Document对象把正文拆成两个平铺序列doc.paragraphs和doc.tables。from docx import Document doc Document(机械类专业词汇表.docx) # 1) 段落承载标题、章节分组名、说明文字 for i, p in enumerate(doc.paragraphs): if p.text.strip(): print(f[P{i}] style{p.style.name!r} text{p.text!r}) # 2) 表格绝大多数机械词汇表把词条放在这里 for ti, tb in enumerate(doc.tables): print(f--- table {ti}: rows{len(tb.rows)} cols{len(tb.columns)}) for ri, row in enumerate(tb.rows): # row.cells 里合并单元格会重复出现先原样打出来看现象 print(ri, [c.text.strip() for c in row.cells])逻辑说明段落和表格是两个互不相交的序列各自的索引和正文里的真实位置无关。参数上p.style.name比p.text更有信息量Heading 1、Heading 2往往就是词汇表的分类维度如「齿轮传动」「公差配合」「热处理」而Normal才可能是词条。提示直接把doc.paragraphs和doc.tables分别遍历会丢掉「分类标题 → 词条表格」的从属关系。这是后面归类字段丢失的根因。2.2 按文档流真实顺序遍历段落与表格正确做法是遍历document.xml的 body 子元素遇到w:p产段落遇到w:tbl产表格。from docx import Document from docx.table import Table from docx.text.paragraph import Paragraph def iter_blocks(doc): 按正文流的真实顺序交替产出 Paragraph 和 Table body doc.element.body for child in body.iterchildren(): tag child.tag if tag.endswith(}p): yield Paragraph(child, doc) # 需要传父容器否则拿不到 styles elif tag.endswith(}tbl): yield Table(child, doc) domain None for block in iter_blocks(Document(机械类专业词汇表.docx)): if isinstance(block, Paragraph): name block.style.name if name.startswith(Heading) and block.text.strip(): domain block.text.strip() # 切换当前分类 else: print(当前分类 , domain, 表格行数 , len(block.rows))参数说明tag.endswith(}p)用的是命名空间后缀匹配不写死完整的{...}wordprocessingml/...URL兼容性更好。Paragraph(child, doc)和Table(child, doc)的第二个参数是父容器传错了样式和关系引用会失效。2.3 三种常见排版与对应的解析策略排版形态识别特征解析策略主要风险三列/四列表格doc.tables非空列数固定按列位映射字段合并单元格导致row.cells重复表头 段落词条无表格段落形如「齿轮 —— gear」按分隔符切分单元格文本分隔符不统一—、-、、Tab多级标题分组Heading 1/2承担分类遍历时维护current_domain标题层级跳级、编号手写混乱混合式分类下先一段说明再跟表格两种策略并联说明段丢弃说明段里混进了词条三种形态在真实文件里经常同时出现同一个 docx 前 30 页是表格后面附录变成段落。别指望一套正则打通先跑 2.1 的脚本统计列数分布再决定分支。2.4 合并单元格、软回车、上下标的三个坑第一个坑是合并单元格。Word 的合并是gridSpan/vMergepython-docx 的row.cells会把被合并的区域重复映射到同一个_Cell看起来像空列。def dedup_cells(row): 合并单元格会让同一个 tc 元素出现多次按 XML 对象去重 seen, out set(), [] for c in row.cells: key id(c._tc) # _tc 是底层 lxml 元素身份唯一 if key in seen: continue seen.add(key) out.append(c.text.strip()) return out第二个坑是软回车。单元格里按 ShiftEnter 会产生w:brcell.text里保留成\n按 Enter 则会新建一个w:pcell.text拼接时反倒把两段粘在一起。用cell.paragraphs逐段处理才稳。第三个坑是上下标。m³有的作者写成m3有的写成m^3还有的用真正的上标 run。run.font.superscript能判出来处理方式是在清洗阶段统一降级成普通数字保留在symbol字段里不做语义还原。3. 机械术语抽取与清洗中英词对、缩写和分类字段怎么落3.1 字段模型先定死再写代码字段设计决定了后面能不能查得动。机械词汇表建议至少落这些列字段类型说明term_zhTEXT中文术语如「双列圆柱滚子轴承」term_enTEXT英文术语保留原文大小写abbrTEXT缩写如 GB/T、Ra、H7domainTEXT分类来自文档标题层级definitionTEXT释义可能为空symbolTEXT符号串如H7/g6、Ra1.6aliasesTEXT同义译法逗号分隔source_refTEXT来源位置如table3:row17abbr单独拎出来是有原因的很多文件的英文列里塞的是「cylindrical roller bearing (CRB)」缩写在括号里也有的文件缩写直接在第三列。混在一起会污染英文检索。3.2 判定哪一列是中文、哪一列是英文不同版本的文件列顺序会变写死列号迟早出事。用汉字占比判定import re CJK re.compile(r[\u4e00-\u9fff]) LATIN re.compile(r[A-Za-z]) def cjk_ratio(s: str) - float: s s.strip() if not s: return 0.0 return len(CJK.findall(s)) / len(s) def guess_columns(rows, sample20): 返回 {列号: zh|en|other} cols len(rows[0]) scores [] for c in range(cols): vals [r[c] for r in rows[1:sample 1] if c len(r)] scores.append(sum(cjk_ratio(v) for v in vals) / max(len(vals), 1)) return {c: (zh if s 0.5 else en if s 0.1 else other) for c, s in enumerate(scores)}逻辑说明阈值 0.5 和 0.1 不是铁律工程上我一般扫 20 行取众数再定阈值。other那一列通常是备注、单位或图号直接落到definition或symbol。中英混排在同一格的齿轮 gear会被判成other交给 3.4 处理。3.3 normalize 清洗管道机械术语的脏字符集中在不换行空格、全角空格、上标数字、全角括号、兼容字符㎡。统一走一遍import re import unicodedata _WS re.compile(r[ \t\u00a0\u3000]) _SUP str.maketrans(⁰¹²³⁴⁵⁶⁷⁸⁹, 0123456789) def normalize(text: str) - str: if not text: return t text.replace(\u00a0, ).replace(\u3000, ) t t.replace(\r, \n) t _WS.sub( , t).strip() # 折叠连续空白 t t.translate(_SUP) # 真上标数字先降级 t unicodedata.normalize(NFKC, t) # 全角字母数字括号、㎡ 统一 return t参数说明NFKC是兼容分解会把㎡变成m2、①变成1、全角变成A。如果文件里的圈号编号本身有意义比如图号①要在调用normalize之前先把它抽走否则编号信息永久丢失。\u00a0从 Word 粘贴出来的文本里极常见不处理会导致「轴承」和「轴承 」匹配不上。清洗规则汇总规则输入输出空白折叠齿轮 传动齿轮 传动上标降级m³/minm3/min兼容分解公差H7公差(H7)去尾部编号齿轮 12齿轮 12十位以上编号需人工确认3.4 单单元格中英混排的拆分段落型词汇表里一格同时装中文和英文。用「最后一个中文字符」做切点比按分隔符切更抗噪CJK_CH re.compile(r[\u4e00-\u9fff]) def split_zh_en(text: str): 「双列圆柱滚子轴承 double-row cylindrical roller bearing」- (zh, en) t normalize(text) idx -1 for i, ch in enumerate(t): if CJK_CH.match(ch): idx i if idx -1: return None, t # 纯英文条目 zh t[:idx 1].strip( -—–:|) en t[idx 1:].strip( -—–:|) return zh, (en or None)逻辑说明这样切能扛住中文里夹英文单词的情况如「O 形密封圈」也不会被英文里的连字符ball-bearing打断。代价是英文列里如果出现中文注释会被误切实际文件里概率很低切完做一次「英文列不含 CJK」的校验兜底即可。3.5 去重与冲突裁决同一份文件里「齿轮」出现三次、英文分别是 gear、gear wheel、gear 的情况非常普遍。from collections import defaultdict def merge_entries(rows): 按 (中文, 英文小写) 归并收集别名和来源 bucket defaultdict(list) for r in rows: key (r[term_zh], (r[term_en] or ).lower()) bucket[key].append(r) merged [] for (zh, en_lower), group in bucket.items(): base dict(group[0]) base[aliases] ,.join(sorted( {g[term_en] for g in group if g[term_en]} - {base[term_en]})) base[source_ref] ,.join(sorted({g[source_ref] for g in group})) merged.append(base) return merged逻辑说明归并键用英文小写展示时保留原始大小写因为Bearing和bearing在检索上是同一个词。aliases保留同义译法source_ref记下所有出处后面做 diff 时能定位到是哪一页改了。4. 机械类专业词汇表落库SQLite FTS5 检索与拼音首字母4.1 关系表与唯一索引CREATE TABLE term ( id INTEGER PRIMARY KEY, term_zh TEXT NOT NULL, term_en TEXT, abbr TEXT, domain TEXT, definition TEXT, symbol TEXT, aliases TEXT, py_initials TEXT, -- 拼音首字母如 cl source_ref TEXT ); -- 唯一索引给 ON CONFLICT 用也顺手挡住 3.5 没归并干净的重复 CREATE UNIQUE INDEX ux_term ON term(term_zh, IFNULL(term_en, )); CREATE INDEX idx_term_domain ON term(domain); CREATE INDEX idx_term_py ON term(py_initials);参数说明IFNULL(term_en, )是必要的SQLite 的唯一索引里 NULL 之间互不相等纯缩写条目会绕过去重。py_initials单独建索引因为前缀查询走LIKE cl%时有索引和没索引差一个数量级。4.2 建 FTS5 虚拟表unicode61 对中文基本没用SQLite 的 FTS5 默认分词器unicode61把 CJK 汉字当作字母类字符一整串「双列圆柱滚子轴承」会变成一个 token。用MATCH 轴承永远匹配不到因为它不是一个完整 token。-- 方式一trigram 分词器SQLite 3.34 之后内置支持中文子串 CREATE VIRTUAL TABLE term_fts USING fts5( term_zh, term_en, abbr, definition, contentterm, content_rowidid, tokenizetrigram ); -- 方式二自己造 bigram 列兼容老版本 SQLite -- 入库前把「轴承」写成「轴承 承轴」之外的形式不划算 -- 更常见的是拆成单字空格串「轴 承」由 unicode61 分词分词方案中文子串两字词版本要求unicode61默认不支持不支持全版本trigram支持需 ≥3 字符两字词查不到3.34手工 bigram 列支持支持全版本实际项目里我一般两条腿走trigram 表负责长词检索py_initials和LIKE %xx%负责两字词和单字兜底。两字词查询量在大文件里很可观「轴承」「齿轮」「键槽」别指望 trigram 全包。4.3 拼音首字母字段与多音字词表中文技术词典的使用习惯是打首字母cl出「齿轮」zc出「轴承」。from pypinyin import lazy_pinyin, Style, load_phrases_dict # 机械术语里的多音字必须显式登记否则首字母会错 load_phrases_dict({ 曲轴: [[qū], [zhóu]], # 不登记会读成 qǔ 重载: [[zhòng], [zài]], 公差: [[gōng], [chā]], 淬火: [[cuì], [huǒ]], }) def initials(text: str) - str: 双列圆柱滚子轴承 - slyzgzzc return .join(lazy_pinyin(text, styleStyle.FIRST_LETTER, errorsdefault))参数说明Style.FIRST_LETTER只取每字拼音首字母errorsdefault让非汉字字符原样保留这样「O 形密封圈」会得到oxmfq不会报错。多音字在机械领域是高频陷阱——「差」在公差里读 chā在「差别」里读 chā在「出差」里读 chāi只有登记词表才稳。词表可以外置成 JSON几百条就够覆盖常见术语。4.4 四条查询入口与性能对照-- 入口一中文子串走 trigram FTS SELECT t.term_zh, t.term_en, t.domain FROM term_fts f JOIN term t ON t.id f.rowid WHERE term_fts MATCH 滚子 LIMIT 20; -- 入口二英文精确大小写不敏感 SELECT term_zh, term_en FROM term WHERE lower(term_en) lower(ball bearing); -- 入口三拼音首字母前缀走 idx_term_py SELECT term_zh, term_en FROM term WHERE py_initials LIKE zc% LIMIT 20; -- 入口四分类 英文模糊 SELECT term_zh, term_en, definition FROM term WHERE domain 齿轮传动 AND term_en LIKE %helical%;入口触发场景走的索引注意中文子串输入「滚子」term_ftstrigram两字词退回LIKE英文精确复制英文原文无小表尚可数据量大时加term_en_lower列拼音首字母输入zcidx_term_py结果多时按domain分组分类模糊指定章节找词idx_term_domainLIKE前置通配符不走索引4.5 批量写入与 FTS 同步外部内容表contentterm不会自动跟着主表变必须手动同步。import sqlite3 conn sqlite3.connect(mech_term.db) conn.execute(PRAGMA journal_modeWAL) # 允许边写边查 def upsert(rows): conn.executemany( INSERT INTO term(term_zh, term_en, abbr, domain, definition, symbol, aliases, py_initials, source_ref) VALUES(:term_zh, :term_en, :abbr, :domain, :definition, :symbol, :aliases, :py_initials, :source_ref) ON CONFLICT(term_zh, IFNULL(term_en, )) DO UPDATE SET definition excluded.definition, domain excluded.domain, aliases excluded.aliases , rows) conn.commit() def rebuild_fts(): # 全量重建几十万条以内秒级增量则用 delete 再插入 conn.execute(INSERT INTO term_fts(term_fts) VALUES(rebuild)) conn.commit() upsert(merged_rows) rebuild_fts()参数说明ON CONFLICT的目标必须和ux_term完全一致写成ON CONFLICT(term_zh)会报「没有匹配的唯一索引」。rebuild是全量词汇表更新频率低月更或季更全量最简单可靠如果要做增量先INSERT INTO term_fts(term_fts, rowid, ...) VALUES(delete, ...)再插新行顺序错了索引会残留脏数据。5. 从库反写机械类专业词汇表.docx 并做一致性校验5.1 生成规范表格与中英混排字体反写最容易翻车的地方是字体。run.font.name只写w:ascii和w:hAnsi中文走w:eastAsia不单独指定就会跟随主题字体换台机器打开样式就变了。from docx import Document from docx.shared import Pt, Cm from docx.oxml.ns import qn doc Document() table doc.add_table(rows1, cols4) table.style Table Grid # 不设就没有边框打印稿不可用 headers [中文术语, 英文术语, 缩写, 所属分类] for i, name in enumerate(headers): table.rows[0].cells[i].text name def style_run(run, ascii_fontTimes New Roman, ea_font宋体, size10.5): run.font.name ascii_font run.font.size Pt(size) # 中文必须显式设置 w:eastAsia否则跟随主题 run._element.rPr.rFonts.set(qn(w:eastAsia), ea_font) widths [Cm(4.0), Cm(6.5), Cm(2.5), Cm(3.0)] for row_data in merged_rows: cells table.add_row().cells for cell, value in zip(cells, [row_data[term_zh], row_data[term_en], row_data[abbr], row_data[domain]]): run cell.paragraphs[0].add_run(value or ) style_run(run) # 列宽必须逐格设 tcW只设 table.columns[i].width 部分版本不生效 for cell, w in zip(cells, widths): cell.width w逻辑说明row.cells[i].width最终写的是w:tcWWord 排版时以它为准table.columns[i].width在某些 Word 版本会被自动布局覆盖两个都设最保险。5.2 导出后的四项一致性校验校验项规则失败处理空字段term_zh为空且abbr也为空打标待人工确认中英串列英文列含 CJK 字符回炉走 3.4 重切缩写未拆出term_en abbr拆到abbr列重复未归并唯一索引冲突合并aliasesCJK re.compile(r[\u4e00-\u9fff]) def validate(rows): problems [] for r in rows: if not r[term_zh] and not r[abbr]: problems.append((空字段, r[source_ref])) if r[term_en] and CJK.search(r[term_en]): problems.append((中英串列, r[term_zh], r[term_en])) if r[term_en] and r[term_en] r[abbr]: problems.append((缩写未拆出, r[term_zh])) return problems5.3 一个具体技巧用术语级 diff 守住版本变化Word 每次保存都会重排 XML整份 docx 做二进制 diff 只会得到「全变了」。把抽取结果转成中文|英文的集合再比对才能看出真正新增、删除、改译了哪些词。import difflib def diff_terms(old_rows, new_rows): o {f{r[term_zh]}|{r[term_en]} for r in old_rows} n {f{r[term_zh]}|{r[term_en]} for r in new_rows} for line in difflib.unified_diff(sorted(o), sorted(n), lineterm, n0): print(line) # 新增- 删除可接 CI 做阻断把这一步接到 CI 上每次词汇表更新都能产出一份人可读的变更清单再配合 5.2 的校验改译漏字段、缩写塞错列这类问题在合并前就能拦住。术语表的维护成本从来不在第一次抽取而在第一百次更新时还认得出哪几个词被动过。本文还有配套的精品资源点击获取
返回列表