MinerU 是 OpenDataLab (上海人工智能实验室)开源的文档解析工具。它把 PDF 、扫描件、图片和 Office 文档转成结构化的 Markdown 与 JSON :先做版面分析,再按阅读顺序重排,表格转 HTML 、公式转 LaTeX 、扫描件叠一层 OCR 。可本地私有化部署,也能批量跑,常被用来给 RAG 和知识库准备数据。
一、安装 1.1 装基础包还是 full 选择取决于你的硬件。基础包走 ONNX 加 llama.cpp , CPU 和 Apple Silicon 上都够用;有 NVIDIA 卡、想要高吞吐,再上 full , Linux 下它会自动装 vLLM 。
# 基础包,先跑起来用这个 uv pip install -U "mineru>=4.0,<5" # 有 NVIDIA 卡、要吞吐再上这个 uv pip install -U "mineru[full]>=4.0,<5" # 安装全局工具 uv tool install "mineru>=4.0,<5.0" --python 3.12 习惯 pip 的话把 uv pip 换成 pip 即可,但建议先建虚拟环境。 Python 版本要求 3.10 到 3.14 。
1.2 先把模型源切到国内 模型不在 pip 包里,首次运行会下载,体积从几百 MB 到几个 GB。默认源是 auto ——先试 Hugging Face,不通再退 ModelScope 。国内网络不切源的话,第一次下载能卡到怀疑人生:
export MINERU_MODEL_SOURCE=modelscope # Windows Power Shell: $env :MINERU_MODEL_SOURCE= "modelscope"
也可以提前下好:
mineru-kit models download --tier standard 装完务必跑一下 mineru-kit models show 。它会告诉你当前实际生效的是哪套小模型后端、哪个 VLM 引擎。这一步很容易被忽略:有人以为自己一直在用 GPU ,其实全程在跑 CPU ,只是慢得莫名其妙。
1.3 第一个脚本 准备一个 PDF (下面假设叫 paper.pdf ),创建 main.py :
from pathlib import Path from mineru.parser import parse result = parse( "paper.pdf" , tier= "standard" , ocr_mode= "txt" , # 文本型 PDF 不做 OCR;扫描件改成 "ocr" page_range= "1-20" , # 页码从 1 开始,闭区间 ) Path( "paper.md" ).write_text(result.markdown(), encoding= "utf-8" ) Path( "paper.json" ).write_text(result.to_json(), encoding= "utf-8" ) print (result.markdown()[: 300 ]) 运行 python main.py 。第一次会下载模型,等几分钟;之后目录里会多出 paper.md 和 paper.json 。打开前者能看到带 # 标题、 HTML 表格和 LaTeX 公式的 Markdown ,后者是给程序用的结构化数据。
tier 直接决定成本: flash 走原生文本提取,不动大模型; standard 才会拉起小模型和视觉语言模型。
二、具体执行 2.1 两个入口,默认值不一样 命令行有两个入口,行为不同,混用会踩坑。
mineru-kit parse 是无状态的,不碰数据库、不建缓存,默认把整个文档解析完。适合一次性转换和脚本流水线。
mineru parse 是文档库客户端,会建缓存、建索引, PDF 默认只读前 10 页。它适合"反复读同一份文档"的场景,也支持续读和检索。
# 单文件转 Markdown,standard 档:小模型 + VLM mineru-kit parse paper.pdf -o paper.md --tier standard # 批量目录出 zip;显式 --pages all,绕开两个入口的默认值差异 mineru-kit parse ./docs -o ./out --format zip --pages all # 只是入库检索,flash 档不跑 VLM,速度快一个量级 mineru-kit parse manual.pdf -o manual.md --tier flash 记住这条: 要全量就写 --pages all ,否则 PDF 可能只解析了一部分。
2.2 四个档位怎么选 flash basic ONNX standard advanced
standard 背后是 MinerU2.5-Pro-2605-1.2B 。模型不大,但它是在"文档解析"这个任务上专门训练过的,不是拿通用多模态模型硬套。这一点很关键——专用小模型在版面上更稳,显存也省得多。官方数据里,这个 1.2B 模型在 OmniDocBench 上的表现超过了 Gemini 2.5 Pro 、
GPT-4o 这类大模型。
PDF 和图片支持全部四档; Office 、 EPUB 、 OFD 、 HTML 、 CSV 这些本身就是结构化的格式,走 flash 的整篇解析,在 Python SDK 里要显式写 tier="flash" 。
2.3 Python API: parse 与 parse_async 4.0 把无状态入口收进了 mineru.parser :
from mineru.parser import parse result = parse( "paper.pdf" , tier= "standard" , page_range= "1-3" ) print ( type (result)) # print (result.markdown()[: 200 ]) ParseResult 上挂了几个常用方法: markdown() 给人看, to_json() / to_dict() 给程序用, structured_content() 给切块用。三者来自同一次解析,不用重复跑。
批量任务用异步入口,注意"一个坏文件不能拖垮整批":
import asyncio from pathlib import Path from mineru.parser import parse_async async def main (): Path( "out" ).mkdir(exist_ok= True ) for pdf in sorted (Path( "docs" ).glob( "*.pdf" )): try : result =
await parse_async( str (pdf), tier= "standard" ) out = Path( "out" ) / f" {pdf.stem} .md" out.write_text(result.markdown(), encoding= "utf-8" ) except Exception as exc: print ( f" {pdf.name} 解析失败: {exc} " ) asyncio.run(main()) 已经身处事件循环里时,直接 await parse_async(...) ,外面不要再套一层 asyncio.run 。
2.4 中间产物才是重点 很多解析服务只给你一份 Markdown,看上去很美,但下游没法用——所有结构都退化成了纯文本。
MinerU 除了 Markdown,还保留 middle.json 和 structured_content.json 。每个块都带 type 、 page_idx 、 bbox 这些字段。有了这层信息,你就能自己写切片逻辑:表格块整体进向量库,公式块单独抽出来检索,页眉页脚按坐标过滤掉。RAG 里最头疼的"表格被切断"问题,在这一层就能解决。
from mineru.parser import parse result = parse( "manual.pdf" , tier= "standard" ) tables, formulas, texts = [], [], [] for page in result.structured_content()[ "pages" ]: for block in page[ "blocks" ]: if block[ "type" ] == "table" : tables.append(block[ "content" ]) # HTML,行列关系还在 elif block[ "type" ] == "equation" : formulas.append(block[ "content" ]) # LaTeX elif block[ "type" ] == "text" : texts.append(block[ "content" ]) print ( f"表格 { len (tables)} ,公式 { len (formulas)} ,正文 {
len (texts)} " ) 按块切比按字符数切靠谱得多,因为边界天然对齐了版面结构。
2.5 三种部署方式 按团队规模选就行。
一个人用,直接命令行,模型下载一次就结束,没必要起服务。
两个以上业务要共享同一个模型,起 V1 API,模型只加载一次,比每个进程各吃一份显存划算得多:
mineru-kit api-server --host 0.0.0.0 --port 8000 --tier standard 文档地址: http://localhost:8000/docs
多卡或多机再上 router ,它会按 --local-gpus auto 自动拉起 worker :
mineru-kit router --host 0.0.0.0 --port 8002 --local-gpus auto 客户端复用同一个解析器实例即可,能力发现、上传、轮询、下载都封装在里面:
from mineru.parser import MinerUApiParser # api_url 填 /v1 之前的根地址;密钥走 MINERU_API_KEY,别写死在代码里 parser = MinerUApiParser(api_url= "http://127.0.0.1:8000" , tier= "standard" ) print (parser.parse( "contract.pdf" ).markdown()) 每次调用会自己打开和关闭 HTTP 会话,不需要手动释放。
第三种是 Gradio 前端, mineru-kit webui 起一个,给不写代码的同事做人工校验。别把它当生产接口用,它只是个可视化的壳。
三、优化处理 3.1 实战一:清理一批标书 这是最能体现价值的一类活:文件多、版式杂、人工处理不现实。
有人接过一批两千多份的标书,双栏、跨页表格、 LaTeX 公式全有。第一版用 pdfplumber 抽文本,读取顺序被打乱,表格退化成只有一列数字。换成 MinerU 跑了一个晚上,第二天打开 structured_content.json ,表格已经是 HTML ,公式已经是 LaTeX ,标题层级也原样保留。
from pathlib import Path from mineru.parser import parse raw, out = Path( "bids" ), Path( "md_out" ) out.mkdir(exist_ok= True ) for pdf in sorted (raw.glob( "*.pdf" )): try : md = parse(pdf, tier= "standard" ).markdown() (out / f" {pdf.stem} .md" ).write_text(md, encoding= "utf-8" ) print ( "ok:" , pdf.name) except Exception as exc: print ( "fail:" , pdf.name, exc) 两千个文件不可能一口气跑完,通常是挂一晚上。第二天先看失败清单,再抽查几份复杂排版的转换质量——重点看跨页表格有没有拼完整、公式是不是变成了 LaTeX。
3.2 实战二:把解析结果接进 RAG 这一步分两半:先按块把内容取出来,再连上向量库写进去。向量库用 Qdrant,装的时候带上 fastembed,embedding 就在本地算,不用另外调接口:
pip install "qdrant-client[fastembed]" 先建集合。向量维度交给 get_embedding_size 去取,省得手抄:
from qdrant_client import QdrantClient, models MODEL = "sentence-transformers/all-MiniLM-L6-v2" COLLECTION = "handbook" client = QdrantClient(path= "qdrant_data" ) # 连服务端就换成 url="http://localhost:6333"
if client.collection_exists(COLLECTION): client.delete_collection(COLLECTION) # 演示用重建;生产请改成增量 upsert client.create_collection( COLLECTION, vectors_config=models.VectorParams( size=client.get_embedding_size(MODEL), distance=models.Distance.COSINE, ), ) client.create_payload_index(COLLECTION, "type" , models.PayloadSchemaType.KEYWORD) 然后解析文档、按块写库。 Document 会在本地算好向量, upload_collection 负责分批写入:
from mineru.parser import parse result = parse( "handbook.pdf" , tier= "standard" ) vectors, payloads = [], [] for page in result.structured_content()[ "pages" ]: for block in page[ "blocks" ]: if block[ "type" ] not in ( "text" , "table" , "equation" ): continue vectors.append(models.Document(text=block[ "content" ], model=MODEL)) payloads.append({ "page" : page[ "page_idx" ], "type" : block[ "type" ], "text" : block[ "content" ]}) client.upload_collection(COLLECTION, vectors=vectors, ids= list ( range ( len (vectors))), payload=payloads) print ( f"入库 { len (vectors)} 个块" ) 检索时同样把问题包成 Document ,还能顺手按 type 过滤——只查表格,或者只看正文:
hits = client.query_points( COLLECTION, query=models.Document(text= "如何配置模型源" , model=MODEL), query_filter=models.Filter( must=[models.FieldCondition(key= "type" , match =models.MatchValue(value= "text" ))] ), limit= 3 , ).points for hit in hits: print ( f"[ {hit.payload[ 'type' ]} ] 第 {hit.payload[ 'page' ]} 页 {hit.score: .3 f} " ) print (hit.payload[ "text" ][: 120 ]) 3.3 成本策略:先 flash 全量,再 standard 精修 全量上 standard 是能跑,但没必要。更务实的做法是两遍走:
from pathlib import Path from mineru.parser import parse # 第一遍:全量走 flash,只建索引,快且便宜 for pdf in sorted (Path( "docs" ).glob( "*.pdf" )): md = parse(pdf, tier= "flash" ).markdown() (Path( "index" ) / f" {pdf.stem} .md" ).write_text(md, encoding= "utf-8" ) # 第二遍:只对检索命中率高的文档,用 standard 重解析 for name in [ "招标文件_A" , "技术方案_B" ]: md = parse( f"docs/ {name} .pdf" , tier= "standard" ).markdown() (Path( "final" ) / f" {name} .md" ).write_text(md, encoding= "utf-8" ) 这套组合拳能省下一半以上的算力。 flash 拿来做召回足够,真正需要进上下文的少数文档再走 standard 。
3.4 什么情况下不要用 三种情况建议绕开。
第一,纯文本 PDF、只要文字不要结构。 pypdf 几十毫秒就能完成,上 MinerU 是拿高射炮打蚊子。
第二,对延迟敏感的在线接口。 standard 一次解析通常是秒级到十几秒级,取决于输入和设备。这种场景应该老老实实做异步任务队列,不要让用户在前端等。
第三,只想"看一眼里面写了什么"。直接 tier="flash" ,别开 VLM。
3.5 许可与合规 3.1.0 起,MinerU 从 AGPLv3 换成了基于 Apache 2.0 的 MinerU 开源许可证 ,商业使用比过去宽松很多。但它是"Apache 2.0 + 附加条款",不是纯粹的 Apache 2.0:比如月活超过一亿、或月收入超过两千万美元时,需要单独取得商业许可;对外提供在线服务时,必须在界面或文档里注明使用了 MinerU。
所以别轻信"随便用"这句口头结论,动手前把仓库里的 LICENSE.md 读一遍,尤其是要拿它做对外产品的时候。
MinerU 之所以流行,不是因为榜单第一,而是因为它同时做到了三件事:能私有化部署、能批量跑、能产出结构化的中间结果。做 RAG 的团队最缺的往往不是"再调一个云 API",而是对数据入口的控制权。
如果只记一件事,记这个: 解析出来的 structured_content.json 比那份漂亮的 Markdown 更值钱 。标题、表格、公式、页脚的边界和坐标都在里面,后续怎么切、怎么检索、怎么过滤,全由你决定。