电工基础试题库doc转JSON+SQLite,用Python构建刷题自测系统 简介面向电气工程及相关专业初学者、备考学生和授课教师这套电工基础试题库及参考答案聚焦直流电路、电路分析与电磁感应等核心知识点能够用于课后练习、考前复习与课堂测验。包内为1份doc文档压缩包约3.92MB内容编排清晰便于打印或电子浏览。试题囊括填空、判断、选择等常见题型覆盖导体与绝缘体分类、电路三种状态、电流方向与电压电动势、电阻与电导、欧姆定律、楞次定律、电阻串并联、电桥平衡、基尔霍夫定律、戴维南定理、叠加定理等多数题目可直接对照参考答案自测也方便教师快速组建试卷。已有139人学习下载适合电工基础入门练习、期末备考和教学出题使用是巩固理论知识并检验掌握程度的实用文档。1. 电工基础试题库这份doc是时候改成结构化题库了拿到《电工基础试题库及参考答案.doc》这类文件很多人第一反应是打开、翻几页、背几道题或者直接打印出来。但对靠电脑吃饭的IT从业者来说这份doc最大的问题是不可检索、不可统计、不可变着花样组卷。你没办法知道哪类电工基础公式题反复错也没法把题目快速倒进在线练习系统。我拿到这类文档的第一件事就是把它改造成JSON和SQLite然后基于结构化数据做命令行自测和本地网页刷题。整个过程用Python就能搞定不需要咬牙读完几百页doc。这篇博文就讲清楚怎么拆题、怎么存、怎么自测以及doc里那些坑怎么填。2. 把《电工基础试题库及参考答案.doc》拆成可处理的文本2.1 为什么先转docx而不是直接读docpython-docx这个库只能读取.docx格式它内部是一个XML包而老式的.doc是二进制结构需要用Word或LibreOffice先转一次。我一般用LibreOffice的headless模式批量转换命令如下soffice --headless --convert-to docx --outdir ./converted 电工基础试题库及参考答案.doc转换完成后converted目录下就有同名.docx文件。这条命令在Linux服务器上也能跑适合大批量处理。参数说明--headless表示不弹界面--convert-to docx指定输出格式--outdir指定输出目录最后一个参数是输入文件。如果文件名里有空格记得加引号。转换这一步不能省。有人试着用antiword或catdoc直接读.doc那些工具只能抽出纯文本带编号的图表和公式会丢电工基础题里常见的电路图、公式符号往往就没了。转成docx之后段落结构、表格、图片都能被python-docx识别后续解析才有据可依。2.2 电工基础试题库常见的版式是什么样的我处理过不少电工基础类题库文档虽然没看过你手上这份的原稿但这类教材配套题库的版式高度相似。最常见的单选结构是题目编号后跟题干选项A到D各占一行答案以“参考答案B”或“【答案】C”的形式出现在题干后面或文档末尾。也有少数把答案集中放在最后一页比如“1B 2A 3D”这样的压缩块。为了确认你那份doc的版式转换后先打印前20个非空段落from docx import Document doc Document(电工基础试题库及参考答案.docx) paragraphs [p.text.strip() for p in doc.paragraphs if p.text.strip()] for i, p in enumerate(paragraphs[:20]): print(i, p)运行后重点看三个地方题目编号用的是“1.”、“1、”还是“1”选项前是“A.”还是“(A)”答案标识是“参考答案”还是“答案”或“正确答案”。这三个正则模式直接决定了解析代码要不要改。很多人在这一步就翻车因为拿到文档不先观察结构直接套通用正则结果解析出一堆空字段。2.3 按段落归并题目块提取题干、选项和答案确认版式后最稳妥的解析方式不是逐行当独立数据而是以题目编号为起点把后续非空段落收进同一个题目对象直到遇到下一个编号。下面这段代码能处理“1.”“1、”“1”三种编号以及选项和答案的常见写法import re import json from docx import Document def parse_question_bank(docx_path): doc Document(docx_path) lines [p.text.strip() for p in doc.paragraphs if p.text.strip()] questions [] cur None q_pattern re.compile(r^(\d)[\.、]\s*(.*)) opt_pattern re.compile(r^([A-D])[\.、\s]\s*(.*)) ans_pattern re.compile(r(?:参考答案|答案|正确答案)[:]?\s*([A-D])) for line in lines: m q_pattern.match(line) if m: if cur: questions.append(cur) cur { id: int(m.group(1)), question: m.group(2), options: {}, answer: None } continue if not cur: continue m opt_pattern.match(line) if m: cur[options][m.group(1)] m.group(2) continue m ans_pattern.search(line) if m: cur[answer] m.group(1) if cur: questions.append(cur) return questions questions parse_question_bank(电工基础试题库及参考答案.docx) print(f解析到 {len(questions)} 道题) print(json.dumps(questions[:2], ensure_asciiFalse, indent2))这段代码做的事把每个符合条件的非空段落入当前题目对象里。q_pattern匹配题目编号opt_pattern匹配选项ans_pattern搜索答案字母。逻辑说明选项和答案都必须发生在某个题目开始之后所以用continue跳过与当前题目无关的行。参数说明如果选项写成“A、”而不是“A.”把opt_pattern改成r^([A-D])[、\.]\s*(.*)如果题号是“第1题”这种则改为r^第(\d)题\s*(.*)。解析后建议立刻打印前两道题看结构。常见问题是题干里包含图片说明文字导致question字段多出东西或者答案字母被当成选项内容。遇到这种情况先看原始行内容再决定是修改正则还是手工清洗。3. 电工基础题目的结构化存储与自测脚本3.1 定义一份干净的JSON结构解析得到的Python字典可以直接存成JSON。结构越简单越好方便后续被命令行工具、Web应用、Anki插件消费。我一般用这套字段字段类型说明是否必填idint题目序号来自原文是typestring题型默认single单选是questionstring题干纯文本是optionsobject键为A-D值为选项文本是判断题可为空answerstring正确选项字母是analysisstring解析或备注否为什么不用数组存选项因为JSON对象可以直接用options[B]定位避免在数组里找字母下标。判断题没有选项时options可以留空但answer应写成A或B代表“正确”或“错误”这样判分逻辑统一。写入JSON的代码很简单with open(questions.json, w, encodingutf-8) as f: json.dump(questions, f, ensure_asciiFalse, indent2)ensure_asciiFalse是为了让中文直接可读indent2方便在编辑器里人肉检查。如果你准备给前端页面或小程序用这一步已经够了。3.2 把JSON灌进SQLite查询和统计才有优势纯JSON适合小体量但当题目超过两千道想在自测时按章节、按错误次数抽题SQLite就比反复加载JSON更顺手。建表和灌数据可以用标准库sqlite3完成import sqlite3 import json conn sqlite3.connect(questions.db) conn.execute(CREATE TABLE IF NOT EXISTS questions ( id INTEGER PRIMARY KEY, type TEXT, question TEXT, options TEXT, answer TEXT, analysis TEXT, wrong_count INTEGER DEFAULT 0 )) with open(questions.json, encodingutf-8) as f: data json.load(f) conn.executemany( INSERT OR REPLACE INTO questions (id, type, question, options, answer, analysis) VALUES (?, ?, ?, ?, ?, ?), [(q[id], q.get(type, single), q[question], json.dumps(q[options], ensure_asciiFalse), q[answer], q.get(analysis, )) for q in data] ) conn.commit()逻辑说明options字段用json.dumps序列化成字符串存入SQLite查询时反序列化回字典。wrong_count字段预留在后面统计错题。参数说明INSERT OR REPLACE保证重复导入时按id覆盖适合转换脚本反复调整后重新灌库如果文档没有唯一id可以删掉这行让SQLite自动生成主键。3.3 在命令行里做十题一轮的快速自测题库存进SQLite后最直接的用法是写一个不依赖任何框架的抽题脚本。下面这个函数每次从表里随机取10道题逐题等用户输入答案最后打印得分和错题清单import sqlite3 import json import random def quiz(conn, num10): rows conn.execute( SELECT id, question, options, answer FROM questions ORDER BY RANDOM() LIMIT ?, (num,) ).fetchall() score 0 wrong [] for i, (qid, question, options_json, correct) in enumerate(rows, 1): options json.loads(options_json) print(f\n{i}. {question}) for key in sorted(options.keys()): print(f {key}. {options[key]}) answer input(你的答案直接输入字母).strip().upper() if answer correct: score 1 print(答对了) else: wrong.append((qid, question, correct, answer)) print(f答错了正确答案是 {correct}) print(f\n得分{score} / {len(rows)}) if wrong: print(错题记录) for qid, q, c, a in wrong: print(f {qid}: {q} (正确 {c}, 你选 {a})) return wrong参数num控制抽题数量ORDER BY RANDOM()在SQLite里对几千行的表性能足够不需要额外随机算法。逻辑说明答案统一用.upper()转大写避免用户输入小写导致误判。wrong列表里存了题目id方便后续把错题单独导出。要注意的是题库中如果混有多选题和判断题这个脚本需要按type分支处理否则多选题只答一个字母必然判错。4. 处理电工基础文档时经常踩的解析坑4.1 答案标记五花八门一个正则应多备几组我见过的一份电工基础试题库里答案交替出现“参考答案A”“【答案】 B”“正确C”三种写法甚至还夹杂全角冒号。只用一组正则很容易漏。我的做法是准备一个模式列表逐行尝试ans_patterns [ r参考答案[:]?\s*([A-D]), r答案[:]?\s*([A-D]), r【答案】[:]?\s*([A-D]), r正确答案[:]?\s*([A-D]), ] def find_answer(line): for pattern in ans_patterns: m re.search(pattern, line) if m: return m.group(1) return None注意顺序把“参考答案”放在“答案”前面否则“参考答案”中的“答案”会被先匹配取到的可能是错位字符。参数说明如果文档里答案写成“选A”或“应选A”再增加r选([A-D])模式。每增加一组模式前先统计一下未匹配的题目数量不要盲目堆。4.2 答案集中在文末时需要按题目id回填部分文档为了排版紧凑把答案统一放在最后一页形式是“1B 2C 3A 4D”。这种情况下按段落解析根本拿不到答案必须先定位整个文档的答案块解析出id到答案的映射再回填到题目列表。import re def parse_tail_answers(text): block re.search(r答案[:]((?:\d[A-D]\s*)), text) if not block: return {} pairs re.findall(r(\d)([A-D]), block.group(1)) return {int(qid): ans for qid, ans in pairs} # 把文档所有段落拼成一个大文本再匹配 full_text \n.join(lines) tail_answers parse_tail_answers(full_text) for q in questions: if q[answer] is None and q[id] in tail_answers: q[answer] tail_answers[q[id]]逻辑说明\d匹配题目序号[A-D]匹配答案字母两者成对出现。参数说明如果答案是“正确”和“错误”则改成r(\d)\(?(正确|错误)\)?但注意这一步只针对判断题。回填后必须再跑一遍校验确认每条记录都有答案否则后面自测脚本会因answer为空而崩溃。4.3 表格版题目需要换一种遍历方式有时候电工基础题库的题目不是独立段落而是嵌在Word表格里每个单元格存一道题。Document.paragraphs只会返回表格外面的段落表格内文字需要用doc.tables遍历。def extract_from_tables(doc): rows [] for table in doc.tables: for row in table.rows: cells [cell.text.strip() for cell in row.cells] if cells and cells[0]: rows.append(cells) return rows这段代码把每个单元格的文本拼成一行后续再按行内容判断是题干、选项还是答案。逻辑说明表格版和一个段落一个选项的版式区别在于表格中每个单元格内容可能是“A. 电流”而不是换行后的独立段落所以需要按行合并后重新走正则。参数说明row.cells在合并单元格时会返回重复引用如果发现解析结果出现大量重复选项需要去重处理。提示解析前最好统计一下文档里tables的数量。一张题量几百的doc表格抽取错误会导致题目数量少一半。5. 让电工基础题库变成一个本地网页应用5.1 用Flask提供一个读题接口结构化数据最实落的使用场景是刷题网页。用Flask起一个本地服务每次从JSON里随机抽题前端拿到题目渲染提交答案后返回对错。最小可跑的后端代码如下from flask import Flask, jsonify, request import json import random app Flask(__name__) with open(questions.json, encodingutf-8) as f: QUESTIONS json.load(f) app.route(/question) def get_question(): q random.choice(QUESTIONS) return jsonify({ id: q[id], question: q[question], options: q[options] }) app.route(/answer, methods[POST]) def check_answer(): data request.get_json(forceTrue) qid data[id] answer data[answer].upper() q next(x for x in QUESTIONS if x[id] qid) correct answer q[answer] return jsonify({correct: correct, correct_answer: q[answer]}) if __name__ __main__: app.run(debugTrue, port5000)逻辑说明random.choice从题池中均匀抽取不做权重调整如果你希望优先抽错题改成从SQLite的wrong_count高的记录里取。参数说明debugTrue只适合本机调试局域网给别人用时要关闭并设置host0.0.0.0。5.2 前端十行JS完成下一题和计分后端接口只需要一个简单的HTML页面配合这里不用Vue直接写在static/index.html里let current null; let score 0; let total 0; async function loadQuestion() { const res await fetch(/question); current await res.json(); total; document.getElementById(q).textContent current.question; const opts Object.entries(current.options) .map(([k, v]) ${k}. ${v}).join(br); document.getElementById(opts).innerHTML opts; } async function submitAnswer() { const answer document.getElementById(answer).value.trim(); const res await fetch(/answer, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({id: current.id, answer}) }); const data await res.json(); if (data.correct) { score; document.getElementById(result).textContent 正确; } else { document.getElementById(result).textContent 错误正确答案 ${data.correct_answer}; } document.getElementById(score).textContent ${score} / ${total}; }这段代码没有做异步防抖连续快速点“提交”会导致结果错乱但作为单人自测工具够用了。逻辑说明score和total是全局变量每次载入新题累计。参数说明如果题目是多选这里要把输入框改为复选框组合submitAnswer内收集所有勾选字母后排序再与后端比对。5.3 验证题库完整性的三条命令流程全部跑通后不要急着删掉原始doc。先做三轮验证第一轮确认JSON语法正确第二轮确认题目数量与原文一致第三轮确认每条答案都存在于选项里。python -m json.tool questions.json /dev/null echo JSON OK sqlite3 questions.db SELECT COUNT(*) FROM questions; python -c import json; djson.load(open(questions.json)); bad[q[id] for q in d if q[answer] not in q[options] and q.get(type) ! judge]; print(bad ids:, bad)第一条命令校验JSON格式 /dev/null是把格式化后的内容丢弃只看有没有报错第二条命令输出总题目数和原始doc里数到的题号最大值比对第三条命令找出答案字母不在选项内的题目id这些题通常是解析阶段漏选项或答案写错位置。如果bad ids为空说明整个解析链路是通的。到这里一份“电工基础试题库及参考答案.doc”就变成了一套可持续维护的刷题系统。本文还有配套的精品资源点击获取