ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python解析docx题库:从非结构化文档到SQLite全文检索与去重校验

2026/9/19 12:02:55 拓冰建站 浏览量
Python解析docx题库:从非结构化文档到SQLite全文检索与去重校验 简介这份2025年公共基础知识题库及参考答案完整版面向公务员、事业单位及各类公职考试备考人群聚焦公共基础知识高频考点帮助考生在冲刺阶段系统刷题、查漏补缺。资源以单选题为主覆盖意识定义与分类、社会保障制度目的、我国人口占世界比重、社会主义改革性质、公文种类区分、科学发展观根本着眼点、辩证唯物主义与历史唯物主义学习意义、偶然性在世界历史中的作用、行政处罚种类以及案件管辖等核心模块每题均附参考答案与解析便于理解命题思路与法条依据。资源包共1个docx文件约54KB内容按题目与解析顺序编排结构紧凑适合打印或移动端随时翻阅。目前已有153人学习下载可作为考前强化记忆与错题复盘的实用工具尤其适合需要快速梳理知识框架、巩固易混考点的考生使用。1. 从一份 docx 题库说起为什么“完整版”反而最难维护很多人拿到“2025年公共基础知识题库及参考答案完整版.docx”这类文件第一反应是直接打开背题。但如果你是要把它做成一个能长期用的刷题工具、一个内部培训系统或者一个可检索的错题本真正的难点根本不在题目本身而在于这份 docx 的结构题干、选项、答案、解析往往混在段落、表格、文本框里格式还经常不统一。直接复制粘贴到数据库十有八九会出现选项串行、答案错位、解析丢失。这类题库文档的典型特征是题号用“1.”“第1题”“【1】”混着写选项用“A.”“A、”“A”混着写答案有的在题干后面有的单独成段有的藏在表格最后一列。所以真正要解决的问题是把一份非结构化的 docx 变成结构化数据再落到可查询、可校验、可导出的形态。适合谁适合要做内部考试系统、要做题库小程序、要批量整理历年真题的开发和运维同学。下面按“先解析、再入库、再校验、最后做检索和导出”的顺序讲清楚。2. 用 python-docx 解析公共基础知识题库 docx 的完整流程2.1 先看清 docx 的三种常见排版结构在动手写代码前先用python-docx把文档的段落和表格都打印出来判断它属于哪种结构。常见的有三类纯段落型题干、选项、答案各占一段、表格型每行一题列分别是题干、选项、答案、解析、混合型题干在段落选项在表格。判断方法很简单统计doc.paragraphs和doc.tables的数量再抽样打印前 30 段文本。from docx import Document doc Document(2025年公共基础知识题库及参考答案完整版.docx) print(段落数:, len(doc.paragraphs)) print(表格数:, len(doc.tables)) # 抽样看前 30 段判断题号、选项、答案的书写习惯 for i, p in enumerate(doc.paragraphs[:30]): text p.text.strip() if text: print(i, repr(text[:60]))逻辑说明doc.paragraphs按文档流顺序返回所有段落doc.tables返回所有表格。先看数量级如果表格数接近题目数基本就是表格型如果表格数为 0就是纯段落型。参数上p.text已经去掉了大部分格式但文本框里的内容不会出现在paragraphs里这是第一个坑。提示如果打印出来的段落数远小于题目数说明题目大量放在文本框或表格里需要额外遍历doc.element.body下的w:txbxContent节点。2.2 用正则把题干、选项、答案、解析拆开纯段落型最考验正则。核心思路是先按题号切分大块再在每块里用选项标记切分。下面这段代码覆盖了“1.”“第1题”“【1】”三种题号以及“A.”“A、”“A”三种选项写法。import re # 题号1. / 第1题 / 【1】 Q_RE re.compile(r^\s*(?:第\s*)?(\d)\s*[\.、题\]】]\s*) # 选项A. / A、 / A / A OPT_RE re.compile(r^\s*[(]?([A-Da-d])[)、\.]\s*) # 答案行答案A / 参考答案 A / 【答案】A ANS_RE re.compile(r^\s*(?:参考)?答案[:】\s]*([A-Da-d])) # 解析行解析xxx / 【解析】xxx EXP_RE re.compile(r^\s*(?:解析|【解析】)[:\s]*(.)) def parse_paragraphs(paragraphs): questions, cur [], None for p in paragraphs: t p.text.strip() if not t: continue m Q_RE.match(t) if m: if cur: questions.append(cur) cur {num: int(m.group(1)), stem: Q_RE.sub(, t), options: {}, answer: , explain: } continue if cur is None: continue mo OPT_RE.match(t) if mo: cur[options][mo.group(1).upper()] OPT_RE.sub(, t) continue ma ANS_RE.match(t) if ma: cur[answer] ma.group(1).upper() continue me EXP_RE.match(t) if me: cur[explain] me.group(1) continue # 没匹配上的追加到题干或解析 if cur[answer]: cur[explain] t else: cur[stem] t if cur: questions.append(cur) return questions逻辑说明Q_RE负责识别题号并开启新题OPT_RE把选项字母和内容分离ANS_RE和EXP_RE分别抓答案和解析。参数上[A-Da-d]限定四个选项如果有多选题A-E要改成[A-Ea-e]。ANS_RE里的([A-Da-d])支持多选答案如“ABD”。没匹配上的行按“答案是否已出现”决定追加到题干还是解析这是处理换行题干的关键。2.3 表格型题库的列映射与合并单元格处理表格型相对省事但坑在合并单元格。python-docx里合并单元格的cell.text会重复返回同一内容导致一题被读多次。稳妥做法是按行读取用第一列是否为空来判断是否是新题。def parse_tables(tables): questions [] for tb in tables: for row in tb.rows: cells [c.text.strip() for c in row.cells] # 去重相邻重复合并单元格特征 dedup [] for c in cells: if not dedup or dedup[-1] ! c: dedup.append(c) if len(dedup) 3: continue questions.append({ stem: dedup[0], options: dedup[1], answer: dedup[2], explain: dedup[3] if len(dedup) 3 else }) return questions逻辑说明row.cells对合并单元格会返回多个相同对象dedup去掉相邻重复后列数才接近真实列数。参数上len(dedup) 3用来跳过表头或空行。如果选项在一列里用换行分隔还需要对dedup[1]再按\n切一次。2.4 解析结果落库前的字段规范不管哪种来源最后都要统一成同一套字段否则后面查询和导出会乱。建议字段如下字段名类型说明idint自增主键qnumint原题号stemtext题干optionsjson选项如 {A:...,B:...}answervarchar(8)正确答案多选如 ABDexplaintext解析categoryvarchar(32)分类如法律、政治、经济sourcevarchar(64)来源文件options用 JSON 存查询时用JSON_EXTRACT或应用层解析都方便。category建议在解析阶段用关键词打标比如题干含“宪法”“刑法”归法律含“GDP”“通胀”归经济后面检索会用到。3. 把题库 docx 导入 SQLite 并做全文检索的实操3.1 建表与批量插入的最小可用脚本SQLite 单文件、零配置适合本地题库工具。下面脚本把上一节解析出的questions列表写进库并建 FTS5 全文索引。import sqlite3, json conn sqlite3.connect(tiku.db) cur conn.cursor() cur.executescript( CREATE TABLE IF NOT EXISTS questions( id INTEGER PRIMARY KEY AUTOINCREMENT, qnum INTEGER, stem TEXT, options TEXT, answer TEXT, explain TEXT, category TEXT, source TEXT ); CREATE VIRTUAL TABLE IF NOT EXISTS q_fts USING fts5( stem, explain, contentquestions, content_rowidid ); ) def save(questions, source): for q in questions: cur.execute( INSERT INTO questions(qnum,stem,options,answer,explain,category,source) VALUES(?,?,?,?,?,?,?), (q.get(num), q[stem], json.dumps(q.get(options, {}), ensure_asciiFalse), q.get(answer, ), q.get(explain, ), q.get(category, ), source)) conn.commit() # 同步 FTS 索引 cur.execute(INSERT INTO q_fts(q_fts) VALUES(rebuild)) conn.commit()逻辑说明contentquestions让 FTS5 做外部内容表不重复存正文省空间。rebuild命令在批量插入后重建索引比逐条触发触发器快得多。参数上ensure_asciiFalse保证中文选项不被转义成\uXXXX方便直接查看。3.2 用 FTS5 做题干和解析的联合检索建好索引后检索就变成一条 SQL。比如查“行政复议”相关题目同时命中题干或解析SELECT q.id, q.qnum, q.stem, q.answer FROM q_fts f JOIN questions q ON q.id f.rowid WHERE q_fts MATCH 行政复议 ORDER BY rank LIMIT 20;逻辑说明MATCH走 FTS5 索引rank是内置的相关性排序。参数上如果要查多个词用行政复议 OR 行政诉讼要精确短语用行政复议 。注意 FTS5 默认对中文按字切分短词检索没问题长句建议配合tokenizeunicode61或外部分词。3.3 按分类和答案状态筛选的查询写法实际刷题时经常要“只看法律类、且我答错的题”。这需要一张错题表配合CREATE TABLE IF NOT EXISTS wrong_book( qid INTEGER PRIMARY KEY, wrong_count INTEGER DEFAULT 1, last_wrong_at TEXT ); -- 查法律类错题按错误次数倒序 SELECT q.stem, q.answer, w.wrong_count FROM wrong_book w JOIN questions q ON q.id w.qid WHERE q.category 法律 ORDER BY w.wrong_count DESC;逻辑说明wrong_book用qid做主键答错一次wrong_count加一。参数上last_wrong_at存 ISO 时间字符串方便做“最近一周错题”筛选。分类字段在解析阶段打标这里直接当过滤条件用。注意SQLite 的category如果没打标可以先跑一遍关键词更新比如UPDATE questions SET category法律 WHERE stem LIKE %宪法% OR stem LIKE %刑法%再人工抽查。4. 题库答案校验与去重的 4 个必查参数4.1 答案字段的合法性校验解析出来的答案经常有脏数据空答案、答案字母超出选项范围、多选答案顺序乱。校验脚本如下def validate(q): errs [] ans q.get(answer, ) opts q.get(options, {}) if not ans: errs.append(答案为空) for ch in ans: if ch not in opts: errs.append(f答案 {ch} 不在选项中) if len(set(ans)) ! len(ans): errs.append(答案有重复字母) return errs bad [(q[num], validate(q)) for q in questions if validate(q)] print(异常题数:, len(bad))逻辑说明逐题检查答案非空、每个答案字母都能在选项里找到、无重复字母。参数上如果题库有多选且答案要求排序可以加if ans ! .join(sorted(ans))来标记乱序答案统一重排。4.2 题干相似度去重同一份“完整版”里经常混入重复题题干只差一两个字。用difflib做快速比对from difflib import SequenceMatcher def dedup(questions, threshold0.9): kept [] for q in questions: dup False for k in kept: ratio SequenceMatcher(None, q[stem], k[stem]).ratio() if ratio threshold: dup True break if not dup: kept.append(q) return kept逻辑说明SequenceMatcher返回 0 到 1 的相似度threshold0.9表示九成相似就判重。参数上题库量大时这个 O(n²) 会很慢可以先按题干长度分桶只在同桶内比对能把耗时降一个数量级。4.3 选项数量与题型匹配单选题应该有 4 个选项判断题可能只有 2 个。校验时按题型分别设阈值题型选项数答案长度单选41多选42-4判断21如果单选题答案长度大于 1基本可以判定是解析错位或答案串行需要回查原始 docx 对应段落。4.4 解析缺失的补全策略解析为空不影响刷题但影响学习体验。常见做法是先标记explain的题导出成 CSV人工补或者用题干关键词去匹配已有解析库。批量导出命令sqlite3 -header -csv tiku.db \ SELECT qnum,stem,answer FROM questions WHERE explain ORDER BY qnum; \ missing_explain.csv逻辑说明-header -csv输出带表头的 CSV方便 Excel 打开。参数上WHERE explain只挑缺失的避免全量导出。补完后用UPDATE回写注意用qnum做匹配键时要确认题号唯一。5. 从 docx 到可分发题库导出 JSON 与增量更新技巧5.1 导出成前端可直接消费的 JSON刷题小程序或网页通常要 JSON。导出时把options从字符串还原成对象并统一字段名import json, sqlite3 conn sqlite3.connect(tiku.db) conn.row_factory sqlite3.Row rows conn.execute(SELECT * FROM questions ORDER BY qnum).fetchall() data [] for r in rows: data.append({ id: r[id], num: r[qnum], stem: r[stem], options: json.loads(r[options] or {}), answer: r[answer], explain: r[explain], category: r[category] }) with open(tiku.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)逻辑说明row_factory sqlite3.Row让结果可以按列名取值比下标可读。json.loads把存进去的选项字符串还原成对象。参数上indent2方便人看线上分发可以去掉以减小体积。5.2 用题号加题干哈希做增量更新题库每年会更新直接全量替换会丢掉用户的错题记录。稳妥做法是给每题算一个稳定指纹import hashlib def fingerprint(q): raw (q[stem] | q.get(answer, )).encode(utf-8) return hashlib.md5(raw).hexdigest()逻辑说明用题干加答案做 MD5题干或答案变了指纹就变。增量更新时新题库里指纹已存在的跳过不存在的插入旧库里指纹不在新库的标记为“已下线”而不是删除这样错题记录还能关联。参数上如果题干有细微格式差异可以先做strip()和全角转半角再算哈希。5.3 一个容易被忽略的细节docx 里的全角字符公共基础知识题库里大量出现全角括号、全角冒号、全角空格。解析前统一转半角能省掉很多正则分支def normalize(s): table str.maketrans(【】, ()[]:,. ) return s.translate(table).strip()逻辑说明str.maketrans建立全角到半角的映射translate一次性替换。参数上映射表按实际文档里出现的符号补全比如还有全角引号“”可以映射成。这一步放在解析最前面后面所有正则都能简化。5.4 验证导出结果是否可用的三个检查点导出后别急着分发先跑三个检查题目总数是否和原始 docx 题号最大值一致随机抽 10 题人工核对答案和选项用 JSON 解析器验证文件合法。命令如下python -c import json;djson.load(open(tiku.json,encodingutf-8));print(len(d))逻辑说明这条命令只做两件事解析 JSON 并打印题数。如果解析报错说明导出时有非法字符如果题数明显偏少回查解析阶段的题号正则是否漏匹配。参数上可以把len(d)和SELECT MAX(qnum) FROM questions的结果对比差值超过 5% 就值得排查。本文还有配套的精品资源点击获取