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

用python-docx把医疗器械培训试题Word转成可组卷判分题库

  • 首页
  • 资讯中心
  • /
  • 用python-docx把医疗器械培训试题Word转成可组卷判分题库

相关资讯

在 Qwen 多模型评测中,OpenRouter token 与 TaoToken Key 的边界 2026/9/18 1:35:45
Godot 源码编译实战:SCons、模块裁剪与导出模板 2026/9/18 1:35:45
x64dbg 入门指南:安装、使用与源码构建 Windows 开源二进制调试器 2026/9/18 1:35:45

最新资讯

SQL Server Always On可用性组搭建实战:从环境准备到故障转移
SuBaseToolsBox:UE5编辑器效率提升开源工具箱
基于STM32的智能婴儿床系统设计与实现
Unreal动画框架全链路解析:AnimGraph求值、线程安全与性能优化
基于Vue3+Python的赛事发布与在线选座系统设计与实现
Storybook 中记录按钮点击事件的 CSF 写法:从 render 硬编码到 Args 驱动

今日推荐

2026年AI设计工具在PPT制作中的核心应用与评测
Matlab手写逻辑回归:从数学原理到多变量概率预测模型实现
高值医用耗材研报PDF:用Python完成字段抽取、清洗与趋势预测

本周热门

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化
Flutter应用改名全指南:从Android到iOS的配置与工具实践

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

用python-docx把医疗器械培训试题Word转成可组卷判分题库

发布时间:2026/9/18 1:40:45
用python-docx把医疗器械培训试题Word转成可组卷判分题库 简介这是一份面向医疗器械行业从业者、质量管理人员及备考人员的培训试题资料聚焦2025年医疗器械监管法规与审批实务可用于岗位培训自测、执业资格复习与合规知识查漏。资源包内含1个docx文档约32KB内容按填空题、单选题、多选题等题型编排并附参考答案覆盖行政许可注销、产品备案与注册分类、注册证五年有效期及届满前6个月延续申请、生产与经营许可条件、广告审批、注册证书违规处罚、不良事件报告与再评价等考点还涉及医用中心制氧系统分类、居民住宅不得作仓库等细节。题目附带解析性答案便于边练边核对法规依据快速定位薄弱环节。目前已有303人学习下载适合需要系统梳理《医疗器械监督管理条例》要点、查漏补缺的初中级从业者与备考人员使用。1. 从一份「2025医疗器械培训试题(含答案).docx」说起质量部的同事把文件甩过来的时候只说了一句话下周三要上线线上答题题目就用这份别改内容。打开一看三百多道题全堆在一个文档里有的按「1. 题干 / A. / B. / 答案A / 解析」排得规规矩矩有的整块塞进两列表格答案用红色字体标出来还有几道多选题写的是「正确答案ABD」另几道只写了「答案 ABD」漏了冒号。直接把这个 docx 挂到内网上让人下载等于没做培训真正要干的活是把它拆成结构化的题库能抽题、能判分、能出统计、能留痕。适合读这篇的人有三类被临时抓来做内部培训系统的开发负责质量体系文件、要整理历年培训试题的器械从业者以及想把 Word 版考试资料转成可检索数据的运维。医疗器械经营和使用单位的培训记录属于质量体系要归档的材料试题只是其中最容易被反复复用的一块把它做成结构化数据后面每年新增的题都是增量维护而不是重新排一次版。2. 用 python-docx 解析「2025医疗器械培训试题(含答案).docx」2.1 先 dump 结构再决定正则怎么写docx 本质上是一个 zip 包加一堆 XML段落里的文字会被拆成多个 run——字体一变、颜色一变、加粗一变就断一次。这意味着paragraph.text拿到的完整句子看着没问题但答案为什么是红色、题干为什么加粗这类信息只在 run 级别才有。指望一个正则吃下全文最后一定在某个奇怪的地方翻车。我一般的做法是先跑一遍探针脚本把前几十个段落的结构打出来看from docx import Document doc Document(2025医疗器械培训试题(含答案).docx) # 段落探针看样式名 每个 run 的文字/加粗/颜色 for i, p in enumerate(doc.paragraphs[:40]): runs [] for r in p.runs: color None if r.font.color is not None and r.font.color.type is not None: color str(r.font.color.rgb) if r.font.color.rgb else None runs.append((r.text[:20], r.bold, color)) print(i, repr(p.style.name), repr(p.text[:60]), runs) # 表格探针不少试卷把题干和答案塞在 2 列表格里 print(tables:, len(doc.tables)) for t in doc.tables[:2]: for row in t.rows[:5]: print([c.text.strip()[:40] for c in row.cells])跑完这一步你会拿到三样关键信息style.name能不能区分「题号行」和「选项行」答案是不是靠某个固定色值比如FF0000或企业模板里的C00000标记表格里是哪一列放题目、哪一列放答案。注意不同模板导出的 docx红色可能是FF0000、C00000、E36C0A三种以上。色值别写死先 dump 出来统计一遍取出现频率最高的那个当阈值。2.2 题干、选项、答案、解析的提取规则真正写解析器时用有限状态机比用一次性正则稳得多逐段扫描判断当前段落属于哪一类元素然后挂到「当前题目」对象上。先把识别规则列清楚元素文档里的常见写法匹配思路题号1.1、第1题(1)行首数字 分隔符同时该行长度通常大于 10单选题干1. 关于无菌器械储存的说法正确的是题号行且不含答案关键词选项A.B、C行首单字母 分隔符字母范围 A–H答案答案A正确答案:AB【答案】A关键词 可选冒号 答案串解析解析答案解析:关键词 冒号 正文题型标记单选题多选判断题括号内关键词也可靠选项数量和答案长度反推对应的解析核心逻辑大致是这样import re from docx import Document RE_QNO re.compile(r^\s*(?:第)?(\d{1,4})\s*[.、)]\s*(.)$) RE_OPT re.compile(r^\s*([A-H])\s*[.、)]\s*(.?)\s*$) RE_ANS re.compile(r^\s*[【\[]?\s*(?:正确答案|参考答案|答案)\s*[】\]]?\s*[:]?\s*([A-H]{1,8}|对|错|正确|错误|√|×)\s*$) RE_EXP re.compile(r^\s*(?:答案解析|试题解析|解析)\s*[:]\s*(.*)$) def parse_docx(path): doc, questions, cur Document(path), [], None for p in doc.paragraphs: # 软回车 w:br/ 会让一段里藏多行必须先拆 for line in p.text.split(\n): line line.rstrip() if not line.strip(): continue m RE_ANS.match(line) if m and cur is not None: cur[raw_answer] m.group(1) continue m RE_EXP.match(line) if m and cur is not None: cur[explanation] m.group(1).strip() continue m RE_OPT.match(line) if m and cur is not None and len(line) 200: cur[options].append({label: m.group(1), content: m.group(2)}) continue m RE_QNO.match(line) # 选项行也可能匹配到题号规则用「当前题目是否已有选项」兜一下 if m and len(m.group(2)) 8 and not (cur and cur[options] and not cur.get(raw_answer)): if cur: questions.append(cur) cur {no: m.group(1), stem: m.group(2).strip(), options: [], raw_answer: , explanation: } continue # 题干换行续写拼回上一题的题干 if cur is not None and not cur[options]: cur[stem] line.strip() if cur: questions.append(cur) return questions这段代码有三处值得说明。第一处是软回车拆分Word 里按住 Shift 回车产生的w:br/python-docx 会渲染成\n留在同一个段落里不拆的话一整个题组会被当成一行。第二处是「续行拼接」不少试卷题干过长会自动换行下一段没有题号也没有选项字母只能判断成上一题的题干补充。第三处是题号规则的兜底条件A. 无菌这种选项行同样能被^\d[.、]之外的模式漏掉所以先匹配选项、再匹配答案、最后匹配题号顺序不能颠倒。2.3 三种解析失败形态的兜底处理跑完第一版几乎不可能全对最常见的失败有三种。其一是表格型试卷题干和答案分列两栏段落遍历拿不到任何内容得单独走doc.tables把每行的 cell 文本按列取出来合成题干、答案再复用上面的正则。其二是红字答案题干和选项里根本没有「答案」两个字答案是直接被标红的需要按 run 判断颜色把红字内容收集起来当答案同时把红字片段从题干里剔除。其三是编号断裂试卷中间插了一张「第 5 题至第 12 题答案B A C D A B D C」的答案汇总表题干区不带答案这时要先把汇总表解析成no - answer的字典遍历完之后再回填。解析完成后一定要做一次数量核对输出题目总数、各题型数量、有答案的题目数、有解析的题目数。任何一个数字和原文档目录对不上都说明有题目被漏掉或合并了回查比事后补数据省事得多。3. 题库表结构设计与入库前的清洗校验3.1 用六张表还是一根 JSON 字段小规模场景几百道题、几十个人答题直接用一个question表加一个 JSON 字段存选项也够用但一旦要按知识点抽题、要统计每个知识点的正确率、要做错题本JSON 字段就开始难受了。稳妥的做法是拆表CREATE TABLE question ( id INTEGER PRIMARY KEY AUTOINCREMENT, year SMALLINT NOT NULL, -- 题目所属年度如 2025 qtype VARCHAR(8) NOT NULL, -- single/multiple/judge/fill/essay stem TEXT NOT NULL, -- 题干 answer VARCHAR(16) NOT NULL DEFAULT , -- 单选 A多选 ABD判断 T/F填空用 | 分隔 explanation TEXT DEFAULT , -- 解析 knowledge VARCHAR(64) DEFAULT , -- 知识点如「无菌器械储存」 difficulty TINYINT DEFAULT 2, -- 1 易 2 中 3 难 source_hash CHAR(32) NOT NULL, -- 题干归一化后的 MD5用于去重 status TINYINT DEFAULT 1, -- 1 启用 0 停用 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE UNIQUE INDEX uk_q_hash ON question(source_hash); CREATE INDEX idx_q_pick ON question(year, qtype, status); CREATE TABLE question_option ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL REFERENCES question(id) ON DELETE CASCADE, label CHAR(1) NOT NULL, -- A/B/C/D content TEXT NOT NULL, sort_no SMALLINT DEFAULT 0 ); CREATE INDEX idx_opt_qid ON question_option(question_id); CREATE TABLE exam_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, paper_id INTEGER NOT NULL, score DECIMAL(5,1) DEFAULT 0, started_at DATETIME, submitted_at DATETIME, passed TINYINT DEFAULT 0 -- 是否达到及格线 ); CREATE TABLE answer_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, exam_id INTEGER NOT NULL REFERENCES exam_record(id), question_id INTEGER NOT NULL, user_answer VARCHAR(64), is_right TINYINT DEFAULT 0, score_got DECIMAL(4,1) DEFAULT 0 );几处设计取舍year单独成列是因为每年题库都会更新抽题时经常要求「2025 年度试题」source_hash做唯一索引重复导入同一份 docx 时靠INSERT OR IGNORE天然幂等knowledge直接存字符串而不是外键表在题量低于一万时完全够用真要统计再补一张映射表也不迟answer_record必须逐题落库错题本和知识点正确率都是从这张表算出来的。3.2 入库前的三道质检答案缺失、选项错位、题干重复解析出来的数据别急着写库先过一遍校验。答案不在选项里、单选题却有两个答案、判断题答案是「正确」而不是T这些错误在人工出题时非常常见进了库就是线上事故。import re, hashlib def norm_stem(s: str) - str: 题干归一化去掉题号、括号、空白和全部标点只留文字 s re.sub(r^\s*(?:第)?\d{1,4}\s*[.、)]\s*, , s) s re.sub(r[\s()【】\[\]。,、;:?!], , s) return s def stem_hash(s: str) - str: return hashlib.md5(norm_stem(s).encode(utf-8)).hexdigest() JUDGE_MAP {对: T, 正确: T, √: T, 错: F, 错误: F, ×: F} def check(questions): errors, seen [], {} for q in questions: no q[no] labels [o[label] for o in q[options]] if q[qtype] judge: q[answer] JUDGE_MAP.get(q[raw_answer], q[raw_answer]) if q[answer] not in (T, F): errors.append((no, f判断题答案未归一化: {q[raw_answer]})) elif q[qtype] single: if len(labels) 2: errors.append((no, 选项少于 2 个)) elif len(q[raw_answer].strip()) ! 1: errors.append((no, f单选答案长度异常: {q[raw_answer]})) elif q[raw_answer].strip() not in labels: errors.append((no, 答案不在选项列表中)) elif q[qtype] multiple: if len(q[raw_answer].strip()) 2: errors.append((no, 多选题只有一个答案)) if not q.get(explanation, ).strip(): errors.append((no, 缺少解析)) h stem_hash(q[stem]) if h in seen: errors.append((no, f题干与第 {seen[h]} 题重复)) seen[h] no return errorsnorm_stem是整套去重的核心把「1. 关于无菌器械储存的说法正确的是」和「2、关于无菌器械储存的说法正确的是」归一到同一串字符才能判出这是同一道题。JUDGE_MAP处理的是判断题口径不统一的问题库里统一存T/F前端展示时再转回「正确/错误」。校验结果建议输出成一份 CSV交给出题人改而不是让程序自动猜。答案不在选项里这种错自动修反而会掩盖原始文档的问题。3.3 批量写库与幂等导入入库用executemany加唯一索引即可重复跑不会产生脏数据import sqlite3, hashlib def import_questions(questions, db_pathbank.db, year2025): conn sqlite3.connect(db_path) cur conn.cursor() ok dup 0 for q in questions: h hashlib.md5(norm_stem(q[stem]).encode(utf-8)).hexdigest() try: cur.execute( INSERT INTO question(year,qtype,stem,answer,explanation,knowledge,difficulty,source_hash) VALUES(?,?,?,?,?,?,?,?), (year, q[qtype], q[stem], q[answer].strip().upper(), q.get(explanation, ), q.get(knowledge, ), q.get(difficulty, 2), h)) except sqlite3.IntegrityError: dup 1 continue qid cur.lastrowid for i, o in enumerate(q[options]): cur.execute(INSERT INTO question_option(question_id,label,content,sort_no) VALUES(?,?,?,?), (qid, o[label], o[content], i)) ok 1 conn.commit() print(f入库 {ok} 题跳过重复 {dup} 题)知识点字段建议在这一步人工补把题目按「无菌」「储存运输」「不良事件监测」「法规」四类打标可以先用关键词规则粗分题干里出现「灭菌」「无菌」归无菌出现「报告」「不良事件」归不良事件再让人过一遍。组卷能不能按知识点配比全靠这一步的质量。4. 组卷、判分与导出把题库跑成一套答题流程4.1 按知识点配比分层抽样组卷随机抽题最容易出的问题是卷子结构失衡——某次抽出来全是法规题下一次全是无菌题。固定的做法是写一份组卷蓝图声明每种题型在各知识点上抽多少道再按配额抽样。这样每次生成的卷子难度和覆盖面都稳定也方便向检查方解释「考了什么、占比多少」。题型无菌储存运输不良事件法规单题分值单选86472 分多选32324 分判断33222 分合计1411911100 分import random BLUEPRINT { single: {无菌: 8, 储存运输: 6, 不良事件: 4, 法规: 7}, multiple: {无菌: 3, 储存运输: 2, 不良事件: 3, 法规: 2}, judge: {无菌: 3, 储存运输: 3, 不良事件: 2, 法规: 2}, } def build_paper(bank, blueprintBLUEPRINT, seedNone): rnd random.Random(seed) # 传固定 seed可复现出同一份卷子便于复盘 picked [] for qtype, quota in blueprint.items(): for knowledge, n in quota.items(): pool [q for q in bank if q[qtype] qtype and q[knowledge] knowledge and q[status] 1] if len(pool) n: raise ValueError(f{qtype}/{knowledge} 题量不足需要 {n}实际 {len(pool)}) picked rnd.sample(pool, n) rnd.shuffle(picked) # 打乱顺序避免同类题扎堆出现 return pickedspool那句raise别省题量不足时报错比悄悄少出几道题好得多。seed参数在复现问题时很有用同一个人对成绩有疑问用当时的 seed 能把卷子原样重建出来。4.2 判分逻辑与多选题的部分给分判断题和单选题判分没争议多选题是坑最多的地方漏选给不给分、错选给不给分、给了多少分考勤表上要能解释清楚。常见规则是「漏选按比例给分、错选或多选零分」代码里一行就能表达def grade(q, user_answer: str): std .join(sorted(q[answer].replace(,, ).replace(, ).upper())) got .join(sorted((user_answer or ).replace(,, ).replace(, ).upper())) if q[qtype] in (single, judge): return 2.0 if got std else 0.0 if q[qtype] multiple: if got std: return 4.0 if got and set(got) set(std): # 真子集漏选按选中比例给分 return round(4.0 * len(got) / len(std), 1) return 0.0 # 错选、多选一律零分 return None # 填空、简答返回 None转人工set(got) set(std)用的是真子集判断能同时排除「答案完全一致」和「选了无关选项」两种情况。填空和简答直接返回None在answer_record里把score_got留空由人工补录不要试图用字符串相似度自动给分——器械培训的简答题经常涉及具体时限和流程模糊匹配给错分比不给分更麻烦。4.3 反向导出含答案版和考生版培训结束后通常还要交一份 Word 归档或者打印出来做纸质补考。用 python-docx 反向生成两个版本只差一个开关from docx import Document from docx.shared import Pt def export_docx(paper, path, with_answerTrue, title2025医疗器械培训试题): doc Document() style doc.styles[Normal] style.font.name 宋体 style.font.size Pt(10.5) doc.add_heading(title (含答案 if with_answer else 考生版), level1) for i, q in enumerate(paper, 1): doc.add_paragraph(f{i}. {q[stem]}) for o in sorted(q[options], keylambda x: x[label]): doc.add_paragraph(f {o[label]}. {o[content]}) if with_answer: doc.add_paragraph(f答案{q[answer]}) if q.get(explanation): doc.add_paragraph(f解析{q[explanation]}) doc.save(path) export_docx(paper, 2025医疗器械培训试题(含答案).docx, with_answerTrue) export_docx(paper, 2025医疗器械培训试题(考生版).docx, with_answerFalse)打印版和屏幕版要分开设字号Pt(10.5)是五号字适合屏幕和双面打印如果卷子要印在 A4 上给一线人员用正文调到Pt(12)、选项缩进两格更合适。导出文件名建议带上时间戳避免覆盖上一版。5. 题库长期可用相似题检测与培训留痕5.1 用字符 n-gram 抓出改头换面的重复题题干哈希只能抓完全相同的题抓不到「关于无菌器械储存的说法正确的是」和「下列对于无菌器械储存的描述哪一项正确」这种换皮题。中文短文本用词级分词效果一般改成字符 n-gram 加 TF-IDF 更稳两三个字的组合能捕捉到「无菌器械」「储存条件」这类固定搭配import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity stems [norm_stem(q[stem]) for q in bank] vec TfidfVectorizer(analyzerchar, ngram_range(2, 4), min_df1) X vec.fit_transform(stems) sim cosine_similarity(X) np.fill_diagonal(sim, 0) # 自己和自己相似度置 0否则全是 1 for i, j in np.argwhere(sim 0.85): if i j: print(f疑似重复: 第{i}题 / 第{j}题 相似度 {sim[i][j]:.2f})ngram_range(2, 4)是中文题干的经验区间只取 1 会退化成单字匹配、噪声极大取到 5 以上则短题干几乎匹配不到任何东西。阈值0.85可以先跑一轮人工看结果再调建议把疑似重复对导出成表格让出题人决定是合并还是保留——不同年份的题即使相似有时也是故意保留的复训题。5.2 培训档案最少要留哪些字段检查培训记录时看的不是试卷本身而是「谁、什么时候、考了什么、得了多少分、有没有补考」。exam_record和answer_record两张表之外至少要补上这几个字段否则回溯时要靠人工翻日志字段存放位置说明姓名 / 工号 / 部门人员表答题时冗余落库人员调岗后旧记录仍要能对上当时的部门试卷快照paper_id 组卷 seed题目后来改了仍能还原当时考的原卷开始与提交时间exam_record判断是否本人限时完成逐题作答answer_record错题本、知识点正确率的数据源培训主题与依据培训批次表注明对应哪份内部制度或操作规范一个容易被忽略的细节是题目要软删除而不是硬删除。用status0停用旧题历史answer_record里的question_id才不会有悬空引用真删了题两年前的考试记录就变成一堆看不懂的 ID。每年更新题库时新增题入question表、旧题改statussource_hash唯一索引会自动挡住重复导入这套结构可以一直用下去不用每年重排一遍 Word 版式。本文还有配套的精品资源点击获取

关于恒美微站

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

快速链接

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

服务项目

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

联系方式

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

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