主流 PDF 解析工具横评:MinerU vs Unstructured vs LlamaParse vs PyMuPDF

0 阅读13分钟

本文适合谁看: 正在选型文档解析工具的 AI 工程师、RAG 系统开发者、Agent 工作流搭建者。
测试范围: 4 款主流工具在真实场景下的准确率、速度、格式支持、LLM 集成能力全面对比,含可复现代码。

为什么要重新比较这四个工具?

PDF 解析是 RAG 系统里最容易被低估的环节——大家花了大量时间调 embedding 模型、优化检索策略,却用 pdfplumber 几行代码把文档读进来,然后奇怪为什么 AI 回答老是出错。 问题往往就在解析层。 2025 年这个赛道发生了根本性的变化:VLM(视觉语言模型)开始主导高精度解析,纯规则方案在复杂文档面前逐渐力不从心。选错工具,下游的 chunk 质量、召回率、生成质量全部会受影响。 这篇文章对比的四个工具分别代表了不同的技术路线:

工具技术路线定位
MinerUVLM + Pipeline 双引擎开源高精度,RAG/学术首选
Unstructured规则 + 轻量模型企业级 ETL,格式最全
LlamaParseLLM 语义解析LlamaIndex 生态,付费云服务
PyMuPDF纯规则,速度极快轻量提取,原生 PDF 文字首选

一、工具基本信息

维度MinerUUnstructuredLlamaParsePyMuPDF
开源 / 商业✅ 开源(Apache 2.0)⚠️ 核心开源,企业版收费❌ 商业 SaaS(有免费额度)✅ 开源(AGPL / 商业双协议)
技术路线VLM(1.2B)+ OCR Pipeline规则 + 轻量 CV 模型GPT-4o 语义解析纯规则,无模型
主要输出格式Markdown、JSONJSON(Element List)Markdown、JSON、结构化纯文本、HTML、JSON、图片
支持格式PDF、Word、PPT、图片、HTML、EPUBPDF、Word、PPT、Excel、HTML、图片、邮件等 20+PDF、Word、PPT、图片、网页PDF、XPS、EPUB(只读文字)
本地部署✅ 支持,推荐 GPU ≥8GB✅ 支持,Docker 部署❌ 仅云端 API✅ 纯本地,无任何依赖
OCR 语言数109 种~30 种依赖 GPT-4o(支持主流语言)~40 种(需额外配置 Tesseract)
GitHub Stars30k+10k+—(闭源产品)5k+

二、Benchmark 对比

2.1 综合解析质量(OmniDocBench)

OmniDocBench 是目前公认最全面的文档解析 benchmark,覆盖 9 种文档类型(学术论文、教材、财报、试卷、报纸、手写体等),由 CVPR 2025 收录。

说明:OmniDocBench 使用"编辑距离归一化误差"评分,分数越低越准确。下表转换为直觉友好的"准确率"形式(1 - 误差)。LlamaParse 和 PyMuPDF 无 OmniDocBench 官方结果,数据来自公开对比测试。

工具综合准确率文字提取公式(LaTeX)表格(HTML)阅读顺序
MinerU 2.590.7%93.2%87.4%85.6%94.1%
Unstructured(API)~68%75%22%61%70%
LlamaParse~76%82%65%74%78%
PyMuPDF(原生PDF)~82%*91%❌ 不支持54%85%

PyMuPDF 对原生可选文字 PDF 表现优秀,但遇到扫描件/图片型 PDF 准确率骤降至 <40%。 关键发现:

  • MinerU 在公式和复杂表格上领先幅度最大,这是 VLM 引擎的核心优势
  • PyMuPDF 在纯文字 PDF 上速度和准确率都很高,但一遇到公式直接放弃
  • LlamaParse 靠 GPT-4o 在语义理解上有优势,但表格结构化输出质量不稳定
  • Unstructured 的免费版(无 GPU)公式识别几乎为 0

2.2处理速度对比

测试环境:A100 80GB GPU / Intel i7-12700 CPU,测试文件:学术论文 PDF(20页,含公式和表格)

工具GPU 模式(页/秒)CPU 模式(页/秒)冷启动时间备注
MinerU(pipeline)2.1 页/s0.3 页/s~15s模型加载耗时
MinerU(vlm)1.8 页/s0.2 页/s~20s精度更高
Unstructured(本地)0.8 页/s~3s无 GPU 加速
LlamaParse~0.5 页/s*<1s受网络延迟影响
PyMuPDF50+ 页/s<0.1s无需模型,极快

LlamaParse 为云端 API,速度受网络和服务器负载影响。
MinerU 速度说明:CPU 模式较慢是主要痛点。生产环境推荐 GPU 部署,或使用 MinerU Open API(云端加速)。Flash 模式(Agent 轻量解析)免 Token 免费,适合快速原型验证。

2.3场景适配矩阵

场景MinerUUnstructuredLlamaParsePyMuPDF
学术论文(含公式)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐(无公式)
财务报告(含表格)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
扫描件 / 手写体⭐⭐⭐⭐⭐⭐⭐⭐⭐
普通原生 PDF(纯文字)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
多语言文档⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
格式多样性(Word/PPT/图片)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐(仅PDF)
处理速度⭐⭐(GPU)/ ⭐(CPU)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
RAG/LLM 集成⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
私有化部署⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
成本💚 完全免费⚠️ 企业版收费🔴 按页计费💚 完全免费

三、代码实战对比

3.1 基础用法:同一份 PDF,四种解法
测试文件:一篇含公式和表格的学术论文 PDF

MinerU(本地 VLM 模式)

MinerU(本地 VLM 模式)
from magic_pdf.data.data_reader_writer import FileBasedDataWriter, FileBasedDataReader
from magic_pdf.data.dataset import PymuPDFDataset
from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze
from magic_pdf.config.enums import SupportedPdfParseMethod

# 读取文件
with open(PDF_PATH, "rb") as f:
    pdf_bytes = f.read()

# 使用 VLM 精准模式
ds = PymuPDFDataset(pdf_bytes)
model_json, sa = doc_analyze(ds, ocr=True)

# 提取 Markdown
from magic_pdf.pipe.UNIPipe import UNIPipe
image_writer = FileBasedDataWriter("/tmp/output/images")
pipe = UNIPipe(pdf_bytes, model_json, image_writer)
pipe.pipe_parse()
markdown_content = pipe.pipe_mk_markdown("/tmp/output/images", drop_mode="none")
print(markdown_content[:1000])

MinerU(Open API / Flash 模式,免 Token)

MinerU(Open API / Flash 模式,免 Token)
from mineru import MinerU

# Flash 模式:无需 Token,免费,≤20页/10MB
client = MinerU()
result = client.flash_extract(PDF_PATH)
print(result.markdown[:1000])

# Precision 模式:需要 Token,支持完整公式和表格识别
client_pro = MinerU("your-api-token")
result = client_pro.extract(
    PDF_PATH,
    model="vlm",          # 推荐 VLM 模型
    formula=True,         # 开启公式识别
    table=True,           # 开启表格识别
    ocr=True,             # 开启 OCR
    language="en",
)
print(result.markdown[:1000])
for img in result.images:
    print(f"  图片: {img}")

Unstructured

Unstructured
from unstructured.partition.pdf import partition_pdf
from unstructured.staging.base import elements_to_json

# 高精度模式(需要安装 hi_res 依赖)
elements = partition_pdf(
    filename=PDF_PATH,
    strategy="hi_res",          # 高精度,使用 YOLOx 布局检测
    infer_table_structure=True, # 尝试识别表格结构
    extract_images_in_pdf=True,
)

# 输出为 JSON
output = elements_to_json(elements)

# 转为 Markdown(需自行处理)
markdown_parts = []
for el in elements:
    if el.category == "Title":
        markdown_parts.append(f"## {el.text}")
    elif el.category == "Table":
        markdown_parts.append(el.metadata.text_as_html or el.text)
    else:
        markdown_parts.append(el.text)

markdown_content = "\n\n".join(markdown_parts)
print(markdown_content[:1000])

LlamaParse

LlamaParse
from llama_parse import LlamaParse
from llama_index.core import SimpleDirectoryReader

# 需要 LLAMA_CLOUD_API_KEY 环境变量
parser = LlamaParse(
    result_type="markdown",
    verbose=True,
    language="en",
    # parsing_instruction 可以用自然语言指导解析行为
    parsing_instruction="提取所有表格为Markdown格式,公式保留为LaTeX",
)

file_extractor = {".pdf": parser}
documents = SimpleDirectoryReader(
    input_files=[PDF_PATH],
    file_extractor=file_extractor
).load_data()

markdown_content = documents[0].text
print(markdown_content[:1000])

PyMuPDF

import fitz  # pip install pymupdf

doc = fitz.open(PDF_PATH)
markdown_parts = []

for page_num, page in enumerate(doc):
    # 提取文字(保留布局信息)
    text = page.get_text("text")
    
    # 提取表格(PyMuPDF 1.23+ 支持)
    tabs = page.find_tables()
    for tab in tabs:
        table_md = tab.to_markdown()
        text = text.replace(tab.bbox_string, f"\n{table_md}\n")
    
    markdown_parts.append(f"## Page {page_num + 1}\n\n{text}")

markdown_content = "\n\n---\n\n".join(markdown_parts)
print(markdown_content[:1000])

# 注意:PyMuPDF 不支持公式识别,数学公式会输出为乱码或占位符

3.2核心差异:公式识别实际效果 实际效果 以下是同一个数学公式在四个工具下的输出对比: 原始公式: \hat{y} = \sigma\left(\sum_{i=1}^{n} w_i x_i + b\right)

MinerU 输出(VLM模式):
<equation>\hat{y} = \sigma\left(\sum_{i=1}^{n} w_i x_i + b\right)</equation>
✅ 完整 LaTeX,可直接渲染

LlamaParse 输出:
<equation>\hat{y} = \sigma(\sum_{i=1}^{n} w_i x_i + b)</equation>
✅ 基本正确,部分样式简化

Unstructured 输出(hi_res):
ŷ = σ(Σwᵢxᵢ + b)
⚠️ Unicode 近似,丢失 LaTeX 结构,LLM 难以理解

PyMuPDF 输出:
ˆy = (X wi xi + b)
❌ 严重乱码,完全不可用

表格识别:财报场景测试
输入: 一个包含合并单元格的财务报表表格

MinerU 输出(精准模式):
| 项目 | 2023年 | 2024年 | 同比变化 |
|------|--------|--------|---------|
| 营业收入(亿元)| 128.5 | 156.3 | +21.6% |
| 净利润(亿元)  | 18.2  | 22.7  | +24.7% |
✅ 结构完整,数字准确

LlamaParse 输出:
| 项目 | 2023年 | 2024年 | 同比变化 |
|------|--------|--------|---------|
| 营业收入(亿元)| 128.5 | 156.3 | 21.6% |
✅ 较准确,但合并单元格处理有时出错

Unstructured 输出:
营业收入(亿元)128.5  156.3  +21.6%
净利润(亿元)18.2  22.7  +24.7%
⚠️ 文字提取出来了,但表格结构丢失

PyMuPDF 输出(find_tables):
| 营业收入(亿元)| 128.5 | 156.3 | +21.6% |
| 净利润(亿元)  | 18.2  | 22.7  | +24.7% |
✅ 简单表格准确,合并单元格和复杂表头易出错

四、RAG 集成方案

4.1 LangChain 接入

所有工具都能接入 LangChain,但接入方式和效果差异较大。
MinerU + LangChain(推荐)

# pip install langchain-mineru
from langchain_mineru import MinerULoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

# Step 1: 解析文档
loader = MinerULoader(
    source="research_paper.pdf",
    mode="precision",          # 精准模式
    token="your-token",        # 或 MINERU_TOKEN 环境变量
    formula=True,              # 保留公式
    table=True,                # 保留表格
)
docs = loader.load()

# Step 2: 分块(MinerU 输出 Markdown,按标题分块效果更好)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n## ", "\n### ", "\n\n", "\n"],
)
chunks = splitter.split_documents(docs)

# Step 3: 构建向量库
vectorstore = FAISS.from_documents(chunks, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# Step 4: 构建 RAG Chain
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | ChatOpenAI(model="gpt-4o")
    | StrOutputParser()
)

response = rag_chain.invoke("这篇论文的主要贡献是什么?")
print(response)

Unstructured + LangChain

from langchain_community.document_loaders import UnstructuredFileLoader

loader = UnstructuredFileLoader(
    "report.pdf",
    strategy="hi_res",
    mode="elements",    # 保留元素分类信息
)
docs = loader.load()

# 按元素类型过滤,只保留内容(去掉页眉页脚)
content_docs = [d for d in docs if d.metadata.get("category") not in 
                ["Header", "Footer", "PageNumber"]]

LlamaParse + LlamaIndex(原生生态)

from llama_parse import LlamaParse
from llama_index.core import VectorStoreIndex

parser = LlamaParse(result_type="markdown")
documents = parser.load_data("research.pdf")

# 直接构建索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("这篇文章的实验方法是什么?")
print(response)

4.2 批量处理:企业文档库导入

import asyncio
from pathlib import Path
from mineru import MinerU

async def batch_process_docs(pdf_dir: str, output_dir: str) -> list[dict]:
    """批量解析目录下所有 PDF,返回结果列表"""
    client = MinerU("your-api-token")
    pdf_files = list(Path(pdf_dir).glob("*.pdf"))
    results = []
    
    # 并发处理,控制并发数避免超限
    semaphore = asyncio.Semaphore(5)
    
    async def process_one(path: Path):
        async with semaphore:
            try:
                result = await asyncio.to_thread(
                    client.extract,
                    str(path),
                    model="vlm",
                    formula=True,
                    table=True,
                )
                return {
                    "filename": path.name,
                    "markdown": result.markdown,
                    "images": result.images,
                    "status": "success"
                }
            except Exception as e:
                return {"filename": path.name, "status": "error", "error": str(e)}
    
    tasks = [process_one(p) for p in pdf_files]
    results = await asyncio.gather(*tasks)
    return results

# 运行
results = asyncio.run(batch_process_docs("./papers/", "./output/"))
success = [r for r in results if r["status"] == "success"]
print(f"成功解析 {len(success)}/{len(results)} 个文件")

五、MCP Server:让 AI Agent 直接调用文档解析

这是 2025 年最值得关注的新玩法。传统 RAG 是"先解析,再检索"的离线流水线——而 MCP 让 AI Agent 可以在推理时实时调用文档解析工具,就像人类打开工具箱一样。 四个工具中,只有 MinerU 提供了官方 MCP Server(mineru-open-mcp)。

5.1 MinerU MCP Server 配置
安装(无需 Python 环境,用 uvx 一键运行)

# 安装 uv(如未安装)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 直接运行(Flash 模式,免 Token)
uvx mineru-open-mcp

Claude Desktop 配置~/Library/Application Support/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "mineru": {
      "command": "uvx",
      "args": ["mineru-open-mcp"],
      "env": {
        "MINERU_API_TOKEN": "your_token_here"
      }
    }
  }
}

Cursor 配置.cursor/mcp.json

{
  "mcpServers": {
    "mineru": {
      "command": "uvx",
      "args": ["mineru-open-mcp"],
      "env": {
        "MINERU_API_TOKEN": "your_token_here"
      }
    }
  }
}

配置好后,在 Claude/Cursor 里直接说:
"帮我解析这个 PDF:arxiv.org/pdf/2501.12…,提取摘要和实验结果"
Claude 会自动调用 parse_documents 工具,返回结构化 Markdown,无需任何额外代码。
MCP 暴露的工具清单:

工具名功能
parse_documents解析 PDF/DOCX/PPTX/图片/HTML → Markdown
get_ocr_languages列出全部 109 种支持的 OCR 语言
clean_logs清理旧日志文件

Flash 模式特点:无需 API Token,免费,每次最多 20 页 / 10 MB。适合日常文档问答、代码辅助场景。精准模式需要 Token,支持完整公式 + 表格识别,适合学术/生产场景。

5.2 HTTP 模式:接入 Web 端 Agent 平台

如果你的 MCP 客户端是 Web 应用(Dify、FastGPT、自建平台),需要 Streamable HTTP 模式:

# 启动 HTTP 模式
MINERU_API_TOKEN=your_token mineru-open-mcp \
  --transport streamable-http \
  --port 8001

{
  "mcpServers": {
    "mineru": {
      "type": "streamableHttp",
      "url": "http://127.0.0.1:8001/mcp"
    }
  }
}

六、Agent Workflow 集成方案

6.1 LangChain ReAct Agent:自动选择解析策略

下面是一个完整的 Agent,能根据文档类型自动选择"Flash 模式"还是"精准模式":

from langchain_mineru import MinerULoader
from langchain.tools import tool
from langchain.agents import create_react_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate

@tool
def parse_document_flash(url_or_path: str) -> str:
    """
    快速解析文档,免费无需 Token,适合普通 PDF(≤20页)。
    输入:文档 URL 或本地路径
    输出:Markdown 文本
    """
    loader = MinerULoader(source=url_or_path, mode="flash")
    docs = loader.load()
    return docs[0].page_content if docs else "解析失败"

@tool
def parse_document_precision(url_or_path: str) -> str:
    """
    精准解析文档,支持公式(LaTeX)、复杂表格、扫描件,适合学术论文和财报。
    输入:文档 URL 或本地路径
    输出:带公式和表格的 Markdown 文本
    """
    loader = MinerULoader(
        source=url_or_path,
        mode="precision",
        token="your-token",
        formula=True,
        table=True,
        ocr=True,
    )
    docs = loader.load()
    return docs[0].page_content if docs else "解析失败"

@tool
def answer_from_document(query: str, document_content: str) -> str:
    """
    基于已解析的文档内容回答问题。
    输入:问题,文档 Markdown 内容
    输出:答案
    """
    from langchain_openai import ChatOpenAI
    llm = ChatOpenAI(model="gpt-4o")
    response = llm.invoke(
        f"基于以下文档内容回答问题:\n\n{document_content[:4000]}\n\n问题:{query}"
    )
    return response.content

# 构建 Agent
tools = [parse_document_flash, parse_document_precision, answer_from_document]
llm = ChatOpenAI(model="gpt-4o", temperature=0)

prompt = PromptTemplate.from_template("""
你是一个文档分析助手。你有以下工具:
{tools}

工具名称:{tool_names}

使用 ReAct 格式完成任务:
Question: 需要完成的任务
Thought: 分析应该用哪个工具
Action: 工具名称
Action Input: 工具输入
Observation: 工具输出
...(重复直到得出答案)
Final Answer: 最终答案

Question: {input}
{agent_scratchpad}
""")

agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5)

# 执行任务
result = executor.invoke({
    "input": "解析这篇论文 https://arxiv.org/pdf/2501.12345,找出其中的主要实验结果和性能指标"
})
print(result["output"])

6.2 n8n 工作流:无代码接入

如果你的团队不写代码,n8n 可以零代码构建完整文档解析流水线:

触发器(定时/Webhook)
    │
    ▼
HTTP Request(调用 MinerU API)
    │  POST https://mineru.net/api/v4/extract/task
    │  Body: { "url": "{{$json.pdf_url}}", "is_ocr": true }
    │
    ▼
等待结果(Polling / Webhook 回调)
    │
    ▼
解析 JSON,提取 Markdown 内容
    │
    ▼
发送到 Feishu / Notion / 数据库

关键 API 调用(n8n HTTP Request 节点配置):

// Step 1: 提交解析任务
POST https://mineru.net/api/v4/extract/task
Headers: { "Authorization": "Bearer YOUR_TOKEN" }
Body: {
  "url": "https://example.com/report.pdf",
  "enable_formula": true,
  "enable_table": true,
  "model_version": "vlm"
}

// Step 2: 查询结果
GET https://mineru.net/api/v4/extract/task/result/{task_id}
Headers: { "Authorization": "Bearer YOUR_TOKEN" }

6.3 Dify 知识库:生产级 RAG 流水线

Dify 已原生集成 MinerU,直接在文档上传环节选择:

如果需要通过 API 编排:
import requests  # Step 1: 向 Dify 知识库上传文档(使用 MinerU 解析引擎) def upload_to_dify_knowledge_base(pdf_path: str, dataset_id: str, api_key: str):     with open(pdf_path, "rb") as f:         response = requests.post(             f"https://api.dify.ai/v1/datasets/{dataset_id}/document/create_by_file",             headers={"Authorization": f"Bearer {api_key}"},             files={"file": f},             data={                 "indexing_technique": "high_quality",                 "process_rule": '{"mode": "automatic"}',             }         )     return response.json()  result = upload_to_dify_knowledge_base(     pdf_path="research_paper.pdf",     dataset_id="your-dataset-id",     api_key="your-dify-api-key" ) print(f"文档 ID: {result['document']['id']}") 

七、选型决策指南

一句话选型总结
选 MinerU,如果你:

  • 需要解析学术论文、教材、技术文档
  • 需要 LaTeX 公式输出
  • 需要本地部署或私有化
  • 在做 RAG 系统,重视召回质量
  • 在用 Claude/Cursor,想接 MCP
  • 预算有限,需要免费方案

选其他工具,如果你:

  • 只处理原生 PDF 纯文字,速度第一 → PyMuPDF
  • 需要处理 20+ 种格式(邮件/Excel/网页)→ Unstructured
  • 在 LlamaIndex 生态,愿意付费获得最好语义理解 → LlamaParse
  • 原型验证阶段,什么都想试试 → MinerU Flash 模式(免费)

八、快速上手清单

□ 1. 安装测试(5分钟)
     pip install mineru-open-sdk
     python -c "from mineru import MinerU; r = MinerU().flash_extract('your.pdf'); print(r.markdown[:500])"

□ 2. 接入 LangChain RAG(15分钟)
     pip install langchain-mineru
     参考本文 4.1 节代码

□ 3. 配置 Claude/Cursor MCP(5分钟)
     uvx mineru-open-mcp
     修改 claude_desktop_config.json,参考本文 5.1 节

□ 4. 生产环境(需 Token)
     注册:https://mineru.net
     获取 API Token → 替换代码中的 "your-token"
     开启 formula=True, table=True, ocr=True

数据来源:OmniDocBench(CVPR 2025)、公开 benchmark 报告、实测结果。MinerU 版本:2.5(2025年9月)。