恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

老式.doc批量转结构化数据:从批量转换到SQLite全文检索

  • 首页
  • 资讯中心
  • /
  • 老式.doc批量转结构化数据:从批量转换到SQLite全文检索

相关资讯

MinGW-w64实战指南:从安装配置到编译调试与避坑 2026/10/15 15:36:21
STM32舵机控制机械臂实战:从PWM配置到逆运动学避坑指南 2026/10/15 15:36:21
MADDPG多智能体强化学习实战:PaddlePaddle与PARL实现及调参指南 2026/10/15 15:36:21

最新资讯

C语言银行系统实战:消息队列实现进程间通信与并发控制
智能车摄像头图像处理:五种阈值算法选型与代码实现
把区域建模成服务半径参数而不是计价参数
AI辅助创作占比怎么算、信息增量怎么看:以媒介果发稿流程为参照的审核自查指南
nRF52832 GCC编译环境搭建:工具链选型、Makefile调优与CI集成
爆火的 OpenClaw 到底怎么用?完整安装与使用教程(TaoToken 统一 Key 接入版)

今日推荐

AI视频生成的时间一致性难题与时空建模演进
基于bbox的PDF文档解析:多栏重排与水印过滤实战
探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

老式.doc批量转结构化数据:从批量转换到SQLite全文检索

发布时间:2026/10/15 15:41:22
老式.doc批量转结构化数据:从批量转换到SQLite全文检索 简介这是绵阳师范学院《C语言程序设计》课程期末考试B卷的参考答案与评分标准文档面向该课程学生和任课教师尤其适合期末备考阶段用来核对知识点掌握情况。文档对应2018—2019学年第二学期期末考试覆盖单选题、填空题、判断题、程序运行结果和程序设计五类题型答案完整且附有评分标准。资源为1个doc文件整体大小29KBWord格式便于直接查看或打印。已有61人学习浏览适合作为期末复习的自测与订正材料。通过文档中的程序题代码示例读者可以对照标准写法检查自己的解题思路尤其对补码表示、合法标识符、浮点类型、取地址与按位与运算等易混淆概念能得到明确辨析有助于快速查漏补缺。整份文件结构紧凑从客观题到编程题均有对应得分与参考答案适合考前集中复盘。1. 「B答案.doc」这个文件名背后藏着一整条批处理链路某次文档系统迁移我接到一个听起来很简单的需求把项目里那两百多份题为“B答案.doc”的老文档读出来整理成结构化数据。打开第一份文件时我就知道这事没那么简单——用文本编辑器看到的是大段乱码双击用办公软件打开又正常原因是这类 .doc 是二进制格式正文、样式、表格和图片全部混在一个文件里。标题里的 .doc 不是笔误它恰恰是整条处理链路的起点转换、抽取、清洗、校验、入库。这篇笔记写给要处理老式 Word 文档的开发者尤其是想把答案、题库、历史资料批量转成结构化数据的人。我会把每一步命令和踩过的坑都写清楚照着做基本能跑通。2. 先把 .doc 读出来转换方案选型与最小可跑命令2.1 为什么不能双击另存为doc 的二进制结构与三种抽取思路老式 .doc 不是纯文本文件它基于 OLE 复合文档结构正文、字符映射表、样式定义、修订记录全部打包在几个二进制流里。正文本身又可能用不同的编码保存所以拿文本编辑器打开看到乱码是正常现象不代表文件损坏。有人会说那我双击打开另存为 .docx 不就行了。单个文件可以两百多份的时候你会崩溃。办公软件在转换时会逐个弹“格式兼容性”对话框这种弹窗没法脚本化而且人工操作容易误点“取消”。另一个容易被忽视的问题是另存为会改变文档内部结构一些原本独立存在的内容会被重新组织后面程序化抽取表格时就多了一堆隐藏变量。针对老 .doc 转文本常见做法有三条路一是用 soffice 的命令行模式做无界面转换把 .doc 转成 .docx再用代码解析二是用反编译器类的老牌命令行工具直接抽文本三是丢给在线转换网站。在线转换我基本不考虑答案文档往往涉及内部数据传出去不放心。我最终选的是第一条路因为答案文档里经常夹着表格和公式只要转成 .docx就能用 python-docx 同时拿到段落和表格两个层次后续结构化处理空间大得多。方案支持老 .doc中文兼容表格支持批量能力依赖soffice 转 docx支持较好需字体环境保留强安装办公套件antiword 抽文本支持需指定 UTF-8 映射基本丢弃强单一命令在线转换网站支持视站点而定不稳定弱无但数据外流选型还要看文档的实际情况。如果这批答案文档全是纯文本段落题干和答案一行挨一行那 antiword 一条命令就够了根本不用绕一圈转 docx。如果里面有大量表格那就老老实实走转换路线。我的习惯是先用轻量方法抽一两份试试再决定整批走哪条路。2.2 用 soffice 无界面批量转出 docx 的命令转换这一步没什么技术含量但细节决定成败。先建一个独立输出目录避免转换结果和源文件混在一起。最关键的一点是--headless参数必须带上否则会拉起图形界面弹窗脚本直接挂起在那里等人工操作批量任务当场变手工活。mkdir -p converted for f in ./*.doc; do soffice --headless --convert-to docx --outdir converted $f || echo [FAIL] $f done逻辑很简单遍历当前目录下所有 .doc逐个交给 soffice 转换输出到 converted 目录。--convert-to docx指定目标格式--outdir指定输出目录刚才踩过的坑是转换失败时 soffice 不会报错退出它会静默返回成功所以后面用|| echo [FAIL] $f把失败文件打印出来。for f in ./*.doc这行需要注意如果目录里一个 .doc 都没有通配符会原样传给命令导致 soffice 尝试打开一个名叫*.doc的文件。第一行mkdir -p里的-p参数代表目录已存在时不报错重复执行脚本也不会翻车。跑完以后检查一件事转换出的 .docx 文件名和源 .doc 同名只是后缀变了。如果文件路径里有空格变量$f必须用双引号包起来否则路径会被拆成两段。另外soffice 第一次运行会初始化用户配置速度稍慢属于正常现象不要误判为卡死。2.3 老文档直接用 antiword 抽文本一条命令拿正文当确认文档内容以纯文本为主时直接用 antiword 更快。这个工具专门解析老 .doc不依赖办公套件安装后一条命令就能把正文抽出来。antiword -m UTF-8.txt B答案.doc B答案.txt-m UTF-8.txt是指定字符映射文件把文档内部编码映射到 UTF-8 输出。这一步是中文不乱码的关键不指定映射文件时中文大概率变成一堆问号。把标准输出重定向到文本文件注意重定向会覆盖已有文件想追加密加一份就改用。我一般会在批量之前先对每份文档跑一句话file确认它确实是老式 Word 文档而不是披着 .doc 外壳的别的格式。antiword 的边界要心里有数它只认老 .doc不认 .docx表格内容基本丢弃文本框和页眉页脚也读不出来。拿它做初筛或纯文本答案的抽取足够一旦发现文档里有表格就回到 2.2 的转换路线。3. 把答案文本变成结构化数据从 docx 正文抽出一个干净的题库 JSON3.1 用 python-docx 抽取段落和表格先拿全量内容docx 本质上是一个 zip 压缩包里面装着若干 XML 文件python-docx 把这个细节封装掉了直接操作Document对象就能拿到段落和表格。动手写正则之前先把两份文档的内容全量打印出来看一眼确定题号和答案的排版规律这一步能省掉后面大量返工。from docx import Document doc Document(converted/B答案.docx) paragraphs [p.text.strip() for p in doc.paragraphs if p.text.strip()] tables [] for table in doc.tables: rows [] for row in table.rows: rows.append([cell.text.strip() for cell in row.cells]) tables.append(rows) print(f段落数{len(paragraphs)}表格数{len(tables)})逻辑是先把所有非空段落收集起来再把所有表格按行读取统一存成二维列表。p.text.strip()去掉段落首尾空白同时过滤掉空段落cell.text.strip()同理目的是后续正则匹配时不至于被多余空格干扰。这里有个细节python-docx 只能处理 .docx如果你跳过第 2 章直接把 .doc 喂给它会直接抛异常。段落和表格分开抽是因为很多答案表是“题目占一格、答案占一格”如果混在 paragraph 流里语义会被打碎后面组装 JSON 时很难对齐。3.2 用正则把「题号 题干 答案」切成分组记录纯文本型的答案文档常见排版有两种一种是1、题干内容答案回答内容全部挤在一个段落里另一种是题干一行下一行以答案开头。针对这两种格式正则的写法略有差别但核心思路一致行首数字做锚点找答案关键词做切分点。import re text \n.join(paragraphs) pattern re.compile( r^\s*(\d{1,3})\s*[\.、]\s*(.?)\s*(?:答案|参考答案)\s*[:]\s*(.)$, re.M | re.S, ) records [] for m in pattern.finditer(text): records.append({ id: int(m.group(1)), question: m.group(2).strip(), answer: m.group(3).strip(), })逐个拆开看^\s*表示从行首开始允许开头有空白(\d{1,3})捕获 1 到 3 位数字作为题号[\.、]同时兼容英文句点、中文顿号和全角句点(.?)是非贪婪匹配它会尽量少地匹配字符直到遇到后面的答案关键词(?:答案|参考答案)是非捕获分组代表在这里出现哪个词都行re.M让^匹配每一行的开头而不是整个字符串的开头re.S让.能匹配换行符这样跨行排版的题干也能被完整捕获。跑完别急着入库先打印前 20 条看看“question”和“answer”切得对不对。经常出现的情况是题干里包含“答案”字样导致提前截断这类边界要靠人工观察后给正则加条件。我的习惯是保留原始题号哪怕中间缺号也不重排这样后续可以和原文档对得上。3.3 表格型答案按字段对齐表头映射与行数据组装很多答案文档是表格形式第一行是“题目/题型/答案/解析”这类表头后面每行一条记录。处理这种结构最忌讳按列位置写死代码因为列顺序在不同文档之间很可能不一样。for idx, rows in enumerate(tables): if not rows: continue header [h.strip() for h in rows[0] if isinstance(h, str) and h.strip()] field_map {题目: question, 题干: question, 答案: answer, 解析: analysis, 题型: type} col_index {} for i, h in enumerate(header): if h in field_map: col_index[field_map[h]] i for row in rows[1:]: rec { question: row[col_index[question]] if question in col_index else , answer: row[col_index[answer]] if answer in col_index else , } records.append(rec)先读第一行做表头再把表头名映射到固定的字段名最后按映射的列索引组装每条记录。这样即使某份文档把“答案”列放在第三列另一份放在第五列代码都不用改。isinstance(h, str)是防御性写法防止某些单元格内容是对象类型导致 strip 报错。注意跨页表格在 Word 里会重复表头这段代码没有处理重复表头行留给后面的避坑章节专门说。4. 批量跑通整个目录扫描、转码、清洗与归档4.1 递归扫描整个目录把散落的 .doc 全部找出来文档不会整齐地躺在同一个文件夹里。真实情况往往是根目录下按年份建了一堆子目录每层还混着 .DOC、.doc、.docx 各种后缀。手写for f in *.doc只能处理当前目录一层递归扫描得靠rglob。先做扫描再决定后续动作。from pathlib import Path for p in sorted(Path(src_docs).rglob(*.doc)): print(p.resolve(), p.stat().st_size)Path(src_docs)是你要扫描的顶层目录rglob(*.doc)表示递归匹配所有以 .doc 结尾的文件子目录里的也会被找出来。p.resolve()把相对路径转成绝对路径方便在日志里直接定位p.stat().st_size拿文件大小做初步判断有些大小为 0 的损坏文件一眼就能看出来。注意rglob默认大小写敏感目录里如果存在.DOC后缀的文件需要再补一个rglob(*.DOC)或者直接用只匹配后缀但不区分大小写的写法。扫描列表确认无误后就可以把它和 2.2 的转换命令串起来。用 bash 的find也能达到同样效果写法上更贴近命令行习惯。find src_docs -iname *.doc -print0 | while IFS read -r -d f; do soffice --headless --convert-to docx --outdir converted $f || echo [FAIL] $f done-iname忽略大小写匹配-print0和-d 、read -r组合是为了正确处理文件名里的空格和特殊字符。转换脚本跑完之后对照扫描清单和 converted 目录的文件数就能知道哪些转失败了。4.2 乱码与控制字符清洁BOM、全角空格、残留换行从 doc 或 docx 里抽出来的文本并不干净。我遇到最多的几个问题依次是文件开头带着 BOM 导致 JSON 解析报错、文本里混入制表符和全角空格、连续多个换行把正则的行锚定搅乱。所以清洗不能省直接在写入 JSON 之前做一层过滤。import re def clean_text(raw: str) - str: text raw.replace(\ufeff, ) text re.sub(r[\x00-\x08\x0b\x0c\x0e-\x1f\x7f], , text) text text.replace(\u3000, ).replace(\t, ) text re.sub(r[ \t]{2,}, , text) text re.sub(r\n{3,}, \n\n, text) return text.strip()四个替换各干一件事\ufeff是 BOM 字符去掉它避免写入 JSON 后第一个字段名前面藏一个不可见字符控制字符的正则清掉各类不打印的符号\u3000是中文全角空格统一替换成半角空格制表符也一样最后把两个以上的连续换行压成一个空行的量。清洗函数要应用在每一段文本上位置在 3.2 的正则匹配之前否则正则里的^\s*会被全角空格干扰导致行首锚定失效。清洗和正则的顺序不能反这个经验是从一次题号全部没匹配上得来的。4.3 归档命名与去重校验给每份文档算一个指纹批量处理最后一步是对文件做归档和去重。文件名相同但内容不同的“B答案.doc”可能出现多份按文件名归档会互相覆盖而内容相同但文件名不同的情况也存在。最稳妥的办法是给每份文档算一个内容指纹。import hashlib from pathlib import Path def file_fingerprint(path: Path) - str: return hashlib.md5(path.read_bytes()).hexdigest() for p in Path(src_docs).rglob(*.doc): fp file_fingerprint(p) print(p, fp)读文件原始字节做 MD5同样的内容得到同样的指纹文件名不同不影响判断。实际归档时我一般把指纹前 8 位拼到文件名里既能保留可读性又避免同名覆盖。指纹还有一个用途迁移完成后重新扫描一遍新目录两边指纹列表做差集就能确认哪些文件没被处理到。去重校验要放在入库前不要在清洗之后才做。因为清洗后文本可能完全一致但原始文档的排版差异已经丢了再做指纹对比就失去了意义。5. B答案.doc 处理中的常见问题与避坑清单5.1 转出来的文本全部是乱码一个汉字都读不到现象soffice 转换成功了.docx 也能打开但所有汉字变成了方框或问号或者 antiword 输出的文本全是乱码。原因转换环境的系统里缺少源文档所用的中文字体导致字符映射失败antiword 则多半是没有指定 UTF-8 映射文件。我最早跑偏在后者以为工具不支持中文就绕了一大圈结果只是漏了一个参数。解决antiword 的场景命令里加上-m UTF-8.txt指定映射文件soffice 的场景确认系统装了中文字体。还有一个交叉验证方法把同一份 .doc 转成 PDF再从 PDF 里抽文本如果 PDF 正常而 docx 乱码说明问题出在转换通道本身不是源文件损坏。处理批量任务前先拿两三份样本验证编码比全部跑完再排查省时间得多。5.2 正则把答案里的「1.」误判成新题目现象最终 JSON 的记录数比实际题目数多出一截而且很多条记录的 question 字段特别短answer 字段被截断。原因答案正文里出现了以数字加句点开头的换行比如答案里列举步骤时写了“1. 先执行……”“行首数字加标点”的模式被正则当成了新题号的锚点。解决给正则加前置约束要求题干必须以问句或冒号结尾而不是任意文本。常见做法是把 3.2 的正则改成pattern re.compile( r^\s*(\d{1,3})\s*[\.、]\s* r(.?(?:|\?||:)\s*) # 题干必须以问号或冒号收尾 r(?:答案|参考答案)\s*[:]\s*(.)$, re.M | re.S, )这样“答案里的数字列表”因为不以问号或冒号结尾不会被误当新题。代价是有些题干末尾没有标点符号的文档会少匹配几条所以跑完之后要导出一份“未匹配段落”清单人工核对。宁可少收录也不要错位收录错位数据混进题库查错成本更高。5.3 表格跨页后多出重复表头行数对不上现象解析表格型答案时JSON 里出现大量 question 字段为“题目”“题型”这类表头文字的脏记录。原因Word 里表格跨页后会自动重复表头行python-docx 把这些重复表头也当成普通数据行读出来了。第一次遇到时我以为是 Word 渲染问题实际是文档本身的表格属性。解决清洗表格行时跳过与表头完全一致的行。给 3.3 的代码补一个过滤header_set set(header) for row in rows[1:]: if not row or set(row) header_set: continueset(row) header_set判断当前行是否和表头内容完全一致一致则跳过。要注意的是有些表头单元格是合并的抽取后每个单元格内容相同set 比较仍然有效。如果表头里既有字段名又有空单元格直接比对整行不靠谱那就只对比前两个单元格的内容。5.4 公式和图片抽不出来文本前后断裂现象题干里的数学公式位置在 JSON 里变成了一段莫名其妙的空白或多行折行导致题干和答案在语义上被截断。原因老文档里的公式通常以嵌入对象或图片形式存在它们不在 docx 的文本节点里python-docx 的p.text直接跳过这些对象。看起来是“抽出来了但缺一块”实际是那块内容根本没走文本通道。解决抽取时标记这些位置不要假装它们不存在。近似做法是遍历段落的 run 元素检测到绘图或对象节点时打一个占位标记for para in doc.paragraphs: has_obj any( r._element.xpath(.//w:object) or r._element.xpath(.//w:drawing) for r in para.runs ) if has_obj: records[-1][formula_hint] True_element.xpath直接查 XML 节点是否存在w:object或w:drawing命中就说明这段含公式或图片。运行时把 JSON 里formula_hint为 true 的记录单独导出一份清单走人工复核。不要指望程序能把公式内容还原这个方向的投入产出比很低。5.5 文件后缀是 .doc打开却是别的格式现象antiword 报错说不是有效的 Word 文档soffice 转换后 .docx 是空的或者转换结果只有半个文件大小。原因老系统导出时经常把 RTF、HTML 甚至 docx 文件直接改后缀名成 .doc格式伪装骗过了人工检查骗不过解析器。解决处理前先读文件头做魔数判断再决定走哪条解析路线。MAGIC_DOC b\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1 # OLE 复合文档 MAGIC_DOCX bPK\x03\x04 # zipdocx 也以它开头 MAGIC_RTF b{\\rtf head p.read_bytes()[:8] if head.startswith(MAGIC_DOC): kind doc elif head.startswith(MAGIC_DOCX): kind docx elif head.startswith(MAGIC_RTF): kind rtf else: kind unknownD0CF 开头是标准 OLE 文档真正的老 .docPK 开头说明它是 zip 结构实际是 docx 或 xlsx 之类按后缀名直接改回 .docx 就能正常用{\rtf开头的是 RTF。扫描阶段先按魔数分类把伪装格式的文件踢到对应的处理分支批量脚本就不会在同一个地方反复失败。6. 进阶把清洗后的答案文档变成可检索知识库6.1 先把 JSON 灌进 SQLite一张表装题目清洗后的 records 列表最终会写成一个 JSON 文件但人的日常使用场景是查答案不是翻文件。把 JSON 灌进 SQLite单文件、零部署、好查询后续要扩展字段也容易。import sqlite3, json rows json.loads(open(answers.json, encodingutf-8).read()) conn sqlite3.connect(answers.db) conn.execute(DROP TABLE IF EXISTS answer) conn.execute(CREATE TABLE answer ( id INTEGER PRIMARY KEY, question TEXT, answer TEXT, source TEXT, formula_hint INTEGER DEFAULT 0 )) conn.executemany( INSERT INTO answer(question, answer, source, formula_hint) VALUES(?,?,?,?), [(r[question], r[answer], r.get(source, ), int(r.get(formula_hint, False))) for r in rows], ) conn.commit()每次跑增量更新前先DROP TABLE避免上次残留数据导致主键冲突。formula_hint字段用整数存储SQLite 没有布尔类型这是一个容易被忽略的手脚。空 answer 的记录也要入库入库后跑一条校验 SQL 就能发现哪些答案待补录。6.2 用 FTS5 全文索引做关键词检索SQLite 内置 FTS5 全文索引适合“从答案里搜关键词”这类查询。建虚拟表的方式如下。CREATE VIRTUAL TABLE answer_fts USING fts5( question, answer, contentanswer, content_rowidid ); INSERT INTO answer_fts(answer_fts) SELECT question, answer FROM answer;contentanswer表示索引内容来自 answer 表不复制数据只建索引content_rowidid指定和原表主键的对应关系。检索时用 MATCHres conn.execute( SELECT id, question, snippet(answer_fts) FROM answer_fts WHERE answer_fts MATCH ?, (f{keyword},), ).fetchall()给关键词加上双引号按短语匹配搜出来的结果更准。FTS5 对中文的分词比较粗糙基本是逐字切分但“答案关键词定位”这个场景足够用。查空记录用一条 SQL 就能验证数据质量SELECT count(*) FROM answer WHERE question OR answer ;我最后养成的习惯是每批文档处理完先抽 20 条记录人工核对再整体入库。这个动作看起来原始却是所有自动化流程里成本最低的一道保险。以前有一次跳过核对直接入库答案串到别的题干下面上线两周后才发现重跑清洗脚本又花了一个晚上。从那以后我把抽检写进了流程清单先花五分钟抽查再跑全量。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号