从 .doc 到结构化题库:选择题文档解析与 JSON 转换全流程
2026/9/18 12:34:58 网站建设 项目流程

简介:这份习题集为计算机专业基础课程提供了典型选择题训练,面向本科、高职、自考等阶段的初学者与备考者,可用于章节自测或考前速记。文档以1个doc文件打包,整体仅415KB,轻量便于下载后打印或导入笔记软件使用。全部题目围绕计算机发展脉络展开,涵盖电子数字计算机的分类、ENIAC的历史地位、冯·诺依曼体系结构五大部件、各代计算机逻辑器件与存储器变迁、计算机应用领域(科学计算、数据处理、实时控制等)以及微型机软硬件基础等核心知识点。每题后直接标注正确答案,部分题目附简要解析,能够帮助读者快速定位知识薄弱环节,巩固选择题易考易混的细节。资源已有264人学习浏览,适合考前突击或日常碎片化复习。

1. 一份选择题.doc,卡住的往往不是题目

拿到「计算机专业习题-选择题.doc」这类文档,大部分人的第一反应是双击打开、复制粘贴、整理成 Excel 或导入刷题系统。但真到落地阶段,你会发现麻烦全在“看不见”的地方:老版本 .doc 的二进制格式、段落里混排的自动编号、答案换个位置写法就完全不同的结构。计算机专业课的题库往往是课程组一届一届传下来的,题目没问题,格式却可能跨越了 Word 97 到 WPS 的多个时代。这篇文章会从 .doc 的格式兼容问题讲起,到选择题结构的自动切分、答案匹配与对账,最后落地成一份可直接入库的 JSON。整个过程适用于所有以选择题为主的题库类文档,尤其是含大量代码片段、英文术语的计算机专业课习题。

2. 把 .doc 读出来的三条路径:格式兼容与编码坑

2.1 为什么 python-docx 直接打开 .doc 会报错

常见的办公文档处理库,比如 python-docx,只支持 .docx 格式。.doc 是 OLE2 复合文档结构,而 .docx 是 ZIP 包内的 XML 文件集合,两者底层完全不同。直接拿 python-docx 去读一个老 .doc,通常会抛出一个找不到 word/document.xml 的异常。这个问题的本质不是代码写的对不对,而是容器格式根本不对。

所以在处理「计算机专业习题-选择题.doc」之前,第一步永远是格式转换或底层解析。常见的路径有三条,我按优先级梳理如下。

路径工具适用场景主要限制
无头转换LibreOffice / soffice批量处理、保留排版需要安装 LibreOffice
纯文本抽取antiword只关心文字内容图表、公式全部丢失
底层解析olefile + 手动解析 WordDocument 流特殊格式研究工作量大,不推荐

2.2 首选方案:LibreOffice 无头转换 doc → docx

常见做法是安装 LibreOffice,用它的命令行无头模式把 .doc 批量转成 .docx,再用 python-docx 读取。转换命令长这样:

soffice --headless --convert-to docx --outdir ./converted "计算机专业习题-选择题.doc"

这条命令的核心参数有三个。--headless表示不启动图形界面,适合在服务器上跑;--convert-to docx指定目标格式;--outdir指定输出目录。转换完成后,在converted目录下会生成同名.docx文件,此时再用 python-docx 打开就不会报错了。

如果你有几十份类似的 .doc 文件,可以直接写个循环批量处理:

for f in *.doc; do soffice --headless --convert-to docx --outdir ./converted "$f" done

要注意的是,LibreOffice 转换对中文文件名支持良好,但如果原文档里嵌入了老旧的公式编辑器对象(比如 MathType 3.x),转换成 docx 后公式可能变成图片,这会影响后续的文本提取。计算机专业课习题里常见程序运行结果、进制转换这类纯文本题目,转换后基本不会丢失内容。

2.3 备用路径:antiword 直接抽文本

如果只是想把文本内容捞出来看个大概,不关心格式和排版,antiword 是个更轻量的选择:

antiword "计算机专业习题-选择题.doc" > output.txt

antiword 的输出是纯文本,题号和选项内容都能看到,但缺点是它不处理表格、文本框和图片。如果文档里有用文本框排版的选择题,antiword 抽出来的内容顺序可能会乱。我一般拿 antiword 做快速预览,正式处理还是走 LibreOffice 转 docx 再解析的路线。

提示:转换后一定要抽查转换结果。打开生成的 .docx 确认题目顺序、段落编号、代码缩进没有丢失,再进行下一步的解析。

2.4 中文编码乱码的处理

.doc 里的中文文本存储有 ANSI 和 Unicode 两种可能。老版本 Word 存的是 GBK/GB2312,新版本可能存 UTF-16LE。用 antiword 抽文本时如果看到乱码,多半是编码参数不对:

antiword -m cp936.txt "计算机专业习题-选择题.doc"

现代 Linux 发行版的 antiword 通常自带 cp936 映射文件,找不到时需要手动指定映射路径。如果走 LibreOffice 转 docx 的路线,编码问题基本被透明处理掉了,这也是我优先推荐它的原因。

3. 选择题结构切分:题号、选项与段落噪点

3.1 用正则把题干段落切成题目块

转换成 docx 之后,题目内容以段落形式存在。解析选择题的第一步是把连续的段落按题号切分成一个个独立题目块。计算机专业习题的题号格式比较常规,常见的有1.1、1.(全角句点)等,但偶尔也有第1题这种带文字的格式。

我一般用正则做两段匹配:先匹配题号,再匹配选项。下面是核心切分代码:

import re from docx import Document doc = Document("计算机专业习题-选择题.docx") paragraphs = [p.text.strip() for p in doc.paragraphs if p.text.strip()] question_pat = re.compile(r'^(\d{1,3})\s*[\.、.、))]') option_pat = re.compile(r'^([A-H])\s*[\.、.、))]\s*(.*)') questions = [] current = None for line in paragraphs: qm = question_pat.match(line) if qm: if current: questions.append(current) current = { "qid": int(qm.group(1)), "stem": line, "options": [], "answer": None } else: if current is None: continue om = option_pat.match(line) if om: current["options"].append({ "key": om.group(1), "content": om.group(2) }) else: current["stem"] += "\n" + line if current: questions.append(current) print(f"共解析到 {len(questions)} 道题目")

这段代码的逻辑分三层。第一层用question_pat匹配题号开头的段落,一旦命中就新建一个题目块;第二层用option_pat匹配 A.、B.、C. 这类选项开头;第三层处理题干跨多段的情况,即既不是题号也不是选项的行,直接追加到当前题干的正文里。

正则里的[\.、.、))]覆盖了半角句点、顿号、全角句点和括号的多种书写习惯。计算机专业课的习题里常出现“以下程序运行结果是?”这种短题干占一行、选项跟后面的排版,上面的代码可以正确处理。

3.2 多选题、判断题与无选项题目的边界

计算机专业的选择题不全是单选,常见的变形有三种:多选题、判断题、填空题。多选题的选项和单选一样是 A-D 甚至 A-F,但答案不只一个;判断题往往没有 ABCD 选项,题干直接是“正确”或“错误”的陈述;填空题则根本没有选项结构。

多选题在结构上和单选无差异,区分点全在答案内容的长度上。判断题则需要在解析时做特殊识别:

judge_pat = re.compile(r'^[((]\s*([√×])\s*[))]')

如果一行命中这个模式,说明这是判断题答案,而不是选择题的选项行。遇到这种情况,可以直接把题干标记为判断题,不期望后续出现选项。切分代码里需要注意:判断行的选项匹配不能意外吞掉题干文本,否则后续的答案匹配会对不上号。

3.3 自动编号带来的整段丢失问题

这是最容易踩的深坑。Word 里的“自动编号”不是真实文本,而是 WordprocessingML 里的<w:numPr>结构。如果你在 python-docx 里通过p.text读取,题号可能吞掉,也可能和文本拼在一起成一个怪字符串。

<w:p> <w:pPr> <w:numPr> <w:ilvl w:val="0"/> <w:numId w:val="1"/> </w:numPr> </w:pPr> <w:r><w:t>在操作系统中,进程和程序的主要区别是?</w:t></w:r> </w:p>

上面这段 XML 里,题号“1.”是自动编号生成的,段落文本里只有题干内容。按 3.1 的正则切分时,这个段落匹配不到题号,会被当成题干续行,导致和前面的题目块合并,最终题目数量变少。

处理办法是:当正则匹配不到新题号,但这个段落格式和上一段明显分属不同题目时,需要检查段落的numPr信息。python-docx 对numPr的原生支持有限,但可以通过p._p.pPr手动访问 XML。遇到这种文档,我一般直接绕回 LibreOffice,用转换后的文档跑一遍逐段检查,把自动编号落成真实文本再解析。

4. 答案区域定位与匹配对账

4.1 三种常见的答案书写习惯

切分出题目结构后,最麻烦的一步是怎么把答案对应到题号上。计算机专业习题的 .doc 里,答案的书写习惯基本是三种。

第一种是题末内联,格式为“答案:C”或者“【答案】C”,紧跟在选项后面;第二种是集中答案区,文档末尾有一段连续的区域,格式通常是“1-5:B A C D B”或者“1.B 2.C 3.A”;第三种是带解析的混合型,题目后面跟着答案和解析,比如“答案:C,解析:进程是资源分配的最小单位”。

第三种最容易处理,答案和题干在同一个题目块内。第一种需要先定位题目块结尾,再匹配答案关键字。第二种最难,因为集中答案区往往不分段落,一长串答案挤在一起。

4.2 答案匹配的核心代码

以下代码处理题末内联答案和文件末尾集中答案区两种场景:

import re inline_ans = re.compile(r'[答案|参考答案][::]\s*([A-H]+)') # 处理内联答案 for q in questions: m = inline_ans.search(q["stem"]) if m: q["answer"] = m.group(1) # 处理集中答案区 concentrated_pat = re.compile(r'(\d{1,3})\s*[\.、.]\s*([A-H]+)') concentrated_answers = {} for p in doc.paragraphs: text = p.text.strip() if "答案" in text and re.search(r'\d{1,3}', text): for match in concentrated_pat.finditer(text): concentrated_answers[int(match.group(1))] = match.group(2) for q in questions: if q["answer"] is None: q["answer"] = concentrated_answers.get(q["qid"])

上面这段代码先扫描题目内联答案,优先匹配“答案”或“参考答案”后跟冒号和字母的格式;接着找出包含题号的段落,用finditer把所有“题号 + 答案字母”的配对提取成字典;最后对于没匹配到内联答案的题目,去字典里查它的题号。

re.finditerre.search更适合集中答案区,因为文档里常有“1.B 2.C 3.A”这种一行多个配对的情况,search只取第一个,finditer全部捞出。

提示:集中答案区匹配到的题号数如果远小于题目总数,多半是答案区的题号和题目区的题号格式不一致。比如题目区消耗“1.”半角句点,答案区用“1、”顿号,正则里必须用[\.、.]字符类统一兼容。

4.3 对账失败时先查这四个位置

跑完答案匹配后,必须对账。最简单的方法是统计“有答案的题目数”和“总题目数”,如果不等,优先查四个位置。

第一个是题干里包含答案字样。有些题目本身的题干就写着“以下哪项不是……”,如果解析时把“答案”当作内联答案,就会错配。这种情况需要限制内联答案的匹配位置,比如要求“答案”必须出现在最后一个选项之后。第二个是选项漏切导致答案挂在题干里。第三个是自动编号导致的题号错位,答案区对不上题号。第四个是选择题和判断题混排,判断题答案画的是“√/×”而不是 ABCD,切分后答案字段一直填不上。

对账代码建议这样写:

missing = [q["qid"] for q in questions if q["answer"] is None] print(f"缺失答案的题目:{missing}")

如果缺失的题号呈连续分布,比如 12-18 题全缺,基本可以断定是文档中间某段格式突变,比如 12 题开始选项变成了“① ② ③”的序号,而非 ABCD。此时需要回到原始 .doc,人工确认该区间的真实格式。

5. 批量跑通:从 .doc 到结构化题库 JSON

处理一份「计算机专业习题-选择题.doc」不复杂,但如果是一个课程组的题库文件夹,几十份文档、几千道题,就得把流程串成一个脚本。我的做法是把上一章的解析函数整理成parse_doc_to_questions(docx_path),再加上全目录遍历和 JSON 导出。

import os import json import subprocess from docx import Document def convert_doc_to_docx(doc_path, out_dir): subprocess.run( ["soffice", "--headless", "--convert-to", "docx", "--outdir", out_dir, doc_path], check=True, capture_output=True ) def process_folder(src_dir, tmp_dir, output_path): all_questions = [] for fname in os.listdir(src_dir): if fname.endswith(".doc"): docx_path = convert_doc_to_docx( os.path.join(src_dir, fname), tmp_dir ) questions = parse_doc_to_questions(docx_path) all_questions.extend(questions) with open(output_path, "w", encoding="utf-8") as f: json.dump(all_questions, f, ensure_ascii=False, indent=2) process_folder("题库原始文件", "临时转换目录", "题库输出.json")

process_folder函数做的事很简单:遍历源目录下所有 .doc 文件,逐个转成 docx 再解析,最后把所有题目合并导出成一个 JSON 数组。JSON 里的每个题目对象包含题号、题干、选项列表和答案字段,后续要导入刷题平台、排版新试卷或做知识点标注,都可以基于这个结构继续扩展。

最后的验证环节,我会从导出的 JSON 里随机抽 5 道题,逐条对照原始 .doc 的排版确认题干完整、选项顺序正确、答案对应无误。对于最终落到在线刷题系统的数据,还可以额外跑一遍答案合法性校验:多选题答案里的字母必须按升序排列,答案字母必须在 A-F 范围内,题号必须连续且不能有重复,这些规则用一次遍历就能完成核查。题库整理到此才算真正收口。

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

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

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

立即咨询