扫描版 PDF 转 Markdown
把没有文字层的扫描版 PDF(整本书、教材、影印档案)转换成结构化 Markdown 全文:
逐页 OCR → 按版面重排(处理双栏)→ 按章切分成带页码锚点的 Markdown → (可选)对图表页做视觉读图还原。
何时使用
用户上传的 PDF 是扫描版(打开后无法选中、复制文字),且希望得到可阅读、可检索、可交给 AI 阅读的文本时。
典型说法:"扫描件转文字""图片 PDF 提取文字""把这本扫描书转成 MD""OCR 一下这份档案"。
如果 PDF 本身带文字层(能选中复制),不要用本技能——直接用 pymupdf 提取即可,更快更准。
工作流
第 0 步 · 先判断是不是扫描版
python3 -c "import fitz; d=fitz.open('xxx.pdf'); print(repr(''.join(d[i].get_text() for i in range(min(10,d.page_count)))[:200]))"
输出近乎空字符串 → 扫描版,需要 OCR。输出成段文字 → 有文字层,直接提取,无需本技能。
第 1 步 · 装依赖
pip install pymupdf rapidocr-onnxruntime
rapidocr-onnxruntime是纯 Python 的中文 OCR,模型随包分发,不依赖系统级组件;- 若沙箱/环境被重置,跨会话需重新安装(报
ModuleNotFoundError时先重装)。
第 2 步 · 逐页 OCR(长文档必须后台运行)
# 短文档(几十页)可直接前台跑
python3 scripts/ocr_pdf.py input.pdf out.txt --dpi 170
# 长文档(整本书)后台跑,避免前台超时
nohup python3 scripts/ocr_pdf.py input.pdf out.txt --dpi 170 > ocr.log 2>&1 &
- 输出文本每页以
===== PDF第N页 =====分隔; - 进度写入
out.txt.progress.json,中断后重跑同一命令自动从断点续传; - 耗时约 7–9 秒/页(170 dpi);600 页的书约 70–80 分钟。先小范围试跑几页确认质量,再整本跑。
第 3 步 · 整理为 Markdown
python3 scripts/build_md.py out.txt book.md \
--title "《书名》OCR 全文" \
--source "作者/译者/出版社/版次" \
--titles titles.json \
--front-pages 20
- 按页眉中的章节号(默认匹配"第 N 章")把页面归到各章;
- 每章标题下插入
<!-- PDF第N页 -->页码锚点,便于回溯原文定位; titles.json(可选)形如{"1": "全球化", "2": "国家差异"},把章号映射为章名;- 英文书用
--chapter-regex "Chapter\s*(\d+)"。
第 4 步 · 质检与交付
- 随机抽几页比对原 PDF,确认正文连贯;
- 文件头务必保留"OCR 自动识别,引用原文请以原书为准"的声明;
- 提醒用户:双栏页面(如章首案例、论文摘要)可能有个别左右栏交错,属已知局限;复核或引用核心内容时以原书 PDF 为准。
进阶 · 图表页视觉读图(可选增强)
OCR 只认"字"不认"图"——图表、图片、复杂表格会被打散成错位的零散文字(如"1元1400 / 美1200 / 亿1000"其实是"亿美元 + 刻度1400/1200/1000"的残骸),读起来费劲、也无法检索。对这类页面,用视觉读图把它还原成结构化文字/表格。
判断:纯正文文字 OCR 已足够;表格、图形、图片页建议做。
四步:
# 1. 定位含图表的页(按"图N-M""表N-M"标记)
python3 scripts/find_charts.py book_ocr.txt
# → 含图表的页(PDF 页码,1 基): [28, 33, 36]
# 2. 渲染成高清图(视觉读图建议 200 dpi)
python3 scripts/render_pages.py book.pdf 28 33 36 --dpi 200 --outdir ./pages
# 3. 把每张 page_N.png 交给"具备视觉能力的模型"看,要求输出:
# 图号 + 标题 + 类型 + 表头/坐标轴及单位 + 图例 + 数据 + 资料来源 + 趋势结论
# 4. 留存原图并生成索引(做到"文字为主、原图为证")
python3 scripts/make_image_index.py book_ocr.txt ./pages 图表页原图索引.md --pdf-name "《书名》"
# → 产出索引表:PDF 页码 | 图表编号 | 原图文件 | MD 锚点
回写:在该页内容后追加一个 > 【图表还原 · 视觉读图】 块,保留原始 OCR 文本以便追溯。
留存原图(关键一步):光有文字描述不够——文字是"二手"的,且图形数据只能目测。务必把图表页的原图一并留档(render_pages.py 产出的 PNG),并生成上面的索引,与 MD 配套交付。道理是:文字给我快速理解,原图给我精确核验;这样即使日后脱离原书,也不丢精度。交付形态建议为"MD + 图表页原图 + 原图索引"打包给出。
⚠️ 准确性分层(务必遵守):
| 内容 | 可信度 | 要求 |
|---|---|---|
| 表格(清晰印刷数字) | 较高 | 仍须抽验关键数字 |
| 图形数据点(柱高/折线位置) | 低(模型只能目测) | 不可作为精确数据,标注"估计值",只取趋势与结构 |
| 标题/图例/单位/来源 | 较高 | 可采信 |
铁律:视觉读图产出的一切数值,正式引用一律以原书为准。"看似合理的错误数字"比乱码更危险。
跨平台可用性:
- 宿主 Agent 有视觉(多模态)能力(如 ima、WorkBuddy、Claude Code)→ 直接读页面图,无额外依赖;
- 宿主无视觉能力 / 纯文本环境 → 保持 OCR 输出,并在图表页显式标注"⚠️ 图表区 OCR 不可靠,请查阅原书";
- 需要批量或宿主无视觉能力时 → 可调用外部视觉模型 API(需自备 Key)。
主流程(OCR)本身零视觉依赖、任何平台可跑;视觉读图是可选增强,有则加持、无则降级。
详见 references/charts-vision.md。
脚本
| 文件 | 用途 |
|---|---|
scripts/ocr_pdf.py |
逐页 OCR + 双栏重排 + 断点续传,产出带页码分隔的文本 |
scripts/build_md.py |
把 OCR 文本按章整理成带页码锚点的 Markdown |
scripts/find_charts.py |
从 OCR 文本定位含图表/图片的页 |
scripts/render_pages.py |
把指定页渲染成高清图,供视觉读图 |
scripts/make_image_index.py |
生成"图表页原图索引",把 MD 图表位置与原图文件绑定 |
使用示例
例 1 · 整本扫描教材
> 用户:"把这份 600 页的扫描版教材转成 MD。"
> 第 0 步确认无文字层 → pip install pymupdf rapidocr-onnxruntime → 先 --end 5 试跑检查质量 → nohup python3 scripts/ocr_pdf.py book.pdf book_ocr.txt & 后台整本跑 → 完成后 build_md.py 按章整理 → 抽检、加声明、交付。
例 2 · 单份扫描档案(几十页,单栏)
> 用户:"这份扫描的会议纪要转一下文字。"
> 第 0 步确认 → 前台 python3 scripts/ocr_pdf.py scan.pdf out.txt → 单栏会自动按纵向顺序输出,build_md.py 不传 --titles 也能用。
例 3 · 带图表的教材(要求图表也能读)
> 用户:"这本扫描教材里的图表 OCR 出来是乱码,能弄好吗?"
> 先按主流程 OCR → find_charts.py 定位图表页 → render_pages.py 渲染成图 → 视觉模型读图 → 把还原结果写回 MD(并标注数据为估计值)。
参数与排查
- 参数调优(dpi、分栏阈值、章节正则):见
references/parameters.md - 常见问题(依赖缺失、双栏交错、乱码、超时):见
references/troubleshooting.md - 图表视觉读图(流程、准确性分层、跨平台):见
references/charts-vision.md
边界
- 只做"扫描版 PDF → Markdown";带文字层的 PDF 不需要 OCR;
- 不负责翻译、排版美化、PDF 重新生成;
- OCR 结果会有识别误差,不保证逐字准确,正式引用须回到原书核对;
- 图表/图片页需借助宿主视觉能力做视觉读图,纯文本环境无法还原图形。