Python解析Word题库:从docx提取化工仪表题目并生成Anki刷题包
简介面向化工自动化控制仪表特种作业取证与复审人群这份DOCX复习资料把分散的作业理论考点整理为成套选择题覆盖阀门特性、安全仪表系统故障模式、电磁阀密封、DCS与冗余容错、现场总线、涡流传感器、电磁流量计及CENTUM CS3000系统状态判断等。资源共1个docx文件压缩包约134KB内容以单选、判断形式呈现方便按题号逐条记忆和自测。文档正文保留原版题干与选项并附简要知识说明适合备考刷题后查看解析从快开/等百分比/线性特性到三级安全教育、1211灭火器适用范围、BCD码、B型热电偶冷端补偿等易混点均有涉及。已有154人学习浏览。对需要快速抓重点的考生而言可用作特种作业理论机考的考前速查与错题复盘材料初学或复审阶段都能据此定位薄弱环节。1. 拿到化工自动化控制仪表作业 docx先把它当压缩包而不是文档拿到一份《化工自动化控制仪表作业(原版).docx》很多人会直接双击打开然后对着考题一页页翻。但在企业里这份文档往往不只是给人看的它需要导入培训系统、抽题组卷或者拆成刷题小程序。如果手动复制粘贴几百道题和表格样式会在第一天就让人崩溃。更麻烦的是这种“原版”文档通常没有统一版式题目混在段落和表格里选项换行错位答案带颜色或加粗。你真正需要的不是打开它而是把 docx 当作一个 zip 压缩包用 Python 按结构抽取内容再清洗成可用的 JSON。下面从一个 IT 运维的角度完整做一遍先理解 docx 的 XML 结构再读取段落和表格提取化工自动化控制仪表作业的题目与答案最后生成能直接导入复习工具的数据。适合要处理同类 word 资料但没接触过文档解析的人。2. 化工自动化控制仪表作业 docx 的文档结构解析与选型处理这份文档之前先确认它的真实格式。docx 不是普通文本文件而是 zip 压缩包。用压缩软件打开能看到内部有word/document.xml、word/media/和_rels目录。主体文本写在document.xml里图片和字体放在media和embeddedObjects下。这意味着只要服务器能解压 zip我们就能在不安装 Office 的情况下解析内容。zip 内路径作用word/document.xml文档主体文本所有可见内容word/media/图片、视频等媒体文件word/settings.xml文档级设置如编辑限制word/styles.xml样式定义影响文本外观用unzip -p可以快速查看主体unzip -p 化工自动化控制仪表作业(原版).docx word/document.xml | head -c 800命令从 zip 包中直接读取word/document.xml并输出前 800 字节-p参数把文件内容打印到标准输出不落盘。看到一堆w:t标签时说明文档有文本层如果全部是w:drawing或空文本就要考虑是扫描件。这一个简单的判断决定了后面走文本解析还是 OCR。为什么不直接基于 XML 做正则匹配因为 Word 会把一段文本拆成许多w:r和w:t标签尤其带拼写检查、修订记录或复杂样式时。同一个段落可能被多个富文本标记拆开正则很难维护。用python-docx封装好的段落、表格、样式模型是处理这类文档的常见起点。提示如果还没有安装 python-docx先执行pip install python-docx。选型上我一般用python-docx处理段落和表格遇到它没暴露的属性再用lxml直接看 XML。它默认只读不会改变原文档适合分析和抽取。解析“原版”docx 时文档中可能残留修订记录和批注python-docx的.text不会包含批注但修订过的删除内容也不会出现在 text 中这反而是好事我们只取最终可见文本。2.1 用 python-docx 读取段落最基本的读取方式from docx import Document doc Document(化工自动化控制仪表作业(原版).docx) print(段落数:, len(doc.paragraphs)) for p in doc.paragraphs[:5]: if p.text.strip(): print(段落:, p.text.strip()[:50])doc.paragraphs只返回文档顶层段落不包括表格单元格里的段落。打印时用strip()去掉首尾空白再截断前 50 个字符避免空段落把输出刷屏。len(doc.paragraphs)可以快速判断文档的主要文字是不是以段落形式存在。如果段落数很少但文档看起来有内容文字大概率躺在表格里需要继续读表。2.2 用 python-docx 读取表格并还原顺序题目常以表格形式组织特别是选项对齐时for table in doc.tables: for row in table.rows: cells [cell.text.strip().replace(\n, ) for cell in row.cells] print( | .join(cells))这段代码遍历所有表格的每一行把单元格内容用管道符分隔打印。replace(\n, )把单元格内换行改成空格避免输出多行干扰判断。但这里有两个坑需要注意。第一个是合并单元格row.cells会重复返回同一个单元格对象横向合并后后面的 cell 与前面的 cell 是同一个直接输出会出现重复内容。第二个是顺序问题doc.paragraphs和doc.tables是各自独立的集合Word 文档里段落和表格交替出现时这两个集合无法还原真实顺序。化工仪表题经常是题目用段落选项用表格或者反过来。常见做法是遍历 body 子元素让段落和表格按文档顺序混合输出from docx.document import Document as _Document from docx.table import Table from docx.text.paragraph import Paragraph def iter_block_items(doc): parent doc.element.body for child in parent.iterchildren(): if child.tag.endswith(}p): yield Paragraph(child, doc) elif child.tag.endswith(}tbl): yield Table(child, doc) for block in iter_block_items(doc): if isinstance(block, Paragraph): print(段落:, block.text.strip()) else: print(表格:, len(block.rows), 行)iterchildren()按 XML 顺序遍历 body 下的直接子元素标签以}p结尾的是段落以}tbl结尾的是表格。Paragraph(child, doc)和Table(child, doc)构造出 python-docx 的包装对象因此仍能使用.text、.rows这些 API。有了这个顺序信息后面的结构化抽取才可靠。3. 从化工自动化控制仪表作业题库 docx 中抽取结构化题目拿到 body 顺序后真正的难点是识别题目和答案。不同来源的“原版” docx 格式差异很大但题型基本固定单选、多选、判断和少量填空。我们可以用一组正则把它们拆开再按题型归并。3.1 用正则识别题号、选项和答案先定义模式表元素常见写法正则可参考题号1、 1. [1]^\s*(\d)[.、]选项A、 A. A) A]^\s*([A-H])\s*[.、)\]]答案答案B 【答案】B 参考答案:B(?:参考答案?判断题的答案通常是“√”或“×”所以字符集里同时包含字母和符号。下面是一段按行解析的示例代码把文本切成记录import re pat_num re.compile(r^\s*(\d)\s*[.、]) pat_opt re.compile(r^\s*([A-H])\s*[.、)\]]) pat_ans re.compile(r(?:参考答案?|正确答案?|答案)\s*[:]\s*([A-H√×])) records [] current_q None current_opts [] current_ans def flush(): if current_q is None: return records.append({ question: current_q, options: current_opts, answer: current_ans }) with open(extracted_text.txt, encodingutf-8) as f: for raw in f: line raw.strip() if not line: continue m_num pat_num.match(line) if m_num: flush() current_q line current_opts [] current_ans continue m_opt pat_opt.match(line) if m_opt and len(line) 80: current_opts.append(line) continue m_ans pat_ans.search(line) if m_ans: current_ans m_ans.group(1) continue if current_q is not None: current_q line flush() print(解析到, len(records), 条记录)这段代码的前提是已经用 python-docx 把文档逐段打印并重定向到extracted_text.txt。pat_num匹配行首题号pat_opt匹配行首大写字母选项len(line)80是为了避免把题干中的“MPa”“A类”当选项这个长度阈值可以根据实际排版调整pat_ans提取答案字母或符号。如果一行既不是题号、选项也不是答案就拼到当前题目文字后因为 Word 里的题干经常被软换行拆开。3.2 表格型题目的合并与顺序还原表格里最常见的错位是单元格合并。比如第一行是题目第二行四个选项分布在多个单元格第三行是答案。用row.cells读到的 content 可能重复需要相邻去重def unique_cells(row_cells): result [] for cell in row_cells: text cell.text.strip().replace(\n, ) if text and (not result or result[-1] ! text): result.append(text) return result判断条件result[-1] ! text用来处理合并单元格row.cells会连续返回同一个对象文本相同且相邻去掉重复。如果两个选项内容恰好相同且相邻用这个逻辑会丢一个但题库选项几乎不会出现相邻重复所以可接受。然后配合iter_block_items遇到表格时把每行按unique_cells转成列表多列行按选项模式加入current_opts单列行按题号或答案处理。3.3 清洗文本中的空白、软换行和格式占位符原版 docx 文本中经常混入全角空格、软换行符\v、下划线占位符。题目里的填空“____”抽取后仍然是下划线需要保留但不要和普通空格混在一起。一个常见的清洗函数import re def clean_text(text): text text.replace(\u000b, ).replace(\r\n, \n) text text.replace(\u3000, ) text re.sub(r {2,}, , text) text re.sub(r_[\u4e00-\u9fff]*_, , text) # 保留一个全角下划线 return text.strip()\u000b是 Word 的软换行符常见于 shiftenter 产生的段内换行保留它会导致 JSON 里出现看不见的控制字符所以替换成普通空格。全角空格\u3000在中文输入法里很常见换成半角空白后方便后续正则继续匹配。连续下划线如果夹在中文之间通常是填空占位会被替换成全角下划线作为填空标记。具体替换规则要根据实际题面调整不要一刀切删除下划线。3.4 处理答案汇总在文末的情况有些原版 docx 的题目附近没有答案而是把答案统一列在文档末尾以表格或段落形式出现。此时先用前面的方法解析“题目选项”再单独读取最后一个表格得到{题号: 答案}映射最后回填。answer_map {} for table in doc.tables: for row in table.rows: cells unique_cells(row.cells) if len(cells) 2 and pat_num.match(cells[0]): q_no int(pat_num.match(cells[0]).group(1)) answer_map[q_no] cells[1].strip() for r in records: m pat_num.match(r[question]) if m and not r[answer] and int(m.group(1)) in answer_map: r[answer] answer_map[int(m.group(1))]unique_cells去重后如果一行有两个及以上单元格第一列是题号第二列就是答案。回填时通过pat_num从题目文本中取出题号再检查答案是否为空避免覆盖题目后跟的答案。最后把“√”“正确”等文本统一成规范符号方便导入刷题工具。4. 遇到无法预览、加密和 wps 兼容的 docx 边界场景题库类文档经常从同事或培训系统导出带锁、带只读、格式老旧解析前需要先清理。先验证文件本身是不是有效 zipimport zipfile try: with zipfile.ZipFile(化工自动化控制仪表作业(原版).docx) as z: bad z.testzip() if bad is not None: print(文件损坏第一个异常条目是, bad) except zipfile.BadZipFile: print(不是有效的 zip/docx 文件)testzip()会逐个测试压缩包内文件的 CRC 校验值返回第一个坏文件的文件名。如果抛BadZipFile说明文件头不对可能是旧版.doc改了后缀或者传输中断。这种情况后再谈解析没有意义需要先让提供方重新导出。4.1 docx 里只有图片时先解出 media 目录如果 python-docx 读出的所有段落都是空字符串而文档实际能看到字那很可能是扫描件。这种“原版” docx 的本质是图片外带一个透明文本层。先把word/media/下的所有图片捞出来import zipfile from pathlib import Path with zipfile.ZipFile(化工自动化控制仪表作业(原版).docx) as z: for name in z.namelist(): if name.startswith(word/media/): target Path(media) / Path(name).name target.write_bytes(z.read(name)) print(解出, target)startswith(word/media/)判断文件是否在 media 目录下。z.read(name)返回原始字节流不管里面是 PNG、EMF 还是内嵌的视频资源都会按原名输出。文件没有扩展名时解出后用file命令确认类型。得到图片后要还原文字需要 OCR可以走 tesseract 或者云服务但至少图片能完整备份。4.2 打开加密的文档时先解密再解析带打开密码的 docx 不能直接被 python-docx 读取需要用msoffcrypto-tool解密。常见做法pip install msoffcrypto-tool msoffcrypto-tool 化工自动化控制仪表作业.docx plain.docx -p 密码-p指定密码处理后的plain.docx可以继续用 python-docx 打开。如果不知道密码只能看文档是否有编辑限制而不是文件加密。编辑限制的表现是打开后能看但工具栏里“编辑”是灰的。这类限制写在word/settings.xml的w:documentProtection节点中处理方式是复制一份并修改 XMLimport zipfile with zipfile.ZipFile(化工自动化控制仪表作业(原版).docx) as zin: with zipfile.ZipFile(unlocked.docx, w) as zout: for item in zin.namelist(): data zin.read(item) if item word/settings.xml: text data.decode(utf-8) text text.replace(w:documentProtection, w:documentProtection w:enforcement\0\) data text.encode(utf-8) zout.writestr(item, data)这段代码把settings.xml中documentProtection的enforcement属性改成 0让编辑限制失效。不同的 Word 版本命名空间前缀可能不是w:所以要先 unzip 查看实际 XML。如果原节点本身没有enforcement属性需要手动加上w:enforcement0。4.3 浏览器或 wps 无法预览 docx 时转为 PDF浏览器无法预览 docx 是常见现象因为 docx 不是浏览器原生支持的格式。wps 不能默认新建 docx 则是文件关联问题通常是安装其他办公软件时把.docx的默认打开方式抢走了。在服务器上用 LibreOffice 转 PDF 是最直接的方案soffice --headless --convert-to pdf 化工自动化控制仪表作业(原版).docx --outdir ./preview--headless表示无界面运行--convert-to pdf指定输出格式--outdir设置输出目录。这个命令在 Linux 和 Windows 上都能用前提是装 LibreOffice。转换后的 PDF 可以直接在浏览器预览也方便后续 OCR 和按页抽取图片。如果连 LibreOffice 都不方便装可以用 python-docx 抽出的纯文本渲染成简单 HTML 页面样式有损失但内容可读。适合知识库预览不适合保存原版样式。下面是边界场景速查表症状可能原因处理路径打开提示输入密码文件加密msoffcrypto-tool 解密能看但不能编辑编辑限制修改 settings.xml 中 documentProtection浏览器预览白屏缺少插件或服务LibreOffice 转 PDFWPS 默认不新建 docx文件关联被改办公软件设置里恢复关联读出来的文本全是空扫描件解出 media 图片并 OCR5. 把化工自动化控制仪表作业数据生成 Anki 刷题包结构化记录不能只留在内存里。我们可以把它变成 Anki 能导入的 CSV做一次抽样验证最后用哈希去重。5.1 导出 CSV 作为 Anki 导入源Anki 的 CSV 导入要求每一行是一张卡片字段之间用逗号分隔文本内部支持 HTML。这里把题目放一个字段选项拼成多行答案放在最后import csv def export_csv(records, pathquestions.csv): with open(path, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([题目, 选项, 答案]) for r in records: question r[question].replace(\n, br) options br.join(r[options]) answer r[answer] writer.writerow([question, options, answer])utf-8-sig会写入 BOM方便 Excel 直接打开。把段落换行替换成brAnki 导入时才能保留换行效果。选项列表也用br连接否则所有选项会挤在一行。如果你的题包含图片引用需要把图片放到 Anki 的 collection.media 目录这里不展开。5.2 用抽样统计校验字段完整率导出之前先做一个快速质检。随机抽少量记录打印题目和答案人工看是否有明显错位import random sample random.sample(records, min(20, len(records))) for item in sample: print([题目], item[question][:40]) print([答案], item[answer]) print(---)更可量化的是统计题目和答案的填充率total len(records) q_ok sum(1 for r in records if r[question].strip()) a_ok sum(1 for r in records if r[answer].strip()) print(f题目填充率: {q_ok / total:.1%}) print(f答案填充率: {a_ok / total:.1%})这两个指标能识别出解析器在哪个环节失效。例如答案填充率低于 90%说明pat_ans或答案映射没有覆盖实际格式。此时去查看未被命中的答案原文比逐题人工对要快。5.3 用 SHA1 指纹去掉重复题目原版题库常常把同一道题在不同章节重复出现形式上可能多了一个空格或者换行。清洗以后仍会有细微差异。我一般用清洗后的题目文本做 SHA1 指纹去重import hashlib def dedupe(records): seen set() unique [] for r in records: key clean_text(r[question]).encode(utf-8) digest hashlib.sha1(key).hexdigest() if digest not in seen: seen.add(digest) unique.append(r) return uniqueclean_text就是第 3.3 节里的清洗函数先统一全角空格和软换行再取哈希。这样题目只要实质相同即使选项顺序不同也会被当作重复。如果你希望保留选项顺序差异可以只对题干做指纹选项不参与哈希。去重后的记录数变化能反馈源文档到底有多少重复内容。这个基于哈希的去重思路同样适用于其他 word 资料不只 docx。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →