社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

MinerU , 一个超过的 Python 库

程序员老朱 • 1 周前 • 192 次点击  

 

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 四个档位怎么选

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:.3f}")
    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 更值钱。标题、表格、公式、页脚的边界和坐标都在里面,后续怎么切、怎么检索、怎么过滤,全由你决定。

 


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/202506