简介:这份 PDF 是面向高中物理必修二学生的《机械能守恒定律》章节检测卷及答案解析,适合单元自测、期末复习与错题订正使用。题目围绕动能、重力势能与弹性势能的相互转化,延伸到嫦娥五号轨道变轨、弹簧压缩与弹回、汽车恒定功率启动、传送带多消耗电能等典型情景,并配有选择题与填空题的逐项辨析和计算过程。资源包仅含 1 个 PDF 文件,大小约 548KB,下载后可直接打印或在线查阅;答案部分对易错选项、机械能守恒条件以及变力做功的处理给出了较细致的说明。目前已有 64 人学习下载,可作为课堂检测的补充材料,也适合教师备课选题、学生对照解析梳理能量观点,强化从受力分析到能量守恒的综合建模能力。
1. 从一份《机械能守恒定律》检测卷 PDF 说起
期末前两周,教务群里转得最多的文件,名字往往长得离谱:上海上海市实验学校西校高中物理必修二第八章《机械能守恒定律》检测(答案解析).pdf。学校、册次、章节、题型、答案解析,全塞进文件名里。麻烦也跟着来了——这类试卷基本是排版软件导出的双栏 PDF,题干、插图、选择题选项、参考答案区混排在同一页,直接复制出来公式全散架,搜「动能定理」搜不到,想按知识点重新组卷只能人工拆。
把它变成可检索、可结构化、能自动生成解析的数据,是一次很典型的文档智能工程:版面分析决定切分边界,公式识别决定知识点能不能对上,解析模板决定后续能不能批量复用,最后还得靠数值验算把质量兜住。下面按「解析版面—识别公式—生成解析—校验质量」四步,讲一套能在本地跑通的方案,环境只需要 Python 和一个能装包的解释器。
2. 用 PyMuPDF 解析检测卷版面:题干、选项与答案区的切分
一份《机械能守恒定律》检测卷的信息密度很高:正文是双栏,题干里有上下标和希腊字母,旁边夹着斜面、单摆、弹簧的矢量图,末尾还有一个独立的「参考答案与解析」区块。用纯文本抽取(比如某些在线转换工具)拿到的是一锅粥,因为丢掉了坐标和字号这两个最关键的版面信号。这一章先把版面拆干净。
2.1 用 PyMuPDF 拿到文本块、坐标和字号
第一件事不是写正则,而是把每个 span 的坐标、字号、字体都打印出来看一眼。get_text("dict")会保留这些信息,这是后面所有切分规则的输入。
import fitz # PyMuPDF,pip install pymupdf PATH = "上海上海市实验学校西校高中物理必修二第八章《机械能守恒定律》检测(答案解析).pdf" doc = fitz.open(PATH) for pno, page in enumerate(doc): w, h = page.rect.width, page.rect.height blocks = page.get_text("dict")["blocks"] for b in blocks: if b.get("type") != 0: # type=0 是文本块,1 是图片块(受力图、纸带图) print(pno, "IMAGE", [round(v) for v in b["bbox"]]) continue for line in b["lines"]: for span in line["spans"]: # bbox 是 (x0, y0, x1, y1),页面坐标系原点在左上角 print(pno, round(span["size"], 1), span["font"], [round(v, 1) for v in span["bbox"]], repr(span["text"]))跑完这一步,通常能直接看出三件事:正文正文字号集中在同一个值(比如 10.5),标题和「参考答案」这类区段头会明显更大或加粗,页眉页脚字号偏小且 y 坐标贴边。span["bbox"]里的 x0 则决定它属于左栏还是右栏。图片块单独标出来很重要,物理卷里的机械能守恒题几乎都配图,题干文字和图的相对位置就是判断「这题属于哪一小问」的依据。
2.2 双栏、页眉页脚与题号的切分规则
拿到坐标后,规则可以写得非常直白。先把页面上所有 span 按栏分组:页面中线取w/2,x0 < w/2的进左栏,其余进右栏;如果某个 span 横跨中线(x0 < w/2 < x1且宽度超过页宽的三成),判定为通栏标题,单独成组。分组后再按 y0 排序,双栏顺序就正确了。
页眉页脚和题号的判定可以固化到一张表里,避免每次都凭感觉调阈值:
| 版面元素 | 判断依据 | 处理动作 |
|---|---|---|
| 页眉(校名、章节名) | y0 < 页高 × 0.06 且字号小于正文 | 丢弃,不计入题干 |
| 页脚 / 页码 | y1 > 页高 × 0.94 | 丢弃 |
| 大题号 | 匹配^[一二三四五六]、 | 新建大题分组,记录题型 |
| 小题号 | 匹配^\d{1,2}[.、] | 新建题目对象 |
| 选项 | 匹配^[A-D][.、]或四个 A/B/C/D 位于同一行 | 追加到当前题目的选项列表 |
| 答案区起点 | 文本命中「参考答案」「答案解析」「答案」 | 切换 section 标记为answer |
| 配图 | block type = 1,且与题干 y 区间重叠 | 挂到当前题目的figures字段 |
关键点是「状态机」思路:从左到右、从上到下扫一遍 span,遇到小题号就开一个新题目,遇到选项就追加,遇到答案区标记就把后续内容全部归到答案池。页码这类噪声在扫描阶段直接丢,比事后清洗省事得多。
2.3 答案区定位与题干对齐
答案区通常有两种形态:一种是「1. B 2. D 3. ACD」的选择题答案速查表,另一种是逐题的「解析」。速查表按题号对齐,用正则(\d{1,2})[.、]?\s*([A-D]{1,4})一把梭即可;逐题解析则要看它有没有复述题号。
我一般会做一次双向对齐校验:解析里出现的题号集合,和题干区解析出的题号集合,取对称差。差集非空就说明切分漏了题或者串了题——常见原因是跨栏题干被切到了两栏,或者某道题的图把文字块打散了。
import re def align_answers(stems: dict, answers: dict) -> None: """stems/answers 均为 {题号: 内容},打印对不上的题号""" missing = sorted(set(stems) - set(answers), key=int) # 有题干没答案 extra = sorted(set(answers) - set(stems), key=int) # 有答案没题干 if missing: print("缺答案的题号:", missing) # 通常要去 2.2 表格里放宽题号正则 if extra: print("多出的答案:", extra) # 多半是解析里把分数、页码误认成题号这一步做完,一份试卷就变成了{题号: {题干, 选项, 图, 答案, 解析}}的结构,后面所有处理都在这个结构上做,不再碰 PDF。
3. 机械能守恒公式的识别与归一化:让 E_k、ΔE_p 不再散架
题干拆出来了,但内容不能直接入库。物理卷里v²、E_k、ΔE_p、mgh、kg·m/s²这些东西在 PDF 内部是靠字体和位置拼出来的,纯文本抽取会把上标压回基线,v²变v2,E_k变Ek。知识点打标全靠字符串匹配,公式一散,标签就全错。这一章解决公式保真问题。
3.1 为什么纯文本抽取保不住 W=mgh
PDF 里没有「公式」这个对象,只有一堆带坐标的字形。上标是字号更小、y 坐标更靠上的独立 span;分数线是一条细长的矩形或者一张小图;根号常常是图片。这意味着三件事同时发生:上下标层级丢失、分数结构丢失、根号直接变图片。
实践中不用死磕「还原成排版级 LaTeX」,够用就行。真正影响检索和打标的是三样东西:物理量符号(E_k、E_p、ΔE)、幂次(v²、h³)、单位(J、N/m、m/s²)。把这三样归一化,剩下的交给人工在解析模板里校对。
另外要处理的是字形兼容问题。同一份卷子里Δ可能以三种码位出现(U+0394、U+2206、以及某些字体下的私有区字形),×和·也常混用。统一做一次 Unicode 规范化,能省掉后面无数个「明明看着一样却匹配不上」的坑。
3.2 公式区域检测与 LaTeX 归一化
公式区域检测不需要上模型。一个足够好用的启发式是:同一个文本块内,如果出现「字号不一致的相邻 span」且高频命中物理量符号表,就判定为公式区,走归一化管线;其余走普通文本清洗。
import re import unicodedata # 先做 Unicode 规范化,压掉同形异码 def clean(s: str) -> str: s = unicodedata.normalize("NFKC", s) return (s.replace("\u2206", "\u0394") # 增量符号统一成 Δ .replace("\u00b7", "\u00b7") # 中点保持 .replace("\u2212", "-")) # 减号统一成 ASCII 连字符 # 物理量写法 -> 规范 LaTeX,顺序敏感,长的放前面 RULES = [ (r"\bE\s*[kK]\b", r"E_{k}"), # 动能 (r"\bE\s*[pP]\b", r"E_{p}"), # 重力势能 / 弹性势能 (r"\u0394\s*E\s*[pP]", r"\Delta E_{p}"), # 势能变化量 (r"\bW\s*\u5408\b", r"W_{\u5408}"), # 合外力做功 (r"\b([vhmg])\s*\^?\s*2\b", r"\1^{2}"), # v2 / v^2 -> v^{2} ] def normalize_formula(s: str) -> str: s = clean(s) for pat, rep in RULES: s = re.sub(pat, rep, s) return re.sub(r"\s{2,}", " ", s).strip()规则表按「先长后短」排列很关键:如果把E_p的规则写在\Delta E_p前面,ΔEp会先被拆成Δ+E_{p},结果变成ΔE_{p}而不是带\Delta的整体,语义上就差了一层。NFKC规范化能顺带把全角括号、全角数字、全角减号统一掉,中文试卷里这类字符非常多。
单位单独处理,因为它们决定数值换算对不对:
| 原始写法 | 规范写法 | 说明 |
|---|---|---|
| J | J | 能量,机械能守恒题的核心单位 |
| m/s^2、m/s2 | m/s² | 重力加速度 |
| kg·m/s² | kg·m/s² | 与 N 等价,保留原样便于对照题干 |
| N/m | N/m | 弹簧劲度系数 |
| cm、m | cm / m | 实验题用 cm,计算题用 m,必须显式标注 |
注意:单位混用是物理题解析出错的第一大来源。打点计时器纸带实验用厘米,机械能守恒计算用米,流水线里必须带上单位字段,不能只存数字。
3.3 符号映射与打标前置
归一化之后,知识点打标的准确率取决于符号层是否统一。我一般维护一份小映射表,把卷面里出现过的所有物理量别名收敛到唯一符号,再交给打标规则。这样即使出题老师这次写E_k、下次写Ek、再下次写「动能」,落到库里都是同一个标记。
打标规则本身用正则集合就够,不需要模型:命中「只有重力做功」「只有重力和弹力做功」「机械能守恒」归到主知识点,命中「动能定理」「合外力做功」归到相邻知识点,命中「打点计时器」「纸带」「光电门」归到实验类。经验上,一份 20 题的章节检测卷,人工过一遍打标规则大概十分钟,之后这个章节的所有卷子都能复用。
4. 从题干到解析答案:一条可复现的生成流水线
前两章把内容变成了结构化数据,这一章把数据变成能直接发出去的「检测卷 + 答案解析」。整条流水线的关键是:解析不能靠自由生成,必须靠模板 + 数值验算。物理计算的每一步都有确定的公式,模板化之后的解析比自由文本更可靠,也更容易批量校对。
4.1 题干切分与知识点打标
先定义数据模型,题目对象要能承载小问、选项、配图、答案、解析和标签。
from dataclasses import dataclass, field import re @dataclass class Question: no: str stem: str options: list = field(default_factory=list) sub: list = field(default_factory=list) # 小问,如 (1)(2)(3) figures: list = field(default_factory=list) answer: str = "" analysis: str = "" tags: list = field(default_factory=list) TAG_RULES = { "机械能守恒": [r"机械能守恒", r"只有重力做功", r"只有重力和弹力做功"], "动能定理": [r"动能定理", r"合外力做功", r"W\s*=\s*\u0394E"], "重力势能": [r"重力势能", r"mgh", r"E_\{p\}"], "弹性势能": [r"弹性势能", r"弹簧", r"k\s*x"], "实验探究": [r"打点计时器", r"纸带", r"光电门", r"刻度尺"], } def tag_stem(stem: str) -> list: return [t for t, pats in TAG_RULES.items() if any(re.search(p, stem) for p in pats)]小问切分用^\((\d)\)这个模式,注意中文卷子里还有(1)全角括号的写法,NFKC之后会统一成半角,所以正则只写半角即可。小题号如果跨栏断行,靠 2.2 的栏分组保证顺序,不要用纯 y 坐标排序,否则右栏的内容会插到左栏中间。
4.2 解析模板与数值校验
解析生成分两类。选择题走「选项辨析」模板:把正确选项的物理依据写成一句话,再把最常见的错误选项对应的错误物理模型点出来。计算题走「步骤模板」:列出研究对象、受力与做功分析、列机械能守恒方程、代入求解、检查合理性。
无论哪类,数值都必须过一遍验算函数,这是整个流水线里最值钱的一环。
def check_conservation(m, h1, v1, h2, v2, g=9.8, tol=1e-3): """ 校验一段过程是否满足机械能守恒:E1 == E2 m 单位 kg,h 单位 m,v 单位 m/s,g 默认 9.8 返回 (是否守恒, E1, E2) """ e1 = m * g * h1 + 0.5 * m * v1 ** 2 e2 = m * g * h2 + 0.5 * m * v2 ** 2 ok = abs(e1 - e2) <= tol * max(abs(e1), 1e-9) return ok, e1, e2 # 例:m=1kg 从 h=5m 自由下落,求落地速度,g 取 9.8 import math v = math.sqrt(2 * 9.8 * 5) # 约 9.899 m/s print(check_conservation(1, 5, 0, 0, v)) # (True, 49.0, 49.0)tol用相对误差而不是绝对误差,因为不同题目的能量数量级差很多:单摆题可能是 0.5 J,滑块题可能是 500 J。相对误差取 1e-3,正好能容忍答案四舍五入到两位有效数字带来的偏差,又不会把真的算错的题放过去。如果卷面给出 g=10(不少初中衔接题会这么写),调用时显式传入g=10,别改默认值,否则同一批卷子里两种取值会互相污染。
4.3 批量导出与检索索引
结构化的最后一步是入库,让它可搜、可组卷、可追溯来源试卷。用 SQLite 就够,FTS5 负责全文检索。
-- 主表:一道题一行,paper 字段记录来源试卷文件名 CREATE TABLE questions ( id INTEGER PRIMARY KEY, paper TEXT NOT NULL, no TEXT NOT NULL, stem TEXT NOT NULL, answer TEXT, analysis TEXT, tags TEXT, -- 逗号分隔,如 "机械能守恒,重力势能" UNIQUE(paper, no) ); -- 全文索引,覆盖题干和解析,组卷时按关键词召回 CREATE VIRTUAL TABLE q_fts USING fts5( stem, analysis, tags, content='questions', content_rowid='id', tokenize='unicode61' ); -- 按知识点查题:召回所有机械能守恒题,按来源卷分组 SELECT paper, no, substr(stem, 1, 40) FROM questions WHERE tags LIKE '%机械能守恒%' ORDER BY paper, CAST(no AS INTEGER);UNIQUE(paper, no)这一条约束能在重复解析同一份 PDF 时自动挡住脏数据,比在应用层判重更省心。tokenize='unicode61'对中文按字切分,虽然不如专门的中文分词精确,但对「机械能守恒」「动能定理」这类固定术语的召回已经够用;如果卷子里术语写法多变,可以在入库前把 tags 也写进 FTS 表的 tags 列,靠标签召回比靠原文召回稳。
导出环节建议同时产出两份东西:一份给人看的 Word/PDF(题干在前、答案解析在后,题号严格对应),一份给机器用的 JSON。两份用同一个id,出问题时能直接顺着 id 回查原始页码和坐标。
5. 自动验算与抽检:把机械能守恒检测卷的解析质量管住
结构化做完,真正的风险才露头:错一道题的答案,整份卷子的解析都得返工。质量把控靠两件事——数值验算和定点抽检。
5.1 用守恒关系做交叉验算
凡是带数值的机械能守恒题,都应该能用check_conservation复算一遍。做法是从题干里抽出所有数值和单位,转成标准单位后代入公式,把计算结果和卷面给出的答案比对。选择题如果选项里只有一个值能通过验算,那这个选项的答案基本可以自动确认;如果有多个值都能通过,说明题目本身缺条件,需要人工介入。
import re NUM = re.compile(r"(-?\d+(?:\.\d+)?)\s*(m/s|m|kg|cm|J)") def extract_quantities(text: str) -> dict: """从题干里抠出带单位的量,统一换算成 m/s、m、kg、J""" out = {} for val, unit in NUM.findall(text): v = float(val) if unit == "cm": v, unit = v / 100.0, "m" # 厘米一律换算成米 out.setdefault(unit, []).append(v) return out这个抽取器很粗糙,但只要在解析模板里预先把变量名和单位绑好(比如h1一定对应「高度」「距地面」这类词),准确率就能上一个台阶。不建议在这一步上大模型,规则错了能一眼看出来,模型错了很难定位。
5.2 抽检清单与回退路径
全自动跑完之后,按下面这张表做一次抽检,每一条都对应一个明确的回退位置,出问题不用从头查。
| 抽检项 | 抽样比例 | 判错标准 | 回退动作 |
|---|---|---|---|
| 题号连续性 | 100% | 缺号、重号、跳号 | 回到 2.2 检查页眉丢失与题号正则 |
| 公式渲染 | 10% | 出现裸露的v2、Ek | 回到 3.2 补公式映射规则 |
| 单位一致 | 100% | 同一题内 cm 与 m 混用 | 检查 4.2 的单位换算入口 |
| 答案覆盖 | 100% | 有题干无答案 | 回到 2.3 检查答案区定位 |
| 数值验算 | 100% | 守恒等式相对误差 > 1e-3 | 人工复核题干条件是否完整 |
| 分值合计 | 100% | 各题分值之和不等于卷面满分 | 回到 2.2 检查大题分组 |
最后补一个实操技巧:把g的取值、单位制、以及每道题用到的公式编号一起写进 JSON 的元数据里,别只存答案。同一份上海上海市实验学校西校的章节检测卷,下次改版重排时,只要版面结构没大改,第 2 章的切分规则和第 3 章的公式映射都能直接复用,只有 4.2 的数值验算需要重新跑一遍。遇到解析里出现两个不同的g值时,先查 3.3 的符号映射表,再回头确认题干给出的重力加速度取 9.8 还是 10,这一条能挡掉大半的数值对不上。
本文还有配套的精品资源,点击获取