跳转至

PDF 转在线练习题:问题与解法

状态: ✅ 已完成

创建日期: 2026-03-05 最后更新: 2026-03-05


背景

将 AP World History 的 PDF 试卷(875道选择题,400页)转换为 Markdown 练习文档,过程中遇到大量图片映射、页码偏移、答案提取等问题。本文记录完整的问题清单、根因分析和解决方案,供后续类似 PDF 转练习题场景参考。

源文件

文件 说明
题目 PDF(400页) 无答案标记的干净试卷,包含图片和题目文本
答案 PDF(414页) 带绿色高亮标记正确答案,排版因高亮背景色膨胀多出14页

产出文件

路径 说明
02-academic/AP-World-Test/mcq_unit{1-9}_part{a-d}_20260304.md 23个 Markdown 练习文件,共875题
02-academic/AP-World-Test/img/p{N}_fig{M}.png 163张从题目 PDF 提取的图片
scripts/ap_history/question_index.json 完整索引:每题的答案PDF页码、题目PDF页码、图片、答案、验证状态
scripts/ap_history/answer_key.json 878条从答案 PDF 高亮提取的答案
scripts/ap_history/reextract_ap_world_images.py 8阶段图片提取+映射主脚本
scripts/ap_history/fix_ptags_and_verify.py 页码修正+答案提取+验证+索引构建脚本
scripts/ap_history/spot_check_20.py 20题随机抽查验证脚本

问题一:图片大量重复和错误映射

现象

首次提取得到 275 张图片,但 MD5 去重后只有 143 个唯一哈希。同一张图以不同文件名出现在多个题目中(如 unit1_q12_fig1.pngunit1_q13_fig1.png 内容完全相同)。

根因

PDF 内部通过 xref(交叉引用)复用图片对象。同一个 xref 被多个页面引用时,按页面逐一提取会得到重复文件。原始脚本按"单元+题号"命名图片,但题号在不同 section 中会重启(如 unit3 和 unit4 都有 Q1),导致命名冲突和错误关联。

解法

  1. 删除全部旧图片,从头提取
  2. 按 PDF 文件页码命名p{页码}_fig{序号}.png(如 p67_fig1.png = 题目PDF第67页的第1张图)
  3. 用 PyMuPDF 的 page.get_images() 提取,配合 MD5 去重
  4. 最终得到 163 张唯一图片

关键原则

不要使用 PDF 内容中的页码("Page 6 of 10"),直接使用 PDF 文件的绝对页码(1-indexed)。 内容页码在不同 section 重启,会造成混乱。


问题二:图片与题目的对应关系

现象

图片被映射到了错误的题目。比如一张 Mughal 清真寺照片出现在了一道关于蒙古扩张的题目前面。

根因

PDF 中图片的位置规律是:图片下方的题目才是使用该图片的题目。但自动映射脚本有时把图片关联到了上方的题目。

对于多图页面(同一页有两张图,对应不同题目),简单的"最近题目"启发式会出错。

解法

  1. 贪心算法自动映射:对每页的图片,找到其 Y 坐标下方最近的题号
  2. 对 14 张无法自动匹配的图片,手动指定映射关系(CLOVE 图表、Martellus 地图等特殊案例)
  3. 修复后运行孤儿检查:确保 0 个未引用图片、0 个断链引用

问题三:P-tag 页码偏移(108题)

现象

Markdown 中的题目标记 **P68-Q42.** 表示"答案PDF第68页的第42题",但实际查看答案 PDF 发现 Q42 在第 69 页。

根因

答案 PDF 因为对正确答案加了绿色背景高亮,排版膨胀(400页→414页)。部分页面上的内容被挤到下一页,导致原始生成脚本记录的页码比实际小 1。

107/108 个错误的偏移量都是 +1,1个是 +2。

排查方法

# 从答案 PDF 提取所有题号及其所在页码
for page in answer_pdf:
    for match in re.finditer(r'(?:^|\n)\s*(\d+)\.\s*\n', page.text):
        qnum = int(match.group(1))
        actual_page_map[(page_num, qnum)] = True

# 与 markdown 中的 P-tag 逐一比对
for md_tag in all_markdown_tags:
    if actual_page != claimed_page:
        mismatches.append(...)

解法

逐一将 108 个错误的 P-tag 更新为答案 PDF 的实际页码。


问题四:答案提取——跨页匹配

现象

从答案 PDF 的绿色高亮提取正确答案时,第一版脚本提取了 752 条(遗漏 123 条)。第二版修复后提取 742 条,但出现 105 个答案与 markdown 不匹配。

根因

答案 PDF 的高亮检测面临两个问题:

问题 A:高亮颜色格式

答案行有两种绿色绘图: - 浅绿背景:fill=(0.902, 1.0, 0.902) — 整行背景色 - 深绿边框:fill=(0.228, 0.568, 0.247) — 字母周围的边框

文本内容是 (D) 而不是单独的 D,正则需要匹配 ^\(([A-E])\) 而非 ^([A-E])$

问题 B:答案选项跨页

一道题的选项可能从第 N 页延续到第 N+1 页,高亮出现在 N+1 页但题号在 N 页。按页独立处理会把答案分配给 N+1 页上的下一题。

解法:全局 Y 坐标两趟扫描

Pass 1: 扫描全部页面,收集两类数据点
  - 题号位置: (global_y, page, qnum)    # global_y = page_idx * PAGE_HEIGHT + local_y
  - 高亮位置: (global_y, page, letter)

Pass 2: 对每个高亮,在全局 Y 轴上找"最近的上方题号"
  → 自然处理跨页情况

最终提取 878 条答案,覆盖全部 875 题。


问题五:答案 PDF 中的"Scoring Guide"

现象

排查页码偏移时,怀疑答案 PDF 有独立的 "Scoring Guide" 分隔页导致页码错位。

实际情况

"Scoring Guide" 是每页右上角的水印标签,414页都有,不是独立分隔页。

答案 PDF 中有 40 个无题号页面(纯图片/stimulus 页),这些才是题目 PDF 与答案 PDF 页码差异的来源之一。另一个原因是高亮背景色导致文本排版膨胀。


验证流程

自动验证

运行 fix_ptags_and_verify.py: - Phase 1:从答案 PDF 提取 863 个题号标记 - Phase 2:修正 markdown 中的 P-tag 页码 - Phase 3:用全局 Y 坐标法提取高亮答案 - Phase 4:构建含双页码的 answer_key.json - Phase 5:逐题比对 markdown 答案与 PDF 高亮 - Phase 6:生成 question_index.json 完整索引

视觉抽查

运行 spot_check_20.py: 1. 随机选取 20 道有图片的题目 2. 渲染题目 PDF 对应页为 PNG 3. 对比渲染图与 markdown 中引用的图片是否一致 4. 对比答案是否与 answer_key.json 匹配

最终结果

指标 数值
总题目数 875
答案全部匹配 875/875 (100%)
图片引用 162题有图片,0断链,0孤儿
P-tag 修正 108个
答案修正 14个

关键经验

  1. PDF 页码一律用文件绝对页码,不要用 PDF 内容中的 "Page X of Y"
  2. 图片命名用页码p{page}_fig{n}.png),不要用题号(题号跨 section 重启)
  3. 图片对应规则:图片下方的题目才是匹配的题目
  4. 答案 PDF 与题目 PDF 页码不同步:高亮/批注会改变排版,导致页面膨胀
  5. 跨页答案提取:必须用全局坐标匹配,不能按单页独立处理
  6. PyMuPDF 绘图颜色page.get_drawings() 返回 fill 字段为 RGB 元组,需要容差比较(abs(r - target) < 0.02
  7. 验证必须多维度:自动文本比对 + 视觉抽查 + 索引完整性检查