当前位置: 首页 > ops >正文

【项目】多模态RAG—本地部署MinerU实现多类文档解析

【项目】多模态RAG—本地部署MinerU实现多类文档解析

    • (一)MinerU基本介绍
    • (二)实际需求背景
    • (三)效果图
    • (四)MinerU安装
    • (五)服务端
    • (六)接口文档
      • (1)接口1:批量解析文档路径
      • (2)接口2:上传单个文件进行解析
    • (七)测试
    • (八)总结

(一)MinerU基本介绍

“你是否遇到过Python解析PDF时格式错乱?或从Word表格提取数据丢失样式?传统库(如PyPDF2、python-docx)对复杂格式支持有限,而商业API又贵又慢。今天介绍一款开箱即用的开源工具——MinerU,支持本地部署,API调用,完美保留文档原始格式。”

在这里插入图片描述

MinerU是一款由上海人工智能实验室 OpenDataLab 团队开源的一款高质量数据提取工具,将PDF转换成Markdown和JSON格式。可用于文档数字化、RAG、LLM语料等场景。

具备功能如下:

  • 删除页眉、页脚、脚注、页码等元素,确保语义连贯

  • 输出符合人类阅读顺序的文本,适用于单栏、多栏及复杂排版

  • 保留原文档的结构,包括标题、段落、列表等

  • 提取图像、图片描述、表格、表格标题及脚注

  • 自动识别并转换文档中的公式为LaTeX格式

  • 自动识别并转换文档中的表格为HTML格式

  • 自动检测扫描版PDF和乱码PDF,并启用OCR功能

  • OCR支持84种语言的检测与识别

  • 支持多种输出格式,如多模态与NLP的Markdown、按阅读顺序排序的JSON、含有丰富信息的中间格式等

  • 支持多种可视化结果,包括layout可视化、span可视化等,便于高效确认输出效果与质检

  • 支持纯CPU环境运行,并支持 GPU(CUDA)/NPU(CANN)/MPS 加速

  • 兼容Windows、Linux和Mac平台

从项目提交记录上看,MinerU一直非常活跃,是一个值得持续跟踪的文档解析工具。 相关链接如下:

  • 论文链接: https://arxiv.org/abs/2409.18839

  • 代码链接: https://github.com/opendatalab/MinerU

  • 官网试用链接: https://mineru.net/OpenSourceTools/Extractor

(二)实际需求背景

实际需求:

  1. 目前有大量的政策数据,为了实现基于知识库的AI政务问答系统,原文中大量附件不能被大模型很好的识别,直接读取文本,又会丢失格式,所以目前最好的解决方案就是把附件都转化成markdown格式(AI识别效果好)
  2. 给客户展示数据的时候,又需要能在网页中直接看到附件内容,所以还需要将markdown格式转化成html标签格式。
  3. 需要解析的格式:“pdf”, “doc”, “docx”, “xls”, “xlsx”, “jpg”

(三)效果图

原始页面:

在这里插入图片描述

解析附件后拼接效果:

在这里插入图片描述

带表格附件解析效果:

在这里插入图片描述

(四)MinerU安装

Linux系统安装环境,本地部署MinerU项目,编写服务端程序提供API接口,方便其他项目调用

支持解析的文档格式:“pdf”, “doc”, “docx”, “xls”, “xlsx”, “jpg”

详细使用:

拉取项目、进入项目、从源码安装环境

git clone https://gitee.com/myhloli/MinerU.git
cd MinerU
uv pip install -e .[core] -i https://mirrors.aliyun.com/pypi/simple

下载模型到本地

mineru-models-download
根据提示选择下一步
下载方式选择modelscope
回车
如图所示,即成功开始下载

在这里插入图片描述

在这里插入图片描述

下载完成后就可以使用命令行调用了

mineru -p <input_path> -o <output_path> --source local

<input_path>:需要解析的文件地址

<output_path> :测解析后输出文件的地址

–source local: 使用本地模型解析

(五)服务端

我们使用python写一个服务端程序,方便项目可以使用API调用

脚本放在MinerU项目的根目录即可

在这里插入图片描述

mineru_service.py 脚本代码如下:

# -*- coding: utf-8 -*-
import os
import uuid
import shutil
import time
import json
import asyncio
import logging
from pathlib import Path
from typing import List, Optional
from fastapi import FastAPI, UploadFile, File, Form
from pydantic import BaseModel
from fastapi.responses import FileResponse, JSONResponse
import markdown
import uvicorn
import mimetypes
import subprocess
from tempfile import NamedTemporaryFile# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mineru")# 创建 FastAPI 应用
app = FastAPI()# 临时输出目录
TEMP_BASE_DIR = Path("./temp_output")
TEMP_BASE_DIR.mkdir(exist_ok=True)# 启动清理任务(清除超过 1 小时的目录)
async def clean_temp_dirs():while True:try:for dir in TEMP_BASE_DIR.iterdir():if dir.is_dir() and (time.time() - dir.stat().st_mtime) > 3600:shutil.rmtree(dir, ignore_errors=True)logger.info(f"清理目录: {dir}")except Exception as e:logger.exception(f"清理任务异常: {e}")await asyncio.sleep(3600)@app.on_event("startup")
async def startup_event():asyncio.create_task(clean_temp_dirs())# 请求体模型
class ParseRequest(BaseModel):file_paths: Optional[List[str]] = Nonelang: Optional[str] = "ch"backend: Optional[str] = "pipeline"method: Optional[str] = "auto"output_format: Optional[str] = "md"# 转换常见办公文档为 PDF
def convert_to_pdf(input_path: Path) -> Path:suffix = input_path.suffix.lower()if suffix in ['.doc', '.docx', '.xls', '.xlsx']:output_path = input_path.with_suffix('.pdf')cmd = ["libreoffice","--headless","--convert-to", "pdf","--outdir", str(input_path.parent),str(input_path)]result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)if result.returncode != 0 or not output_path.exists():logger.error(f"❌ 文档转 PDF 失败: {input_path}\n"f"➡️ 命令: {' '.join(cmd)}\n"f"  stdout: {result.stdout}\n"f"  stderr: {result.stderr}")# 可以选择返回原文件而不是报错中断(根据业务需要)# return input_pathraise RuntimeError(f"文档转 PDF 失败: {input_path}")else:logger.info(f"✅ 转换成功: {input_path} -> {output_path}")return output_pathelse:return input_path# 调用 mineru 命令行接口
def run_mineru_cli(input_path: str, output_path: str, lang: str = "ch", backend: str = "pipeline", method: str = "auto"):os.environ['MINERU_MODEL_SOURCE'] = "modelscope"cmd = f"mineru -p {input_path} -o {output_path} --source local"logger.info(f"运行命令: {cmd}")code = os.system(cmd)if code != 0:raise RuntimeError(f"mineru 命令执行失败,退出码 {code}")
@app.post("/parse")
async def parse_from_path(req: ParseRequest):result = {}for path in req.file_paths:name = Path(path).stemtask_id = uuid.uuid4().hex[:8]task_output_dir = TEMP_BASE_DIR / task_idtask_output_dir.mkdir(exist_ok=True)input_path = convert_to_pdf(Path(path)) run_mineru_cli(input_path, str(task_output_dir), lang=req.lang, backend=req.backend, method=req.method)output_md_path = task_output_dir / name / f"auto"output_md_path = output_md_path / f"{name}.md"if req.output_format == "html":md_content = output_md_path.read_text(encoding="utf-8")result[name] = convert_md_to_html(md_content)else:result[name] = output_md_path.read_text(encoding="utf-8")return JSONResponse(content=result)
@app.post("/upload")
async def parse_from_upload(file: UploadFile = File(...),lang: str = Form("ch"),backend: str = Form("pipeline"),method: str = Form("auto"),output_format: str = Form("md")
):content = await file.read()name = Path(file.filename).stemtask_id = uuid.uuid4().hex[:8]task_dir = TEMP_BASE_DIR / task_idtask_dir.mkdir(exist_ok=True)input_path = task_dir / file.filenamewith open(input_path, "wb") as f:f.write(content)input_path = convert_to_pdf(input_path)run_mineru_cli(str(input_path), str(task_dir), lang=lang, backend=backend, method=method)output_md_path = task_dir / name / f"auto"output_md_path = output_md_path / f"{name}.md"if output_format == "html":md_content = output_md_path.read_text(encoding="utf-8")html_content = convert_md_to_html(md_content)return html_contentelse:return output_md_path.read_text(encoding="utf-8")def convert_md_to_html(md_text: str) -> str:html = markdown.markdown(md_text, extensions=['extra', 'tables', 'nl2br'])html = html.replace("\n", "")  # 可选:去除残余换行符return html# 启动服务
if __name__ == '__main__':uvicorn.run("__main__:app", host="0.0.0.0", port=5002, reload=False)

需要用到的依赖:

fastapi
uvicorn[standard]
markdown
pydantic
python-multipart

可以创建一个requirements.txt,把上面依赖粘贴进去

然后执行

pip install -r requirements.txt

安装完毕后,运行脚本

python mineru_service.py

在这里插入图片描述

如上图所示,即运行成功

需要把服务器防火墙的5002端口开放(不同系统使用的防火墙不同,根据自己的系统来)

sudo ufw allow 5002/tcp
或者
sudo firewall-cmd --zone=public --add-port=5002/tcp --permanent
sudo firewall-cmd --reload

由于解析的文件太多,所以在代码里加了一个每隔1小时,清除所有的已解析文件方法,如果修改了导出文件的目录,记得一并修改

在这里插入图片描述

(六)接口文档

(1)接口1:批量解析文档路径

请求方式POST /parse

请求体 JSONapplication/json

{"file_paths": ["/path/to/doc1.docx", "/path/to/doc2.xlsx"],"lang": "ch","backend": "pipeline","method": "auto","output_format": "md"
}
参数名类型是否必填默认值描述
file_pathsList[str]✅ 是要解析的本地文件路径列表(绝对路径)
langstring“ch”语言类型,可选 “ch”(中文)或 “en”
backendstring“pipeline”后端方式,如 “pipeline”、“local” 等
methodstring“auto”抽取方式,例如 “auto”、“qa” 等
output_formatstring“md”输出格式,“md” 表示 Markdown,“html” 表示 HTML 格式

✅ 响应示例(返回 JSON)

{"doc1": "# 一级标题\n- 抽取内容段落1\n- 抽取内容段落2","doc2": "<h1>标题</h1><p>段落内容</p>"
}

键为文件名(无后缀),值为 markdown 或 html 格式文本

(2)接口2:上传单个文件进行解析

请求方式POST /upload

请求类型multipart/form-data

表单字段

参数名类型是否必填默认值描述
fileUploadFile✅ 是上传的办公文档(.docx, .xlsx, .doc, .xls)
langstring“ch”同上
backendstring“pipeline”同上
methodstring“auto”同上
output_formatstring“md”输出格式,支持 “md” 或 “html”

✅ 响应示例:

  • Markdown 响应(output_format=md):
# 一级标题
- 抽取内容段落
- 抽取内容段落
  • HTML 响应(output_format=html):
<h1>一级标题</h1><ul><li>抽取内容段落</li></ul>

错误情况说明

错误码错误信息可能原因
500文档转 PDF 失败LibreOffice 未安装、文件损坏
500mineru 命令执行失败mineru 未安装、参数错误等
422参数校验失败请求体格式不对

(七)测试

上传需要解析的文件到服务器

在这里插入图片描述

使用postman工具,尝试调用(大部分参数有默认值,所以我只填必传参数了)

在这里插入图片描述

(八)总结

实际使用中,解析受显卡GPU影响,如果配置不够高建议单线程慢慢解析,所有的数据附件都存储在文件服务器中(nfs共享服务器),可以直接把附件服务器挂载到安装MinerU的服务器上,方便获取文件,只需要根据文件的名称拼接路径即可读取数据

http://www.xdnf.cn/news/19499.html

相关文章:

  • 懒加载详细讲解
  • 使用修改过的arj源码编译和测试
  • C++ 学习与 CLion 使用:(五)数据类型,包括整型、实型、字符型、转义字符、字符串、布尔型
  • 从DevOps到BizDevOps:哪些DevOps工具能够成为业务创新加速引擎?
  • 响应式编程框架Reactor【8】
  • Notepad++近期版本避雷
  • 中心扩展算法
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘tox’问题
  • 利用 DrissionPage 精准获取淘宝商品描述:Python 爬虫实战指南
  • C/C++、Python和Java语言的比较
  • 【职业】算法与数据结构专题
  • 15693协议ICODE SLI 系列标签应用场景说明及读、写、密钥认证操作Qt c++源码,支持统信、麒麟等国产Linux系统
  • 浪潮科技Java开发面试题及参考答案(120道题-上)
  • 利用本地电脑上的MobaXterm连接虚拟机上的Ubuntu
  • 基于SpringBoot音乐翻唱平台
  • Linux Shell 脚本中括号类型及用途
  • three.js+WebGL踩坑经验合集(10.2):镜像问题又一坑——THREE.InstancedMesh的正反面向光问题
  • UART-TCP双向桥接服务
  • 【51单片机三路抢答器定时器1工作1外部中断1】2022-11-24
  • 参数检验vs非参数检验
  • docker 网络配置
  • 【高级】系统架构师 | 2025年上半年综合真题
  • 硬件开发_基于Zigee组网的果园养殖监控系统
  • 56_基于深度学习的X光安检危险物品检测系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • aws上创建jenkins
  • 力扣 23 912题(堆)
  • JAVA 面试宝典02
  • 工业飞拍技术:高速生产线的 “动态抓拍神器”,到底牛在哪?
  • 20250829的学习笔记
  • 基于GCN图神经网络的光伏功率预测Matlab代码