干过文档处理的人应该都有类似体会:PDF 这玩意儿,看着整整齐齐,可真要把它变成可编辑的 Markdown,公式、表格、双栏排版每一样都能让你怀疑人生。我之前为了一份两百多页的行业报告,硬是在 PDF 阅读器和编辑器之间来回切换,人工修排版修了两天。后来找到 MinerU(早期项目名叫 magic-pdf),才算把 PDF→Markdown 这条链路真正跑通。MinerU 是一个开源文档解析工具,核心能力是自动把 PDF 转成带结构的 Markdown,内部包含版面分析、公式识别、表格重建和 OCR,目前版本已经迭代到 3.4.5。这篇文章就围绕 MinerU 的部署、CLI 使用、Python 集成和版本升级踩坑展开,适合正在做文档结构化解析、RAG 知识库、论文阅读或者批量资料整理的人参考。整个项目上手不算难,但有几个坑如果没人提前提醒,你真得花好几个晚上去踩。
1. 先搞清楚 MinerU 是什么:从 magic-pdf 改名到 3.4.5,中间发生了什么
1.1 magic-pdf 到 MinerU:不只是换个名字
MinerU 这个项目最早是以 magic-pdf 的名字出现的。那时候我用 pip 安装的包叫 magic-pdf,命令行入口也叫 magic-pdf,操作方式跟现在有不少区别。后来项目整体更名为 MinerU,定位也不只是"把 PDF 转成文本",而是想做一整套文档结构化解析方案。最直观的变化是命令行入口从 magic-pdf 变成了 mineru,配置文件和模型管理方式也都跟着调整。发展到 3.4.5 这一版,扫描版 PDF 的 OCR、公式转 LaTeX、表格转 Markdown 这些任务已经整合进同一条管线,不用再像早期版本那样手动拼装多个模型。
这种改名很容易造成困惑。网上搜 magic-pdf 出来的教程,有一半是旧版本,命令直接在新环境里跑不通。我自己的习惯是:看到教程里写 magic-pdf xxx 时,先确认它对应的版本,别照抄;如果是面向新版 MinerU 的内容,多半应该用 mineru 命令。3.4.5 这一版让我印象最深的变化是解析稳定性提升了不少,尤其是双栏论文的阅读顺序处理,不再是简单按纵坐标排序,而是会结合版面结构判断先读哪一栏。这个改进对学术场景影响很大,因为论文双栏排版太常见了。
1.2 解析管线里到底跑了什么模型
MinerU 不是传统意义上"读 PDF 文本"的工具。它内部会先做版面检测,把每一页切分成标题、正文、图片、表格、公式等不同区域,再对每个区域走对应的识别模型。整个流程大致可以理解为四个环节:
- 版面分析:先圈出每个区域的位置和类型,解决"这篇 PDF 里哪里有标题、哪里有正文、哪里有图表"的基本问题。
- 文字识别:对正文区域做高精度文字提取;如果是扫描件,就走 OCR 模型,这也是它能处理非数字版 PDF 的原因。
- 公式识别:把行内公式和独立公式识别出来,输出成 LaTeX 语法,这比截图保存公式实用太多。
- 表格重建:识别表格的行列结构,输出成 Markdown 表格或者带结构的 JSON,后续可以直接喂给知识库。
用一句话类比的话,普通 PDF 工具是让机器"逐字读"页面,MinerU 会让机器先"看版式"再决定怎么读。这个先看版面再分配任务的思路,决定了它输出的 Markdown 是分块、有结构的,而不是一段连续的文字流。
1.3 和普通 PDF 文本提取工具的本质区别
很多人在问 MinerU 和 PyPDF2、pdfplumber 这类库到底有什么区别。简单说,传统工具拿到的只是 PDF 内部已有的文本对象和坐标,本身不做"理解",遇到没有文本层的扫描件就无能为力;MinerU 则是用深度模型重新分析页面内容,相当于给 PDF 做了一次视觉理解。我整理过一张对比表,能直观看到差异:
| 维度 | PyPDF2 / pdfplumber | MinerU |
|---|---|---|
| 版面分析 | 基本没有 | 有,会分区块 |
| 公式识别 | 不支持 | 支持,输出 LaTeX |
| 表格重建 | 只能拿文本和坐标 | 输出结构化 Markdown/JSON |
| 扫描版 OCR | 不支持 | 支持 |
| 输出格式 | 文本、坐标 | Markdown、JSON、图片 |
| 部署门槛 | 轻量,pip 即可 | 依赖 PyTorch 和模型权重,偏重 |
这个对比不是想说明传统库没用,而是说它们解决的是不同问题。如果你只是从 PDF 里抽几行文字,用 pdfplumber 就够了;如果你要批量整理成 Markdown 做二次处理,MinerU 的效率会高出一个量级。
2. 环境准备与模型下载:两种部署路线,以及我最常被问到的问题
2.1 Python 环境和镜像源:先把这个搞稳
MinerU 的依赖包括 PyTorch、PaddleOCR、一些图像处理库,对 Python 版本有要求。我用下来最稳的是 Python 3.10 和 3.11。Python 3.8 太旧,很多新版本依赖根本没有对应 wheel,pip 会在安装阶段直接尝试源码编译,然后报一堆让你不想看的错误。Python 3.12 在部分版本上也能用,但如果你装的是老版本 magic-pdf,很可能会遇到编译问题。
我强烈建议你在虚拟环境里装,别直接往系统 Python 塞。原因很现实:这个项目的依赖链比较长,跟其他 Python 项目混在一起很容易把包版本搞乱。我自己在 Windows 上常用 conda,创建环境之后再把 pip 指向国内镜像,安装速度会快非常多。
注意:如果网络环境不太理想,先换 pip 镜像再安装,能省掉大量等待时间。镜像源的选择按你本地网络习惯来就行,关键是别让下载卡在超时上。
2.2 安装命令:GPU 与 CPU 的差别在 torch 这一层
安装主包的命令并不复杂,常见的是:
pip install "magic-pdf[full]"装完之后命令行工具是 mineru,而不是 magic-pdf,这点要记住。之所以包名还保留 magic-pdf,主要是因为 PyPI 上的历史包袱,新版官方仓库虽然已经叫 MinerU,包发布名没有直接换掉。如果你要固定版本,可以把版本号写上:
pip install "magic-pdf[full]==3.4.5"接下来要处理的是 PyTorch 和 CUDA 的匹配。MinerU 的推理依赖 PyTorch,你如果本来就有 GPU,先确认自己的 CUDA 版本,然后装对应版本的 torch。用 CPU 跑不是不行,我自己拿一台只有 CPU 的旧笔记本试过,解析一份 30 页的 PDF 要等很久,耗时大概是 GPU 的几十倍。所以如果是长期做大批量解析,尽量准备一张显卡;只是偶尔跑几篇论文,CPU 也能凑合。
另一种情况是已经装了 CPU 版 torch,后来想切换到 GPU。这时候不要直接在原环境里往上叠,先卸载 torch 和 torchvision,再按 CUDA 对应版本重新安装,否则可能出现装了新包但推理还是 CPU 的情况。这个坑在官方 issue 里出现过很多次。
2.3 模型权重下载:别让进度条卡死在 99%
MinerU 需要额外下载模型权重,并不是安装完 Python 包就能直接解析。官方仓库里一般提供了下载脚本,比如 download_models_hf.py 或者 download_models.py,前者走 Hugging Face,后者走 ModelScope。对国内网络来说,走 ModelScope 通常更稳。可以在仓库找对应的脚本直接运行,脚本会把你需要的模型文件下载到本地缓存的模型目录里。
下载完成之后,正常情况下模型文件会放在类似~/.cache/modelscope/hub的目录下,里面能找到一个模型包,目录里包含版面检测、公式识别等子模型。整套模型文件加起来体积不小,下载花多久取决于网络。我最开始的教训是没有先看脚本说明,直接在命令行里硬跑,结果进度条到了 99% 超时,重试了几次才明白脚本是从本地固定目录读文件,不是你想放哪就放哪。后来我都是先指定一个专门的模型目录,比如/data/models/mineru,再把配置指过去,下次换机器也方便。
2.4 配置文件的关键字段
MinerU 运行时会读一个配置文件,里面主要记录模型目录、推理设备、语言等设置。不同小版本对字段名可能有调整,但核心逻辑是差不多的。拿 3.4.5 来说,配置文件里我重点关注几个字段:
{ "models-dir": "/data/models/mineru", "device-mode": "cuda", "languages": ["ch", "en"] }models-dir指向模型根目录,确保路径下面有你下载好的模型子目录。device-mode控制推理设备,cuda或cpu;如果你的机器有 Apple Silicon,某些版本也支持 mps,具体看官方文档。languages影响 OCR 语言识别范围。中英文混排场景我建议写["ch", "en"],只写英文会导致中文识别出乱码,只写中文在纯英文文档上又可能误识别。
配置文件的文件名和存放位置在不同版本里有过变化,升级之后最好重新从官方模板拷一份,而不是拿旧配置硬改。我见过不少报错,最后都定位到配置文件里某个旧字段名上。
3. CLI 实战:一条 mineru 命令,把 PDF 变成可用的 Markdown
3.1 最简单的命令
当环境和模型都就绪之后,命令行使用其实很简单。最基本的命令是这样:
mineru -p input.pdf -o ./output -m local --device cuda-p指定输入 PDF,-o指定输出目录,-m local告诉程序使用本地的模型权重,--device cuda把推理放到 GPU 上。如果没装 CUDA,就把--device改成cpu。
第一次运行时可以先拿一份 5 页左右的 PDF 试水。我一般会选自己电脑里随便一份带公式的论文,先跑通再上大批量,不要一上来就丢几百页文件进去。解析完成后,终端会打印输出路径,这时候你去 output 目录看结果,能看到一个以输入 PDF 文件名命名的子目录,里面是最终产物。
这里有个容易忽略的点:输出目录不会自动清理同名文件。如果你对一个 PDF 重复执行多次,最后一次的结果会覆盖同名内容,如果上一次解析到一半报错退出,还可能留下残缺的中间目录。所以我在脚本里会先把目标子目录删掉再执行,避免把旧文件当新结果。
3.2 输出目录里的三种东西
正常情况下,MinerU 生成的每个结果目录里会有几类文件:
- 主 Markdown 文件:最常用的成果,以 PDF 文件名命名,内容和排版基本对齐原 PDF 的结构。
- 同名 JSON 文件:保存了每个区域的位置、类型、识别结果,结构化程度比 Markdown 更高,适合程序二次处理。
- 图片目录:存放从 PDF 中抽取出来的图片,可能是正文插图,也可能是表格被识别后渲染出来的图片。
我用 VS Code 打开 Markdown 时会配合 Markdown Preview 插件直接预览,公式在支持 LaTeX 的预览器里能正常渲染成公式样式,表格也能直接看到对齐效果。这里提醒一句,如果你看到 Markdown 里表格散成多段,不一定是 MinerU 解析错了,很可能是预览器不支持复杂表格语法。先看原始 Markdown 内容,再判断是否要人工修正。
3.3 扫描件场景:OCR 的开关与语言设置
很多人拿 MinerU 处理的是扫描版 PDF,页面本身是图片,没有文本层。这时候命令行一样能跑,因为它会走 OCR 流程。但要保证 OCR 的识别质量,得注意两点。
第一点是语言配置。扫描件里的中文字符必须依赖中文 OCR 能力,如果配置里把语言设成了纯英文,输出结果会惨不忍睹。我建议统一配置成["ch", "en"],在混排文档里表现最稳。
第二点是输入图片质量。扫描件如果太糊或者页面歪斜,OCR 效果会明显下降。我自己的做法是,在进 MinerU 之前先用图像处理手段做一遍纠偏和锐化。很多人搜“pdf歪斜校正纠偏”,就是因为扫描件不预处理的话,任何 OCR 工具都很难救回来。MinerU 不是不能处理歪斜页面,而是歪斜角度大时,版面分析阶段就容易把区块判断错,最终 Markdown 的段落顺序也会乱。
注意:如果 PDF 本身是图片型扫描件且清晰度极差,先别急着甩锅给 MinerU。先做预处理,再解析,效果差距非常大。
3.4 批量处理时的资源控制
CLI 支持一次处理单个 PDF,但你真要把一个目录下几十个 PDF 全跑完,直接在循环里调用命令可能有风险。原因在于 MinerU 每个解析进程都会加载模型,如果多个进程同时跑在 GPU 上,显存很容易爆掉。
我的批量方案是外部分批控制:一次只跑一到两个 PDF,跑完再启动下一批。如果机器只有一块显卡,我甚至倾向一次只跑一个,避免显存溢出导致 Python 进程被系统杀掉。对 CPU 环境来说,慢是慢,但好处是内存占用相对可控,跑大批量时不容易 OOM。
相关参数在不同版本里名字略有出入,最稳的方式是先执行mineru --help,对照当前版本的参数再写脚本,不要拿网上旧版本的参数硬套。
4. Python 工作流集成:在脚本里调用 MinerU 的正确方式
4.1 为什么我不建议直接调内部 API
MinerU 本质是个 Python 库,底层确实提供了不少类和函数,理论上你可以直接 import 然后在代码里调用解析流程。但我个人不建议大多数用户这么干,尤其是拿它做长期项目的时候。
原因是这个项目迭代太快,内部模块路径和函数签名经常调整。今天能跑通的 import 地址,下个月升级一个补丁版本可能就变了。如果你的代码和它内部实现深度耦合,每次升级都是灾难。相比之下,CLI 是官方稳定暴露出来的入口,命令参数的变化比内部 API 要小很多,也更容易排查问题。
所以我在自己项目里的做法是:通过 subprocess 调用 CLI,把 MinerU 当成一个独立的解析服务来用。这样既有 Python 自动化能力,又不会把自己绑死在某个内部实现上。
4.2 用 subprocess 做可靠封装
封装起来并不复杂。下面这段代码是我经常用的模板,先删掉可能残留的旧输出目录,再调用 mineru 命令,最后返回生成的 Markdown 文件路径:
import shutil import subprocess from pathlib import Path def pdf_to_markdown(pdf_path, output_dir, device="cuda"): pdf_file = Path(pdf_path) out_dir = Path(output_dir) out_dir.mkdir(parents=True, exist_ok=True) result_dir = out_dir / f"{pdf_file.stem}" if result_dir.exists(): shutil.rmtree(result_dir) cmd = [ "mineru", "-p", str(pdf_file), "-o", str(out_dir), "-m", "local", "--device", device, ] subprocess.run(cmd, check=True) md_file = result_dir / f"{pdf_file.stem}.md" if not md_file.exists(): raise FileNotFoundError(f"parse result not found: {md_file}") return md_file这个小封装已经能应对绝大多数场景。需要注意的是,不同版本对结果目录的命名规则可能不一致,有的会带后缀,有的不带。我在函数里先按不带后缀的规则去取,找不到再 glob 扫描*.md,多一层兜底。这种细节看着小,实际用起来能省很多事。
4.3 解析结果的二次处理:从 JSON 到自定义格式
Markdown 文件适合人看,但如果你要把解析结果接进知识库或者做进一步统计,JSON 往往更有价值。MinerU 输出的 JSON 里保留了每个块的位置和类型,大致结构类似:
{ "page_idx": 0, "blocks": [ { "type": "text", "bbox": [72.0, 62.0, 540.0, 98.0], "content": "这里是一段正文" }, { "type": "table", "bbox": [72.0, 120.0, 540.0, 240.0], "table": "| 列1 | 列2 |" } ] }我经常用它做两件事:一是过滤掉页眉页脚和参考文献区域,只保留正文和表格;二是按类型把图片、公式、表格分别提取出来,做成结构化的索引。这些操作如果只靠处理 Markdown 文本去做,会很脆弱,因为你得猜格式;用 JSON 里的 type 字段过滤,逻辑清晰得多。
4.4 并发与失败重试
批量解析时我会用 Python 的concurrent.futures控制并发。考虑到显存限制,GPU 环境下我最多让两个解析任务同时运行,CPU 环境下可以适当放宽,但要留意内存使用。
另一个常见问题是偶发失败。有些超大 PDF 在解析过程中可能因为底层 C++ 扩展或内存问题导致进程崩溃,退出码非零。这种情况下,最简单有效的手段是捕获到非零退出码后,把文件记入失败清单,等待几秒再重试一次。重试依然失败的文件,再单独人工检查。我在实际批量处理中,重试一次的成功率已经相当高,也说明这类失败大概率是资源竞争导致的偶发问题,而不是文档本身无解。
5. 升级到 3.4.5 的迁移笔记:命令、配置与一个真实的排查案例
5.1 命令名和配置文件的变化
从旧版本升级到 3.4.5,最直观的差别就是命令名。早期版本里你是这样跑的:
magic-pdf -p input.pdf -o output_dir到了新版,这个命令直接不存在了,换成:
mineru -p input.pdf -o output_dir -m local第一次升级的人经常在这里栽跟头:明明按照旧教程装了新包,运行旧命令时提示 command not found,还以为是安装失败。其实不是没装好,是入口改了。
配置文件也经历了类似变化。旧版本可能叫magic-pdf.json,新版本推荐直接拉官方模板,不要继续沿用旧文件。因为字段名的兼容并不是无限的,某些旧字段在新版读取时会被忽略,导致你以为配好了,实际走的是默认值。
5.2 模型目录不兼容是升级后的第一坑
另一个容易翻车的地方在模型。MinerU 3.x 对模型目录结构有调整,如果你只是把新版本 Python 包装好,却还指着旧版本的模型目录,启动时大概率会报模型加载失败或者加载到不匹配的权重。我见过不少人拿着 0.x 时期下载的模型文件夹去配 3.4.5,折腾了大半天,最后发现重新下载模型才是正解。
注意:升级大版本后,最省心的做法是先备份旧配置,然后把旧模型目录改名,重新运行官方下载脚本。本地模型文件和自己写过的业务代码不同,没有"保留"的必要,它们只是权重文件而已,删了重下更安全。
5.3 一个完整排查过程:从报错到恢复解析
我这里分享一次真实踩坑的排查过程。那次是内部脚本从旧命令切到 3.4.5 之后,跑一个 40 页的 PDF,前几页正常,到中间直接报错退出,日志最后几行指向某个模型推理阶段。
我的排查链路是这样的:
- 先把命令换成最简单的形态,不加任何自定义参数,用同一份 PDF 复现问题。这一步确认是不是脚本里的多余参数引起的。
- 简单命令依然复现,说明问题不在脚本,在配置或者模型。
- 看日志里的模型加载信息,发现它实际加载的模型路径还是指向旧版本下载目录,而配置里写的却是新路径。这说明系统里存在两份配置,命令行参数覆盖了一份,程序又在另一个位置读到了旧配置。
- 清理掉旧配置文件,只保留新模板,并把 models-dir 指到重新下载好的模型目录。
- 再跑同一个 PDF,正常通过。
那次问题其实是"配置读到两份"加"模型目录过期"叠加导致的,只看报错信息根本看不出来。所以我的经验是,升级之后先清理旧配置、旧模型,再开始测试,能避免大量可疑的偶发问题。
5.4 历史归档方案的兼容性处理
如果你已经用旧版本跑过大量 PDF,手上有成批的历史 Markdown 和 JSON,升级到 3.4.5 后要注意:新旧版本生成的 JSON 结构不一定完全兼容。虽然 Markdown 文件是文本,怎么都用得了,但 JSON 里的字段名如果有变化,二次处理脚本可能会崩。
我现在会在每次解析结果里额外写一个版本元信息,记录用的是哪个版本的 MinerU。以后如果需要批量重跑历史文档,至少能知道哪些结果可以直接用,哪些需要重新解析。这个习惯刚开始看不出价值,等结果积累到几百个文件时,就会庆幸当初留了一手。
6. 实拍效果与适用边界:哪些 PDF 可以放心交给 MinerU,哪些不行
6.1 效果最好的几类文件
用了一段时间之后,我慢慢总结出 MinerU 发挥最稳的场景。首先是标准排版的学术论文,尤其是有明确标题层级、公式区、独立图表的论文。这类文档结构规整,MinerU 的版面分析成功率很高,公式转成 LaTeX 后几乎可以直接用,表格也能还原成规整的 Markdown 表格。
其次是技术手册和产品文档,这些文档虽然不一定有复杂公式,但通常有大量代码块、列表、多级标题,MinerU 把它们转成 Markdown 后,结构保留得相当好。配合 VS Code 的 Markdown 插件预览,基本能做到"导出即用"。
中英文混排的文档也没问题,只要 OCR 语言配置正确。我自己做过一个测试,把一份中英混排的会议材料转完,中文和英文段落顺序都正确,没有出现常见的语言串行问题。
6.2 容易翻车的场景
以下几类文件我建议先降低预期,或者提前做预处理:
- 清晰度很差的扫描件。模糊的字会直接导致 OCR 识别错误,版面分析也容易把相邻区域合并或切错。
- 页面歪斜严重的扫描件。歪斜会让坐标框偏移,轻微歪斜可能还行,明显歪斜最好先纠偏。
- 复杂水印和彩色底纹。水印文字会被当作正文识别,彩色底纹干扰版面区域切分。
- 多栏报刊式排版。大量矩形色块、跳转链接、广告位,Markdown 结构会乱。
- 超大 PDF 或者上千页的文件。解析时间和内存消耗都非常高,中途崩溃概率增大。
- 带密码保护的 PDF。需要先解除密码再交给 MinerU。
表格方面,简单规整的表格效果很好,但带有大量合并单元格的复杂表格,识别结果往往只能作为初稿,需要人工调整。跨页表格也经常被拆成多个片段,合并起来比较费劲。
6.3 和常见工具的比较
很多人在考虑要不要从在线转换工具迁移到 MinerU。在线工具确实方便,但往往需要上传文档,对涉及隐私的资料不太友好,而且多数免费工具只提供低质量输出。离线部署 MinerU 之后,文档不出本机,解析质量也更稳定。下表是我对这些方案的主观评估:
| 方案 | 版面分析 | 公式 | 表格 | OCR | 隐私 | 批量 |
|---|---|---|---|---|---|---|
| MinerU 本地部署 | 好 | 好 | 中 | 支持 | 不出本机 | 适合脚本化 |
| PyPDF2 / pdfplumber | 无 | 不支持 | 不支持 | 不支持 | 本机 | 单元级 |
| 在线转换网站 | 弱 | 弱 | 弱 | 部分支持 | 需上传 | 不推荐 |
这里没有贬低 PDF 工具库的意思。你只是要抽几段文字,何必部署一套深度学习模型。但如果你需要的是结构化文档解析,MinerU 这个方向是没错的。
6.4 我现在的使用习惯
最后分享一些实际操作中的小习惯。我在解析前会先看一遍 PDF 是否需要预处理:页面歪斜就纠偏,扫描件太暗就增强对比度,页眉页脚如果会干扰正文,会先想办法去掉。解析完成后,我不是直接看 Markdown,而是先扫一眼 JSON 里的块类型分布,确认有没有大量块被误判成图片,如果有,就说明这台 PDF 的某些区域可能有问题。
公式和表格我再怎么强调抽查都不为过。公式识别虽然已经很好,但偶尔会把上下标位置写反,表格的列对齐也偶尔出问题,尤其带合并单元格时。把这些抽查工作做成一个固定环节,就能长期拿到稳定的结果。
另一个习惯是保留 JSON。Markdown 是给人看的,JSON 是给程序用的。我把 JSON 保留到所有人工修订完成之后再清理,而不是解析完顺手删掉。后续如果有人在 Markdown 里改错了一句,还能从 JSON 追溯原始识别结果。