docx题库解析到竞赛系统:医院感染知识竞赛的自动化方案
2026/9/20 3:55:14 网站建设 项目流程

简介:医院感染应知应会知识竞赛必刷题库及答案是一份面向临床医务人员、院感专兼职人员及竞赛选手的备考资源,围绕医院感染预防与控制的核心知识,精选高频考点并以问答形式呈现。整个资源仅含1个docx文档,压缩包约25KB,页面内容约15页,收录了25道典型简答题及参考答案,覆盖职业暴露处理程序、医院感染暴发与疑似暴发的识别、灭菌与消毒概念、手卫生五个重要指征、多重耐药菌(MDRO)定义、艾滋病病毒职业暴露处置、围手术期抗菌药物预防使用、个人防护用品选择、环境卫生学监测项目以及特殊器械消毒等专题。题目后紧跟答案解析,要点明确,便于医务人员利用碎片时间自测、背诵与考前强化,既能满足知识竞赛备赛需要,也可用于院内感染防控培训与日常考核。已有73人学习浏览这份资料,适合作为感控知识学习与竞赛冲刺的实用手册。

1. 当一份「医院感染应知应会知识竞赛必刷题库及答案.docx」发到手里,先别急着刷题

每年院感宣传月,各科室都要抽人参加知识竞赛,医务科最后甩过来一份 Word 文档,标题就叫「医院感染应知应会知识竞赛必刷题库及答案.docx」。这文件看起来是一份资料,但对 IT 岗的人来说,它其实是一份待清洗的数据源:里面少则几百、多则上千道题,题型从单选、多选到判断,章节从手卫生、消毒隔离到多重耐药菌,格式还经常不统一——有的答案是括号里的,有的集中在文末,有的选项和题干挤在一行里。如果你只把它当文档转发,那竞赛抽题、赛前模拟、成绩统计全得靠人工,而只要你愿意拆开 docx 的结构,这个文件就能变成一个可以检索、可以自动组卷、可以按科室追踪正确率的题库系统。这篇博文就顺着一条最接地气的路径走:docx 解析、题库建模、组卷算法、练习部署,最后再聊几个容易翻车的细节。

2. 先把 docx 拆开:解析医院感染题库的格式边界

2.1 为什么不能把 docx 当纯文本读

医院感染题库的 docx 文件通常由 Word 排好版,题干、选项、答案混在段落和表格里。直接pip install python-docx读取段落文字是最快的起步方案,但要清楚它的边界:python-docx 只能拿到word/document.xml里的文本流,拿不到批注、修订痕迹里的内容,也不会自动帮你区分「题干」和「选项」。如果题库里有多选题答案写成ACD的形式,或者判断题为「对 / 错」而不是「A / B」,那解析逻辑就要分两种分支处理。很多 IT 人员第一步就栽在「以为答案一定出现在题干下一行」这个假设上。

从内存结构上看,docx 是一个 zip 压缩包,document.xml是最主要的正文文件,段落用<w:p>表示,表格用<w:tbl>。python-docx 把段落封装成Paragraph,把表格封装成Table。你要做的第一件事,就是先遍历所有段落,打印出一份「段落清单」,看看题目到底是怎么组织的。

from docx import Document doc = Document("医院感染应知应会知识竞赛必刷题库及答案.docx") for i, para in enumerate(doc.paragraphs[:30]): text = para.text.strip() if text: print(i, text[:50])

这段代码只做一件事:将文档前 30 个非空段落输出到控制台,每段只显示前 50 个字符,用于快速判断文档的排版规律。参数doc.paragraphs返回文档中的所有段落对象,enumerate提供段落索引,打印前 30 段是为了避免命令行刷屏。如果输出中看不到「1. 关于洗手,下列哪项说法正确?」这类题干,说明题目可能放在表格里,需要用doc.tables读取。格式判断是后续一切解析的前提,先花十分钟做这一步,能省掉后面反复调正则的时间。

2.2 用规则模板识别题干、选项、答案三类要素

医院感染题库最常见的格式是「题干 + 选项 + 答案」三行式,但现实中的文档往往会在题干前加「1、」「1.」「第 1 题」等序号,选项可能缩进不一,答案可能是「答案:A」也可能是「【答案】ACD」。我一般会先把每段文本规范化,再做三类匹配:

  • 题干:以数字序号开头,后续文字长度超过 10 个字符,且下一行存在 A. 形式的选项。
  • 选项:以A.B.C.D.或全角A.开头,选项文字以中文字符为主。
  • 答案:包含「答案」「参考答案」关键词,后跟一个或多个字母,或「正确」「错误」的判断题表述。

匹配规则用正则实现,涉及中文全角符号时要把\uff0e等全角形式纳入模式。

import re pattern_ti = re.compile(r'^第?(\d+)[题、..]?\s*(.*?)$') pattern_option = re.compile(r'^([A-Ha-h])[..、\s]\s*(.*)$') pattern_answer = re.compile(r'(答案|参考答案)[::]\s*([A-Ha-h]+|对|错|正确|错误)') for para in doc.paragraphs: text = para.text.strip() if not text: continue m_ti = pattern_ti.match(text) if m_ti and len(text) > 10: print("题干:", text) continue m_opt = pattern_option.match(text) if m_opt: print("选项:", m_opt.group(1), m_opt.group(2)) continue m_ans = pattern_answer.search(text) if m_ans: print("答案:", m_ans.group(2))

这里对每个段落先做题干匹配,再做选项匹配,最后做答案匹配。pattern_ti中的第?(\d+)[题、..]?可以兼容「第 1 题」和「1、」两种序号写法;pattern_option中的[A-Ha-h]覆盖到 H,因为医院感染题库偶尔有 8 个选项的做法;pattern_answer匹配「答案:A」和「答案:对」两种形态。实际运行中经常出现「答案」和选项之间隔了一个空行,所以建议把前 30 个段落的打印结果和这里的匹配结果做人工对照,微调正则的细节。这个步骤没有捷径,格式越乱,正则分支越多,但用规则模板而不是逐题人工录入,始终是文档解析的正确方向。

2.3 表格型题库的解析:python-docx 操作 table

部分医院感染题库的组织方式不是段落而是 Word 表格,表格结构通常是「序号 | 题目 | 选项 | 答案」或者「题目 | A | B | C | D | 答案」。这时候用doc.tables遍历更可靠,不需要正则匹配题干序号。

for table in doc.tables: for row in table.rows: cells = [cell.text.strip() for cell in row.cells] if len(cells) >= 3: print(" | ".join(cells)) # question = cells[1] # answer = cells[-1]

row.cells返回该行的所有单元格文本,cells[-1]取最后一个单元格,一般就是答案列。要注意表格中可能存在合并单元格,导致row.cells返回的列表里同一单元格出现多次,所以判断条件len(cells) >= 3是保险条款。解析完表格数据后,把段落解析和表格解析的结果分别保存,推荐统一转成 JSON 格式的中间文件,后续建模和其他程序读取都方便。这一步不做,等到写组卷代码时还要回头跟 Word 打交道,那才叫痛苦。

3. 把题库做成结构化数据:字段设计、导入与检索

3.1 医院感染题库的表结构设计

无论解析得到多少题,最终都要落到一个稳定的存储结构里。常见做法是选用 SQLite,因为轻量、无需部署服务端、单文件可迁移。医院感染题库的题目需要保留题型、题干内容、选项、答案、章节标签等字段。表结构可以参考下面这个设计。

CREATE TABLE IF NOT EXISTS questions ( id INTEGER PRIMARY KEY AUTOINCREMENT, qtype TEXT NOT NULL DEFAULT 'single', -- single / multiple / judge chapter TEXT NOT NULL DEFAULT 'general', -- hand_hygiene / disinfection / isolation ... stem TEXT NOT NULL, options TEXT, -- JSON格式的选项列表 answer TEXT NOT NULL, difficulty INTEGER DEFAULT 2, -- 1 简单 / 2 中等 / 3 困难 source TEXT DEFAULT 'docx' );

qtype字段保存题型,chapter保存章节名称,用英文或拼音都以自己团队的习惯为准。options字段用 JSON 字符串存所有选项,比如["A. 洗手", "B. 消毒"],这样在组卷时可以按题型区分处理;answer字段存储标准答案字符,单选是"A",多选是"ACD",判断是"对""错"。为什么options不单独建一张关联表?因为竞赛题库的规模通常在 1000 题以内,JSON 字段在查询、导出场景下完全够用,建关联表反而增加解析代码的复杂度。difficulty字段是给组卷算法用的,没有标注难度的题默认填2

3.2 从解析结果导入数据的完整脚本

前面解析得到的 JSON 中间文件,这里接着导入 SQLite 数据库。

import json import sqlite3 with open("questions_parsed.json", "r", encoding="utf-8") as f: items = json.load(f) conn = sqlite3.connect("hai_quiz.db") cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS questions ( id INTEGER PRIMARY KEY AUTOINCREMENT, qtype TEXT NOT NULL DEFAULT 'single', chapter TEXT NOT NULL DEFAULT 'general', stem TEXT NOT NULL, options TEXT, answer TEXT NOT NULL, difficulty INTEGER DEFAULT 2 ) """) for item in items: cursor.execute( "INSERT INTO questions (qtype, chapter, stem, options, answer, difficulty) " "VALUES (?, ?, ?, ?, ?, ?)", ( item.get("qtype", "single"), item.get("chapter", "general"), item["stem"], json.dumps(item.get("options", []), ensure_ascii=False), item["answer"], item.get("difficulty", 2), ), ) conn.commit() conn.close()

这里的参数化 SQL 用四个问号占位,对应后面传给execute的元组。item.get("qtype", "single")表示如果 JSON 里没有qtype字段,默认采用single类型,这是一个非常实用的兜底写法。字段options序列化时指定ensure_ascii=False,保证中文选项存入数据库时依然可读。

导入完成后,务必执行一条计数查询验证数据量:

SELECT qtype, COUNT(*), chapter FROM questions GROUP BY qtype, chapter;

输出结果应当与手头题库的目测数量一致,比如单选 320 题、多选 180 题、判断 100 题。如果数量对不上,回到解析脚本调整正则或表格分列逻辑,这是整个项目中最值得反复校验的环节。

3.3 按章节关键词检索:SQL LIKE 的效率边界

结构化之后,最常见的检索需求就是「把所有关于手卫生的题目捞出来」。由于章节标签chapter已经单独建档,直接精确过滤即可:

SELECT * FROM questions WHERE chapter = 'hand_hygiene';

但如果原始文档没有章节标注,只能通过题干关键词过滤,SQL 的LIKE在 1000 行数据量下不会有性能问题,最多 20 毫秒返回结果。要注意的是LIKE的中文匹配在 SQLite 中默认区分大小写,不过中文字符没有大小写概念,所以影响不大。这里有个容易踩的坑:题目中「隔离」既可能出现在「隔离技术」章,也可能出现在「多重耐药菌隔离措施」题干中,用LIKE '%隔离%'会把两边都捞出来。处理方式是先按关键词粗筛,再做人工复核,粗筛代码用参数拼接防止注入问题:

import sqlite3 conn = sqlite3.connect("hai_quiz.db") cursor = conn.cursor() keyword = "消毒" cursor.execute( "SELECT id, stem FROM questions WHERE stem LIKE ?", ("%" + keyword + "%",), ) rows = cursor.fetchall() for r in rows[:20]: print(r[0], r[1][:60])

这里的"%" + keyword + "%"在参数中拼接通配符。不要把 keyword 直接拼进 SQL 字符串,否则一旦题目文本中含有单引号,轻则报错,重则产生注入。fetchall()返回全部匹配行,加上切片[:20]只打印前 20 条,避免终端刷屏。

4. 竞赛不是刷题是组卷:抽取算法、防偏防重、实时练习

4.1 按章节比例抽题的组卷策略

知识竞赛通常有固定的试卷结构:单选 20 道、多选 10 道、判断 10 道,且各章节题目数量按比例分布。组卷的核心逻辑不是随机抽样,而是「分层抽样」。把题目先按chapter+qtype分组,再从每个分组内抽固定数量的题目。这样说起来简单,但实现时要处理「某章节某题型题量不够」的降级策略。

import random def compose_paper(conn, plan): """ plan: [(chapter, qtype, count), ...] """ cursor = conn.cursor() selected = [] for chapter, qtype, count in plan: cursor.execute( "SELECT id, stem, options, answer FROM questions " "WHERE chapter = ? AND qtype = ? ORDER BY RANDOM() LIMIT ?", (chapter, qtype, count), ) rows = cursor.fetchall() if len(rows) < count: print(f"警告: {chapter}-{qtype} 只有 {len(rows)} 题,目标 {count} 题") # 降级:从全部同题型中补足 cursor.execute( "SELECT id, stem, options, answer FROM questions " "WHERE qtype = ? ORDER BY RANDOM() LIMIT ?", (qtype, count - len(rows)), ) rows.extend(cursor.fetchall()) selected.extend(rows) return selected

这段代码的组卷策略值得拆开说。ORDER BY RANDOM() LIMIT ?是 SQLite 里的随机抽样语法,在数据量小于万行时性能没问题。plan参数是元组列表,调用方只需描述「哪个章节的哪类题目抽几题」,组卷函数不自作主张。第二个值得注意的点是降级策略:当len(rows) < count时,打印警告并从全部同题型题目中随机补足缺额,保证试卷题量恒定。竞赛场景中题量不足比题目重复更容易让组织者不满,宁可降低章节覆盖也必须保证足够题数。

4.2 练习模式:错题优先与答案屏蔽

知识竞赛备战用的练习模式和正式组卷模式不同:练习模式要求即时判分、错题回顾,而且不能一上来就看到答案,否则记忆效果差。这里要一个能交互的循环:

def practice(conn, limit=20): cursor = conn.cursor() cursor.execute("SELECT id, stem, options, qtype, answer ORDER BY RANDOM() LIMIT ?", (limit,)) # 注意:必须从完整表结构化字段中查询,实际脚本需要调整

遇到这种粘贴错误,说明你对 SQLite 的 SELECT 必须带 FROM。修正后的写法是:

def practice(conn, limit=20): cursor = conn.cursor() cursor.execute( "SELECT id, stem, options, qtype, answer FROM questions " "ORDER BY RANDOM() LIMIT ?", (limit,), ) rows = cursor.fetchall() for qid, stem, options, qtype, answer in rows: print(stem) opts = json.loads(options) if options else [] for opt in opts: print(opt) user_input = input("你的答案: ").strip().upper() if user_input == answer.upper(): print("正确\n") else: print(f"错误, 正确答案是 {answer}\n")

这段代码里有三个关键设计。第一个是选项列表从 JSON 还原时判断if options,避免空options直接传给json.loads抛异常。第二个是用户输入统一转大写再比较,防止大小写差异导致误判。第三个是输出答案时不附带解释,等用户答完一轮后再提供查看解析的选项,避免偷看答案。练习完可以把错题写入独立的mistakes表,后续刷题只刷错题,这是提高备考效率的常见做法。

mistakes表结构很简单:question_idwrong_countlast_wrong_at三个字段,每次答错wrong_count + 1,每次练习优先查询这张表和主表JOIN来抽取错题。SQL 写法是WHERE m.wrong_count > 0 ORDER BY m.last_wrong_at ASC,越久没做对的题越靠前。这个逻辑不算高深,但应付院感竞赛完全足够。

4.3 防重复出题的会话内去重技巧

竞赛训练最忌讳的问题:同一场练习中连续出现两道相同题目。防止的办法有两种,一种是在内存里维护一个已出题 ID 集合,另一种是把题库临时表加一个used标志位。小数据量下用内存集合就够了:

seen_ids = set() def fetch_new(cursor, limit, seen_ids): placeholders = ",".join(["?"] * len(seen_ids)) if seen_ids else "''" sql = ( f"SELECT id, stem, options, answer FROM questions " f"WHERE id NOT IN ({placeholders}) ORDER BY RANDOM() LIMIT ?" ) params = list(seen_ids) + [limit] cursor.execute(sql, params) return cursor.fetchall()

这个写法要注意,seen_ids为空时placeholders需要兜底为'',即NOT IN (''),因为 SQLite 不允许NOT IN ()的空列表语法。参数顺序是先填充排除 ID,再填LIMIT的数值。如果seen_ids长度超过几百,NOT IN的查询计划可能变慢,但这个规模在院感题库场景下根本不会触发。数据库层面加一个标记点status字段也可以,但需要每次练习开始前批量重置,不如内存容器方便。

5. 把题库搬上浏览器:用 Flask 快速做院里可用的竞赛练习系统

5.1 单文件 Flask 应用搭建流程

到了这一步,SQLite 里的题库已经可以命令行刷题了,但给医务科同事用,命令行显然不现实。最常见、最省事的做法是用 Flask 写一个单文件 Web 应用,把前面几章的函数挪进路由里。先装依赖:

pip install flask python-docx

然后创建app.py,核心路由只需要三个:/展示题目、/answer提交判分、/result显示成绩。下面是最小可运行版本:

from flask import Flask, request, render_template_string, session import sqlite3 app = Flask(__name__) app.secret_key = "your-secret-key" def get_db(): conn = sqlite3.connect("hai_quiz.db") conn.row_factory = sqlite3.Row return conn @app.route("/") def index(): conn = get_db() cursor = conn.cursor() cursor.execute("SELECT * FROM questions ORDER BY RANDOM() LIMIT 20") questions = cursor.fetchall() conn.close() session["qids"] = [q["id"] for q in questions] return render_template_string(PAGE_TEMPLATE, questions=questions) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)

注意几点。session["qids"]存储本场练习的题目 ID 列表,是为了在/answer路由里判断用户提交的答案对应哪道题。conn.row_factory = sqlite3.Row可以把查询结果按字段名访问,模板中写q["stem"]而不是q[0],代码可读性高得多。host="0.0.0.0"允许局域网内其他电脑访问,这样不用每台机器装 Python。

5.2 前端页面的模板要点

Flask 的render_template_string可以直接传 HTML 字符串,不需要单独建模板文件。HTML 模板里用循环输出每道题的题干、选项和单选框,判断题单独渲染成「对」和「错」两个 radio。对医院感染题库而言,多选题是最容易让前端出错的点:每个选项都要是一个独立 checkbox,而不是一组 radio。提交后需要在前端先做一次空值校验,防止用户一道题没选就提交导致后端报 KeyError。前端 JS 的校验代码不复杂,但必不可少:

function validateForm() { const groups = document.querySelectorAll(".question-group"); for (let g of groups) { const checked = g.querySelectorAll("input:checked"); if (checked.length === 0) { alert("请完成所有题目后再提交"); return false; } } return true; }

querySelectorAll(".question-group")选中所有题目的容器,遍历时只要有一组没有任何勾选,就阻止提交并弹窗提示。这种校验在后端也要做一次,因为浏览器校验只防正常用户,防不了直接发 POST 请求的同事。后端校验加上这一句就够了:

if not request.form.getlist(f"q_{qid}"): return "该题未作答", 400

getlist是 Flask 中获取多选框值的标准方法,单选和判断用request.form.get,多选必须用getlist,拿到的是一个列表,再和数据库中的answer字段逐字符比较:

submitted = set(request.form.getlist(f"q_{qid}")) correct = set(q["answer"]) # 答案是 "ACD" if submitted == correct: score += 1

这里把字符串"ACD"转成集合{"A", "C", "D"}再比较,好处是不用关心选项顺序,用户选"CAD"也能判对。如果是单选题,这个比较逻辑依然成立,因为集合只有一个元素。

5.3 院内局域网部署与数据安全注意

Flask 自带服务器只适合内部小规模使用,院内竞赛几十人同时访问不会有压力,但有两个部署细节值得留意。第一,app.run(debug=True)在对外服务时必须改为debug=False,否则调试器会暴露堆栈信息。第二,SQLite 文件建议放在和app.py同一目录,git 提交时记得把数据库文件排除掉,避免题目答案外泄。第三,如果院内对端口有管控,可以改用 80 端口或 8080 端口,但host="0.0.0.0"配合port=8080是安全边界更合理的默认组合。如果想让题库内容不可被随意下载,可以在路由上增加一个简单的 token 参数,但内部工具做到这个程度已经算到位。院感竞赛结束之后,同一个系统可以换一份 docx 重新跑一遍解析流程,切换题库的成本是零,这比每年手工整理试卷可靠得多。

整个链路从 docx 到 Web 练习系统,核心代码不超过 300 行,但每一步都踩过格式不统一的坑。最值得记住的经验是:解析阶段不要追求一版正则通吃全局,先做段落打印、再迭代规则;建模阶段不要过度设计表结构,SQLite 单表 + JSON 选项字段足够;组卷阶段宁可降级补题也不要让试卷缺题;部署阶段务必关 debug、注意局域网端口可达性。按这个顺序走,任何一份「医院感染应知应会知识竞赛必刷题库及答案.docx」摆到面前,都能在一个工作日内变成一个可以真刀真枪模拟竞赛的内部系统。

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

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

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

立即咨询