上周有个读者甩给我一个场景:手上攒了几十本 PDF 教材,想让 AI 帮忙做成能搜、能查、每句话都能溯源的笔记,但又不放心把整本书传上云。我在 GitHub 上翻到一个刚上线两天的项目,专门冲这个需求来的,索性从代码到实跑全流程走了一遍。
直接说结论:textbook-to-note 是一条"窄而深"的路子——不跟那些万能文档转换器抢地盘,专攻教材场景的双栏还原、图表引用溯源、抽取阶段零 LLM token。我围绕"把一本 PDF 教材转成可 grep 的干净 markdown"实测,一次跑通,0.28 秒转完 3 页。结论是:如果你要系统整理一堆本地 PDF、且在意"每条笔记能查回原文哪一页",这条路值得走;如果你只是偶尔转一两个文件、不在乎溯源,用更成熟的通用转换器就够了。
背景:为什么"喂 PDF 给大模型"没那么简单
把一本 PDF 直接丢给前沿模型,听起来一步到位,实际全是坑:
成本和延迟:一本 600 页的书是一两百万 token,每次提问都重读一遍根本扛不住。 静默丢数据:扫描页、坏字体编码会产出乱码,模型悄悄跳过,你还以为笔记是完整的。 文字错乱:教材大多两栏排版,默认的 PDF 文字抽取会把左右两栏交织在一起,而且"是不是乱码"的检查还检测不出来——因为单个字符本身没坏,坏的是顺序。 图表蒸发:解剖图、分类表、治疗流程图往往是一章里最值钱的部分,纯文字抽取全给你丢了。
作者自述是个执业医师,一个专科就有 40 多本指定参考书,同一个概念散落在好几本书的不同章节里。这个痛点足够真实,也解释了为什么它把"溯源"看得比什么都重。项目地址在 textbook-to-note,MIT 协议,Python 写的。
代码长什么样
拉下来一共 49 个文件,主体是 converter/(PDF→markdown)、figures/(按需抠图)、templates/(笔记模板)、workflows/(写笔记的流程规范)几块。核心依赖只有四个:pymupdf、pdfplumber、Pillow、numpy——全是标准库。更妙的是 OCR、Docling 表格增强、语义索引这些重家伙全被注释在 requirements 里,默认一个都不装,用到哪个手动开哪个。这个克制我挺欣赏。
翻了下代码,有三个设计决策我觉得能拿出来说:
一、分层降级的 OCR 阶梯 抽文字走 fitz 文本 → 本地 OCR → 本地视觉模型 → 前沿视觉,一级比一级贵。关键是每一级前面都塞了一个零 token 的确定性检查(字体风险、字符密度、领域正则),先用规则判断上一级是不是"静默失败"了。代价是代码复杂度上去了,但换来"前沿模型只在真正需要判断时才花钱"。
二、双栏阅读顺序复原 按页宽中线加容差,把文本框分成左栏、右栏、跨栏三簇,再按段落重新拼顺序。妙的地方在于:检测不到两栏、或者栏间有重叠有歧义时,它直接放弃、回退到原始抽取——保证"帮不上忙时输出逐字节不变,能帮上忙时才动手"。这个我后面实测验证了。
三、页框伪表格拒识 pdfplumber 有个坑:会把"内容边框 + 页眉横线"误判成一个 1 列的大表格,然后把整页文字塞进那一个单元格里。项目用"1 列且最大单元格超 500 字符、或占了半页面积"的规则把它丢掉,还留一条审计注释说明为什么丢。只拿列数当门槛,真实的多列表格永远不会误伤。
技术栈一句话:Python 3.11 + fitz/pdfplumber 做确定性抽取,重工具全部可选。整个项目注释详尽到每个魔法常数都带理由和开关环境变量,一看就是给 AI agent 运维设计的。
实际部署和运行
环境准备
pip install -r requirements.txt
结果:一次通过。四个核心依赖拉的都是预编译包,import 全部正常。无 GPU、无本地模型、无索引——最小档位真就是零配置。
造一本"教材"来试
手上没有能公开的真教材,我用 pymupdf 造了一本 3 页的 A4 双栏样本:带一个画了框线的 4×3 表格(模拟"各心腔压力值")、埋了 Fig. 3.1 和 Fig. 3.4 的图片引用、还加了运行页眉。然后跑转换:
python converter/convert.py sample.pdf out.md --book-label "Cardiovascular Physiology Sample"
[OK] 3 pages | 1 tables | 10 fig refs | 4.8 KB
结果:成功,耗时 0.28 秒。 感受:快得没有存在感,但打开输出一看,细节全在。
打开产出的 markdown,四件事逐一验证到位:
页码溯源:每页开头一个 <!-- page N -->标记,任何一句话都能锚回 PDF 的第几页。图表引用标记:全文 10 个 <!-- REF: Fig. 3.1 → see PDF page 1 -->这样的注释,把每处对图、对表的引用都标了出来,下游步骤不用重扫 PDF 就知道去哪找。表格抽取:那个带框线的表格被抽成了干净的 markdown 表,LV / RV / Aorta 三行数值一个不差。 就地标注:表格在它所在页的位置也留了内联标记,位置信息没丢。
验证"双栏还原"是不是真在干活
光看输出不够,我想确认那个双栏重排到底有没有生效,就用它自带的开关做了个对照实验:
T2N_COLUMN_SORT=0 python converter/convert.py sample.pdf out_nocol.md
关掉重排后 diff 两份输出,有表格几何的那一页顺序确实变了,而纯文字页保持完全一致。这正好印证了代码里的设计——它不是无脑重排所有页,而是"这页能理清双栏才动,理不清就老老实实退回默认"。这种克制比"我全给你重排"要靠谱得多,因为乱重排比不重排更糟。
一个小插曲:第一次装依赖时前台命令超时了(预编译包有点大),改成后台跑就好了。这种事不该反复重试同一条命令,换个执行方式才是正解。
竞品横评
这个方向上"大而全"的转换器不少,我挑了四个主流的对比:
| 项目 | Star | 协议 | 定位与特点 |
|---|---|---|---|
| docling | 6.3 万 | MIT | IBM 系,通用文档转 genAI,模型多、偏重 |
| marker | 3.8 万 | Apache-2.0 | 高精度 PDF 转 md/json,深度学习驱动,准但要 GPU |
| markitdown | 16 万 | MIT | 万能格式转 markdown,广但浅,双栏/溯源不是强项 |
| MinerU | 7.5 万 | 待确认 | 复杂文档转 LLM-ready,功能全但配置重 |
有意思的是,textbook-to-note 根本没打算跟这些正面刚。它把 docling 这类工具当成可选的表格增强插件(开个开关才启用),而不是默认依赖。它的差异点很清楚:
场景窄而专:只服务教材——双栏、图表引用、逐条引用溯源,这几件事做到位。 token 经济:抽取阶段零 LLM token,只在"写笔记"那最后一步才花前沿模型。 本地优先:重活全在本机跑,资料不出门。
说白了,它卖的不是"又一个 PDF 转换器",而是"一套把教材变成可信笔记的工作流 + 方法论"。转换只是这套流程的第一步。
路径总结
跑完这一圈,我的判断是:
值得走这条路,如果你——手上有一批本地 PDF 要系统整理、在意每条笔记能查回原文、且愿意接受"抽取归抽取、写笔记归写笔记"这种分阶段的思路。它的最小档位零配置、纯本地、几秒转一本,验证成本极低。 可以考虑竞品,如果你——只是偶尔转个把文件(markitdown 更省事)、或者要处理大量扫描版且有 GPU(marker、MinerU 的深度学习路线更抗造)。 有一点要清醒:这项目才 60 多个星、上线两天,语义索引那块还得靠另一个配套仓库,真要上规模用得做好自己搭索引的准备。但就代码质量而言,它明显高于 star 数暗示的水平,方向也扎实。
它最打动我的其实是那股"防静默失败"的执念——处处假设"上一步可能成功地返回了一个错的结果",然后用零成本的规则去兜底。这个思路,做任何数据管线的人都值得借鉴。
你手上有没有一堆吃灰的 PDF?会想用这种"本地优先 + 逐条溯源"的方式整理一遍吗?评论区聊。
夜雨聆风