☰
无人机题库docx解析与本地刷题工具实战
2026/10/12 1:07:13 网站建设 项目流程

简介:这份无人机理论考试题库面向无人机驾驶员、备考人员及相关从业者,用于系统梳理理论考点、检验知识掌握程度。内容覆盖飞行原理中的空气动力学与飞行力学基础,如升力、阻力、推力与重力的相互作用,并延伸至起飞、巡航、降落各阶段的操作规范,以及航程、航时、飞行速度等性能影响因素。维护保养部分强调结构、动力与控制系统关键部件的定期检查,区分预防性维护与故障排除;法律法规部分则涉及空域划分、飞行计划申请、高度速度限制、注册与适航证获取、驾驶员资质认证等要求。题库还涵盖无人机系统组成、飞行安全与农业植保、航拍测绘、电力巡检、物流运输等应用场景。资源包共1个docx文件,约160KB,以选择题形式呈现,便于自测与查漏补缺。目前已有132人学习,适合需要集中刷题、巩固法规与操作知识的备考者使用。

1. 一份 docx 题库,怎么变成能刷题、能检索、能自测的本地工具

很多人拿到「无人机理论考试题库.docx」的第一反应是直接翻页背,但真到刷题阶段就会发现:几百道题混在文档里,没有分类、没有答案索引、没有错题记录,翻三遍也记不住。这份文档真正的价值不在“读”,而在“拆”——把它拆成结构化数据,再套一层本地刷题界面,你就能按章节刷、按错题刷、按关键词搜。

这篇笔记面向三类人:正在备考无人机执照(CAAC/UTC 等)的飞手、想给学员做内部题库的培训机构教员、以及习惯把资料本地化管理的技术型学习者。核心链路只有一条:docx 解析 → 题目结构化 → 本地检索与自测。全程不依赖任何在线服务,一台普通笔记本就能跑完。下面按“先看清文档结构,再动手解析,最后落地成工具”的顺序展开,中间会给出可直接复制的 Python 脚本和参数说明。

2. 先摸清 docx 题库的排版规律,再决定解析策略

2.1 无人机题库常见的四种排版形态

在写任何代码之前,必须先把 docx 打开,用 Word 的“显示编辑标记”看一遍。无人机理论题库的排版通常逃不出这四种:

形态特征解析难度
纯段落型题干、选项、答案各占独立段落低
表格型每道题是一行或一个表格中
混合型题干段落 + 选项表格 + 答案段落高
带编号型题干前有“1.”“第1题”等编号低

判断方法很直接:全选文档,看 Word 状态栏的“字数”和“段落数”比例。如果平均每段字数在 20 字以下,大概率是纯段落型;如果段落数远小于题目数,说明题目被塞进了表格。

我一般会先跑一段统计脚本,把文档的“骨架”打印出来,避免盲目写正则。

from docx import Document doc = Document("无人机理论考试题库.docx") para_count = len(doc.paragraphs) table_count = len(doc.tables) non_empty = [p.text.strip() for p in doc.paragraphs if p.text.strip()] print(f"段落总数: {para_count}") print(f"非空段落: {len(non_empty)}") print(f"表格数量: {table_count}") # 打印前 30 个非空段落,观察排版规律 for i, text in enumerate(non_empty[:30]): print(f"[{i}] {text[:60]}")

这段脚本的作用是“侦察”:para_count和non_empty的差值告诉你有多少空段落(通常是格式占位),table_count决定你要不要走表格解析分支。前 30 行输出能让你一眼看出题干、选项、答案的排列顺序。参数上唯一要注意的是p.text.strip()——不加strip()会把 Word 里的软回车和空格算进去,导致误判。

2.2 用正则识别题干、选项、答案的边界

摸清规律后,下一步是给每一类内容打标签。无人机题库里最常见的模式是:

  • 题干:以数字编号开头,如1.1、第1题
  • 选项:以A.B.C.D.开头,少数用A、
  • 答案:以答案:正确答案:参考答案:开头
  • 解析:以解析:说明:开头

把这些模式写成正则,逐段分类。这里的关键是“顺序敏感”——题干一定在选项之前,答案一定在选项之后,所以可以用状态机而不是纯正则匹配。

import re PATTERNS = { "question": re.compile(r"^\s*(\d+)[\.、\s]"), "option": re.compile(r"^\s*([A-D])[\.、\s]"), "answer": re.compile(r"^\s*(正确答案|答案|参考答案)[::\s]"), "explain": re.compile(r"^\s*(解析|说明)[::\s]"), } def classify(paragraphs): result = [] current = {"q": None, "options": [], "answer": None, "explain": None} for text in paragraphs: if PATTERNS["question"].match(text): if current["q"]: result.append(current) current = {"q": text, "options": [], "answer": None, "explain": None} elif PATTERNS["option"].match(text): current["options"].append(text) elif PATTERNS["answer"].match(text): current["answer"] = text elif PATTERNS["explain"].match(text): current["explain"] = text if current["q"]: result.append(current) return result

classify函数的核心逻辑是:遇到新题干就“结算”上一题,遇到选项就追加,遇到答案和解析就挂到当前题上。参数上,PATTERNS里的正则可以根据你的文档微调——比如有的题库用( )占位答案,有的把答案写在题干末尾的括号里,这时候需要额外加一条分支去提取括号内容。跑完这一步,你会得到一个列表,每个元素是一道题的结构化字典。

2.3 表格型题库的提取:别用 paragraphs,用 tables

如果侦察阶段发现table_count大于 0,说明题目在表格里。这时候doc.paragraphs是拿不到表格内容的,必须遍历doc.tables。无人机题库的表格通常是“一行一题”或“两列:题目 | 答案”。

def parse_tables(doc): rows = [] for table in doc.tables: for row in table.rows: cells = [c.text.strip() for c in row.cells] # 过滤表头和空行 if len(cells) >= 2 and cells[0] and not cells[0].startswith("题目"): rows.append(cells) return rows

这里有个血泪经验:Word 表格的合并单元格会让row.cells出现重复文本,所以拿到cells后最好做一次去重(list(dict.fromkeys(cells)))。另外,表格里的换行符是\n而不是段落分隔,选项可能挤在一个单元格里,需要用split("\n")再拆一次。参数上,len(cells) >= 2是最低要求,具体列数按你的文档调整。

3. 把解析结果落成 JSON 和 SQLite,方便检索与统计

3.1 为什么选 JSON + SQLite 而不是直接存 Excel

解析完题目后,很多人第一反应是导出 Excel。Excel 能看,但不好“查”——你想按关键词搜题干、按章节统计错题、按选项分布做分析,Excel 都别扭。我的做法是双写:JSON 做备份和版本管理,SQLite 做查询和统计。

JSON 的好处是可读、可 diff、方便传给前端;SQLite 的好处是单文件、零配置、支持 SQL。两者结合,既能用git管理题库变更,又能用一条 SQL 查出“所有含‘空域’且答案是 B 的题”。

import json import sqlite3 def save_json(questions, path="questions.json"): with open(path, "w", encoding="utf-8") as f: json.dump(questions, f, ensure_ascii=False, indent=2) def save_sqlite(questions, path="questions.db"): conn = sqlite3.connect(path) conn.execute(""" CREATE TABLE IF NOT EXISTS questions ( id INTEGER PRIMARY KEY, question TEXT, option_a TEXT, option_b TEXT, option_c TEXT, option_d TEXT, answer TEXT, explain TEXT ) """) for i, q in enumerate(questions, 1): opts = {o[0]: o for o in q["options"]} conn.execute( "INSERT OR REPLACE INTO questions VALUES (?,?,?,?,?,?,?,?)", (i, q["q"], opts.get("A"), opts.get("B"), opts.get("C"), opts.get("D"), q["answer"], q["explain"]) ) conn.commit() conn.close()

save_json的ensure_ascii=False是关键参数,不加的话中文会变成\uXXXX,人眼没法读。save_sqlite里用INSERT OR REPLACE是为了支持重复解析——你改一次正则重跑,不会产生重复数据。选项拆成四列而不是一列,是为了后面做“选项分布统计”和“随机组卷”时不用再解析字符串。

3.2 用 SQL 做题库体检:重复题、缺答案、选项数异常

数据入库后,先别急着刷题,做一次“体检”。无人机题库在流传过程中经常出现重复题、答案丢失、选项只有三个等问题。用几条 SQL 就能查出来。

-- 查重复题干 SELECT question, COUNT(*) AS cnt FROM questions GROUP BY question HAVING cnt > 1; -- 查缺答案的题 SELECT id, question FROM questions WHERE answer IS NULL OR answer = ''; -- 查选项不足四个的题 SELECT id, question FROM questions WHERE option_a IS NULL OR option_b IS NULL OR option_c IS NULL OR option_d IS NULL;

第一条帮你合并重复题,第二条防止刷题时“无解可对”,第三条能发现解析遗漏。我一般会把这三条查询的结果导出成一份“待修清单”,人工过一遍再进入刷题环节。参数上,HAVING cnt > 1的阈值可以按需改成> 2,因为有些题库故意保留相似题做变体训练。

3.3 给每道题打上章节和知识点标签

无人机理论考试通常分多个模块:法规、气象、飞行原理、空域、应急处置等。如果 docx 里没有章节标记,就得靠关键词打标签。常见做法是维护一个“关键词 → 章节”的映射表,对题干做匹配。

TAG_RULES = { "法规": ["执照", "注册", "条例", "处罚", "实名"], "气象": ["风速", "云", "气压", "能见度", "雷暴"], "飞行原理": ["升力", "桨叶", "重心", "悬停", "失速"], "空域": ["空域", "禁飞", "限高", "航线", "管制"], "应急": ["失控", "返航", "迫降", "干扰", "故障"], } def tag_question(text): tags = [] for tag, keywords in TAG_RULES.items(): if any(kw in text for kw in keywords): tags.append(tag) return tags or ["未分类"]

这个映射表不是标准答案,而是“够用就好”的起点。跑完后统计一下“未分类”的比例,如果超过 20%,说明关键词覆盖不够,需要补充。参数上,any改成“命中两个以上关键词才打标”能减少误标,但会漏掉短题干,按你的题库质量权衡。

4. 本地刷题工具的三个必调参数与最小实现

4.1 用 Python + 命令行做最小可刷题版本

数据准备好了,刷题工具可以极简:读 SQLite,随机抽题,打印题干和选项,等输入,判对错。不需要 GUI,命令行就够用,而且方便你随时改逻辑。

import sqlite3 import random def quiz(db_path="questions.db", mode="random", limit=10): conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row if mode == "random": rows = conn.execute( "SELECT * FROM questions ORDER BY RANDOM() LIMIT ?", (limit,) ).fetchall() else: rows = conn.execute( "SELECT * FROM questions WHERE id = ?", (mode,) ).fetchall() score = 0 for row in rows: print(f"\n{row['question']}") for opt in ["option_a", "option_b", "option_c", "option_d"]: if row[opt]: print(f" {row[opt]}") user = input("你的答案: ").strip().upper() correct = (row["answer"] or "").strip().upper() if correct and correct[0] in user: print("正确") score += 1 else: print(f"错误,正确答案: {correct}") print(f"\n得分: {score}/{len(rows)}") conn.close()

mode参数支持两种模式:"random"随机抽题,传具体 id 则单题复习。limit控制每次题量,建议从 10 开始,太多会疲劳。判分逻辑里correct[0] in user是为了兼容“答案:B”和“B”两种格式,但要注意如果答案是“AB”多选,这个逻辑会误判——多选需要单独处理,把in改成集合比较。

4.2 错题记录:一张表解决“后悔药”问题

刷题不复盘等于白刷。错题记录不需要复杂设计,加一张wrong_answers表,每次答错就写一条,下次可以专门刷错题。

def record_wrong(conn, qid): conn.execute(""" CREATE TABLE IF NOT EXISTS wrong_answers ( qid INTEGER, wrong_count INTEGER DEFAULT 1, last_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (qid) ) """) conn.execute(""" INSERT INTO wrong_answers (qid, wrong_count) VALUES (?, 1) ON CONFLICT(qid) DO UPDATE SET wrong_count = wrong_count + 1, last_time = CURRENT_TIMESTAMP """, (qid,)) conn.commit()

ON CONFLICT ... DO UPDATE是 SQLite 的 upsert 语法,保证同一道题只留一条记录,但错误次数累加。last_time用来自动排序“最近错的题优先刷”。参数上,wrong_count可以用来做“错三次以上重点标记”,在查询时加WHERE wrong_count >= 3即可。

4.3 组卷参数:题量、章节权重、选项乱序

如果你要模拟真实考试,组卷逻辑要支持三个参数:总题量、各章节占比、选项是否乱序。选项乱序能防止你“背答案位置”而不是“背知识”。

def build_paper(conn, total=100, weights=None, shuffle_options=True): weights = weights or {"法规": 0.3, "气象": 0.2, "飞行原理": 0.2, "空域": 0.2, "应急": 0.1} paper = [] for tag, ratio in weights.items(): n = int(total * ratio) rows = conn.execute( "SELECT * FROM questions WHERE tags LIKE ? ORDER BY RANDOM() LIMIT ?", (f"%{tag}%", n) ).fetchall() paper.extend(rows) if shuffle_options: for row in paper: opts = [row["option_a"], row["option_b"], row["option_c"], row["option_d"]] random.shuffle(opts) return paper

weights的默认值是按常见考试比例设的,你可以按自己考纲调整。tags LIKE ?依赖前面打标签时把 tags 存成了字符串(如"法规,空域"),如果存的是 JSON 数组,这里要改成json_each查询。shuffle_options只打乱显示顺序,不影响答案判定——判定时仍然按原始选项字母比对,所以刷题界面要保留原始选项映射。

5. 解析和刷题环节最容易翻车的五个坑

5.1 现象:解析出来题目数量对不上,少了几十道

原因:docx 里的题目有的在段落,有的在文本框(w:txbxContent),python-docx默认不读文本框内容。解决:用docx2python或直接解压 docx 读document.xml,把w:txbxContent里的文本也提取出来。如果不想换库,至少先用 Word 的“选择窗格”确认有没有文本框。

5.2 现象:选项被合并成一行,A/B/C/D 挤在一起

原因:Word 里用了软回车(Shift+Enter)而不是硬回车,python-docx把它们当成一个段落。解决:在解析前对每个段落做text.replace("\v", "\n").split("\n"),把软回车拆开再逐行分类。这个坑在表格型题库里尤其常见。

5.3 现象:答案提取出来是“B”但实际答案是“B. 保持安全高度”

原因:正则答案[::\s]只匹配到冒号后的第一个字符,或者答案行本身包含了选项全文。解决:答案提取后做一次清洗,用re.match(r"[A-D]", answer)只取首字母;如果答案行是“B. 保持安全高度”,先按[\.、]分割再取第一段。

5.4 现象:SQLite 插入中文报编码错误

原因:连接时没指定编码,或者 JSON 写入时用了默认ascii。解决:sqlite3.connect默认就是 UTF-8,问题通常出在json.dump没加ensure_ascii=False,以及文件打开时没写encoding="utf-8"。两个地方都补上即可。

5.5 现象:刷题时随机抽题总是抽到同一批

原因:ORDER BY RANDOM()在数据量小的时候“看起来”重复,或者你用了LIMIT但没加OFFSET。解决:确认表里数据量足够;如果确实少,改用“先打乱 id 列表再切片”的方式,避免 SQLite 的随机函数在小数据集上的分布偏差。

6. 进阶:把题库接进 Anki 或自建 Web 界面

命令行刷题适合自己用,但如果你要长期复习,或者想分享给学员,有两个进阶方向。一是导出成 Anki 可导入的 CSV,利用 Anki 的间隔重复算法;二是用 Flask 起一个本地 Web 界面,手机连同一 WiFi 就能刷。

导出 Anki 的格式很简单:每行“题干 + 选项 | 答案 + 解析”,用制表符分隔。

import csv def export_anki(conn, path="anki_import.csv"): rows = conn.execute("SELECT * FROM questions").fetchall() with open(path, "w", encoding="utf-8", newline="") as f: writer = csv.writer(f, delimiter="\t") for row in rows: front = row["question"] + "<br>" + "<br>".join( filter(None, [row["option_a"], row["option_b"], row["option_c"], row["option_d"]])) back = (row["answer"] or "") + "<br>" + (row["explain"] or "") writer.writerow([front, back])

delimiter="\t"是 Anki 导入时最稳的分隔符,避免题干里的逗号干扰。<br>是 Anki 支持的 HTML 换行。导入时记得在 Anki 里选“允许 HTML”。

如果走 Web 路线,Flask 的最小实现只需要三个路由:/出题、/answer判分、/wrong看错题。核心是把 SQLite 查询封装成函数,前端用最朴素的 HTML 表单即可,不需要前端框架。我自己的习惯是:先命令行跑通逻辑,再套 Web,避免一开始就陷进样式里。

最后说一个我踩过的坑:题库更新后,一定要重新跑一遍解析和入库,并且用INSERT OR REPLACE而不是INSERT,否则旧题和新题会混在一起。我一般会在解析脚本开头加一行os.remove("questions.db")做全量重建,简单粗暴但不会出错。希望帮到你。

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

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

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

立即咨询