pdf解析工具---Miner-u 本地部署记录

一、MinerU悉知及源码学习

可以做什么: MinerU是一款由上海人工智能实验室 OpenDataLab 团队开发的开源 PDF 转 Markdown 工具,可以高质量地提取 PDF 文档内容,生成结构化的 Markdown 格式文本,可用于RAG、LLM语料准备等场景。

在github上下载项目源码进行阅读。

MinerU两种后端

MinerU对文档解析的方式有两种,分别是Pipeline和VLM的方式。以下是两种方式的对比。

1. VLM 后端(视觉 - 语言模型)
  • 核心模型 :依赖视觉 - 语言大模型 (如 Qwen2VL、LLaVA 等),具备 "看图理解内容 + 格式" 的能力,需配合 vllm 等推理引擎加速(支持批量 / 异步推理)。
  • 辅助工具 :仅需基础的 PDF 转图像工具(如 pdf2image),无需其他专项模型(布局分析、OCR、表格解析等均由大模型内部完成)。
  • 模型特点:单一大模型包办 "识别 + 理解",依赖强算力(GPU),模型体积大(通常数十亿参数)。
2. Pipeline 后端(传统流水线)
  • 核心模型 :由多个专项轻量模型 + 规则 组成工具链,分工处理不同任务:
    • 布局分析:DocLayoutYOLO(识别标题、段落、表格等元素位置);
    • OCR 识别:PaddleOCR(提取图片中的文字);
    • 表格解析:UnetTableModel(有线表格)、RapidTableModel(无线表格);
    • 公式处理:YOLOv8MFD(公式检测)+ Unimernet(公式识别为 LaTeX)。
  • 辅助工具:需要坐标计算(如 IOU 重叠度)、规则匹配(如列表缩进判断)等工程化逻辑。
  • 模型特点:多模型分工明确,单个模型体积小(百万至千万参数),可在 CPU 运行。

MinerU使用的模型

python 复制代码
class AtomicModel:
    Layout = "layout"
    MFD = "mfd"
    MFR = "mfr"
    OCR = "ocr"
    WirelessTable = "wireless_table"
    WiredTable = "wired_table"
    TableCls = "table_cls"
    ImgOrientationCls = "img_ori_cls"

可以在下载的MinerU项目源码的model文件夹里查看默认使用的模型

pipeline

|---------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| 模型类型 | 模型 |
| layout模型 | YOLOv10 目标检测模型,专门用于 "文档布局识别"需提前准备 YOLOv10 文档布局专用权重,使用时会自动下载。 |
| mfd | 基于 YOLOv8 的公式检测模型工具类 |
| ocr | paddleocr2pytorch |
| ocr_cls | 基于 ONNX Runtime 的图像方向分类模型工具类(PaddleOrientationClsModel),核心功能是检测图像(主要是表格图像)的旋转角度(0°、90°、180°、270°),并将旋转的图像校正为正方向,确保后续表格识别、OCR 等处理的准确性。 |
| reading_order | 基于 LayoutLMv3 模型的文档元素排序工具 |
| table | 基于 ONNX Runtime 的表格类型分类模型工具类(PaddleTableClsModel) |

vlm

这里定义了怎么使用vllm来使用视觉模型的命令。

参数类别 关键参数示例 作用说明 默认值 / 处理逻辑
模型相关 --model /path/to/your/model 指定 VLM 模型路径(本地已有的模型) 若不指定,自动下载默认 VLM 模型到工具类的模型目录(如.mineru/models/vlm
服务端口 --port 8000 设置服务监听的端口号 默认30000
GPU 资源 --gpu-memory-utilization 0.7 控制 GPU 内存利用率(0~1 之间) 默认0.5(50%),避免显存占用过高
logits 处理器 --logits-processors "custom:Processor" 自定义 logits 处理器(用于优化模型输出) 若环境兼容(见enable_custom_logits_processors逻辑),默认启用mineru_vl_utils:MinerULogitsProcessor
vllm 原生参数 --tensor-parallel-size 2 vllm 支持的其他参数(如张量并行、最大序列长度等) 直接传递给 vllm,无默认值(按 vllm 原生逻辑处理)

根据源码解决如何更换自己想用的模型:

模型自动下载的缓存地址,重要的就是这个地址要改成自己的模型:

python 复制代码
doclayout_yolo_weights = os.path.join(auto_download_and_get_model_root_path(ModelPath.doclayout_yolo), ModelPath.doclayout_yolo)

auto_download_and_get_model_root_path这个函数在:anaconda3/envs/minerU/lib/python3.11/site-packages/mineru/utils/models_download_utils.py这个路径下。

该函数是 MinerU 模型管理的 "总入口",解决了 "模型从哪里下载、下载到哪里、如何读取本地模型" 的核心问题,实现了 "云端自动下载" 与 "本地路径读取" 的无缝切换,适配不同使用场景(如无网络环境用本地模型,有网络自动拉取最新模型)。

二、虚拟环境配置

python 复制代码
pip install "mineru[vllm]":安装 mineru 主包及 vllm 推理后端相关的基础依赖。

pip install "mineru-vl-utils[vllm]":安装 mineru 的视觉相关核心工具与vllm适配的组件,且确保其支持 vllm 推理。

pip install torch==2.9.0+cu121 torchvision==0.24.0+cu121 torchaudio==2.8.0+cu121 --index-url https://download.pytorch.org/whl/cu121

pip install loguru pypdfium2 doclayout_yolo:安装轻量辅助工具,支持日志、PDF 处理和文档布局分析。

mamba install -c conda-forge tqdm vllm ultralytics:安装进度条工具、vllm 推理引擎和图像处理模型库。

还可参考miner-u项目中的pyproject.toml文件。

三、本地实操,使用VLM后台

步骤1:下载miner-U模型

使用MinerU2.5-2509-1.2B视觉模型。

MinerU2.5-2509-1.2B 是由 OpenDataLab 与上海 AI 实验室于 2025 年 9 月推出的视觉语言模型,专为高精度、高效率的文档解析任务而设计。它是 MinerU 系列的最新迭代版本,聚焦于将 PDF 等复杂格式文档转化为结构化的机器可读数据(如 Markdown 、 JSON 等)。

python 复制代码
# 1. 导入 modelscope 的核心下载函数
from modelscope import snapshot_download
import os

# --- 配置区 ---

# 指定要从 ModelScope Hub 下载的模型 ID
# ModelScope 会自动从 'https://modelscope.cn/models' 下载
model_id = 'OpenDataLab/MinerU2.5-2509-1.2B'

# 指定你希望将模型文件保存到的本地目录
# 注意:在 modelscope 中,这个参数叫做 cache_dir
local_dir_path = './MinerU2.5'

# 指定要下载的模型版本,例如 'master' (主分支) 或一个具体的 commit ID
# 这是一个好习惯,可以保证你下载版本的确定性
revision = 'master'


# --- 执行区 ---

print(f"准备从 ModelScope 下载模型: {model_id} (版本: {revision})")
print(f"将保存到本地目录: {local_dir_path}")

try:
    # 2. 开始执行下载
    # 函数会返回实际存放模型文件的完整路径
    downloaded_path = snapshot_download(
        model_id=model_id,
        cache_dir=local_dir_path,
        revision=revision,
        # allow_pattern="*.safetensors",  # 可选:只下载 safetensors 格式的权重文件
        # ignore_file_pattern=[r"\.md$", r"\.py$"], # 可选:忽略所有 Markdown 和 Python 文件
    )
    
    print(f"\n✅ 模型已成功下载到: {downloaded_path}")
    print("这个返回的路径是包含模型文件的最终目录,你可以直接使用它。")

except Exception as e:
    print(f"\n❌ 下载过程中出现错误: {e}")
    print("\n排查建议:")
    print("1. 检查你的网络连接是否可以访问 modelscope.cn。")
    print("2. 确认模型 ID 'Qwen/Qwen3-8B' 是否正确。")
    print("3. 如果是私有模型,请确保已在终端运行 'modelscope login' 并输入了你的 Token。")

步骤2:使用VLM后台解析pdf

python 复制代码
# Copyright (c) Opendatalab. All rights reserved.
import copy
import json
import os
from pathlib import Path
# 【核心修改】在所有其他導入之前,設定自訂的緩存路徑
# 這行程式碼必須放在腳本的最頂部
# 同時設定 MINERU_MODEL_SOURCE,確保它使用 modelscope 的緩存機制
os.environ['MINERU_MODEL_SOURCE'] = "modelscope"#从魔塔社区下载模型
os.environ['MODELSCOPE_CACHE'] = "/home/agent/models/MinerU2.5"#下载的模型存到这个路径
from loguru import logger

from mineru.cli.common import convert_pdf_bytes_to_bytes_by_pypdfium2, prepare_env, read_fn
from mineru.data.data_reader_writer import FileBasedDataWriter
from mineru.utils.draw_bbox import draw_layout_bbox, draw_span_bbox
from mineru.utils.enum_class import MakeMode
from mineru.backend.vlm.vlm_analyze import doc_analyze as vlm_doc_analyze
from mineru.backend.pipeline.pipeline_analyze import doc_analyze as pipeline_doc_analyze
from mineru.backend.pipeline.pipeline_middle_json_mkcontent import union_make as pipeline_union_make
from mineru.backend.pipeline.model_json_to_middle_json import result_to_middle_json as pipeline_result_to_middle_json
from mineru.backend.vlm.vlm_middle_json_mkcontent import union_make as vlm_union_make
from mineru.utils.guess_suffix_or_lang import guess_suffix_by_path


def do_parse(
    output_dir,  # Output directory for storing parsing results
    pdf_file_names: list[str],  # List of PDF file names to be parsed
    pdf_bytes_list: list[bytes],  # List of PDF bytes to be parsed
    p_lang_list: list[str],  # List of languages for each PDF, default is 'ch' (Chinese)
    backend="pipeline",  # The backend for parsing PDF, default is 'pipeline'
    parse_method="auto",  # The method for parsing PDF, default is 'auto'
    formula_enable=True,  # Enable formula parsing
    table_enable=True,  # Enable table parsing
    server_url=None,  # Server URL for vlm-http-client backend
    f_draw_layout_bbox=True,  # Whether to draw layout bounding boxes
    f_draw_span_bbox=True,  # Whether to draw span bounding boxes
    f_dump_md=True,  # Whether to dump markdown files
    f_dump_middle_json=True,  # Whether to dump middle JSON files
    f_dump_model_output=True,  # Whether to dump model output files
    f_dump_orig_pdf=True,  # Whether to dump original PDF files
    f_dump_content_list=True,  # Whether to dump content list files
    f_make_md_mode=MakeMode.MM_MD,  # The mode for making markdown content, default is MM_MD
    start_page_id=0,  # Start page ID for parsing, default is 0
    end_page_id=None,  # End page ID for parsing, default is None (parse all pages until the end of the document)
):

    if backend == "pipeline":
        for idx, pdf_bytes in enumerate(pdf_bytes_list):
            new_pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id)
            pdf_bytes_list[idx] = new_pdf_bytes

        infer_results, all_image_lists, all_pdf_docs, lang_list, ocr_enabled_list = pipeline_doc_analyze(pdf_bytes_list, p_lang_list, parse_method=parse_method, formula_enable=formula_enable,table_enable=table_enable)

        for idx, model_list in enumerate(infer_results):
            model_json = copy.deepcopy(model_list)
            pdf_file_name = pdf_file_names[idx]
            local_image_dir, local_md_dir = prepare_env(output_dir, pdf_file_name, parse_method)
            image_writer, md_writer = FileBasedDataWriter(local_image_dir), FileBasedDataWriter(local_md_dir)

            images_list = all_image_lists[idx]
            pdf_doc = all_pdf_docs[idx]
            _lang = lang_list[idx]
            _ocr_enable = ocr_enabled_list[idx]
            middle_json = pipeline_result_to_middle_json(model_list, images_list, pdf_doc, image_writer, _lang, _ocr_enable, formula_enable)

            pdf_info = middle_json["pdf_info"]

            pdf_bytes = pdf_bytes_list[idx]
            _process_output(
                pdf_info, pdf_bytes, pdf_file_name, local_md_dir, local_image_dir,
                md_writer, f_draw_layout_bbox, f_draw_span_bbox, f_dump_orig_pdf,
                f_dump_md, f_dump_content_list, f_dump_middle_json, f_dump_model_output,
                f_make_md_mode, middle_json, model_json, is_pipeline=True
            )
    else:
        if backend.startswith("vlm-"):
            backend = backend[4:]

        f_draw_span_bbox = False
        parse_method = "vlm"
        for idx, pdf_bytes in enumerate(pdf_bytes_list):
            pdf_file_name = pdf_file_names[idx]
            pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id)
            local_image_dir, local_md_dir = prepare_env(output_dir, pdf_file_name, parse_method)
            image_writer, md_writer = FileBasedDataWriter(local_image_dir), FileBasedDataWriter(local_md_dir)
            middle_json, infer_result = vlm_doc_analyze(pdf_bytes, image_writer=image_writer, backend=backend, server_url=server_url)

            pdf_info = middle_json["pdf_info"]

            _process_output(
                pdf_info, pdf_bytes, pdf_file_name, local_md_dir, local_image_dir,
                md_writer, f_draw_layout_bbox, f_draw_span_bbox, f_dump_orig_pdf,
                f_dump_md, f_dump_content_list, f_dump_middle_json, f_dump_model_output,
                f_make_md_mode, middle_json, infer_result, is_pipeline=False
            )


def _process_output(
        pdf_info,
        pdf_bytes,
        pdf_file_name,
        local_md_dir,
        local_image_dir,
        md_writer,
        f_draw_layout_bbox,
        f_draw_span_bbox,
        f_dump_orig_pdf,
        f_dump_md,
        f_dump_content_list,
        f_dump_middle_json,
        f_dump_model_output,
        f_make_md_mode,
        middle_json,
        model_output=None,
        is_pipeline=True
):
    """处理输出文件"""
    if f_draw_layout_bbox:
        draw_layout_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_layout.pdf")

    if f_draw_span_bbox:
        draw_span_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_span.pdf")

    if f_dump_orig_pdf:
        md_writer.write(
            f"{pdf_file_name}_origin.pdf",
            pdf_bytes,
        )

    image_dir = str(os.path.basename(local_image_dir))

    if f_dump_md:
        make_func = pipeline_union_make if is_pipeline else vlm_union_make
        md_content_str = make_func(pdf_info, f_make_md_mode, image_dir)
        md_writer.write_string(
            f"{pdf_file_name}.md",
            md_content_str,
        )

    if f_dump_content_list:
        make_func = pipeline_union_make if is_pipeline else vlm_union_make
        content_list = make_func(pdf_info, MakeMode.CONTENT_LIST, image_dir)
        md_writer.write_string(
            f"{pdf_file_name}_content_list.json",
            json.dumps(content_list, ensure_ascii=False, indent=4),
        )

    if f_dump_middle_json:
        md_writer.write_string(
            f"{pdf_file_name}_middle.json",
            json.dumps(middle_json, ensure_ascii=False, indent=4),
        )

    if f_dump_model_output:
        md_writer.write_string(
            f"{pdf_file_name}_model.json",
            json.dumps(model_output, ensure_ascii=False, indent=4),
        )

    logger.info(f"local output dir is {local_md_dir}")


def parse_doc(
        path_list: list[Path],
        output_dir,
        lang="ch",
        backend="pipeline",
        method="auto",
        server_url=None,
        start_page_id=0,
        end_page_id=None
):
    """
        Parameter description:
        path_list: List of document paths to be parsed, can be PDF or image files.
        output_dir: Output directory for storing parsing results.
        lang: Language option, default is 'ch', optional values include['ch', 'ch_server', 'ch_lite', 'en', 'korean', 'japan', 'chinese_cht', 'ta', 'te', 'ka']。
            Input the languages in the pdf (if known) to improve OCR accuracy.  Optional.
            Adapted only for the case where the backend is set to "pipeline"
        backend: the backend for parsing pdf:
            pipeline: More general.
            vlm-transformers: More general.
            vlm-vllm-engine: Faster(engine).
            vlm-http-client: Faster(client).
            without method specified, pipeline will be used by default.
        method: the method for parsing pdf:
            auto: Automatically determine the method based on the file type.
            txt: Use text extraction method.
            ocr: Use OCR method for image-based PDFs.
            Without method specified, 'auto' will be used by default.
            Adapted only for the case where the backend is set to "pipeline".
        server_url: When the backend is `http-client`, you need to specify the server_url, for example:`http://127.0.0.1:30000`
        start_page_id: Start page ID for parsing, default is 0
        end_page_id: End page ID for parsing, default is None (parse all pages until the end of the document)
    """
    try:
        file_name_list = []
        pdf_bytes_list = []
        lang_list = []
        for path in path_list:
            file_name = str(Path(path).stem)
            pdf_bytes = read_fn(path)
            file_name_list.append(file_name)
            pdf_bytes_list.append(pdf_bytes)
            lang_list.append(lang)
        do_parse(
            output_dir=output_dir,
            pdf_file_names=file_name_list,
            pdf_bytes_list=pdf_bytes_list,
            p_lang_list=lang_list,
            backend=backend,
            parse_method=method,
            server_url=server_url,
            start_page_id=start_page_id,
            end_page_id=end_page_id
        )
    except Exception as e:
        logger.exception(e)


if __name__ == '__main__':
    # args
    __dir__ = os.path.dirname(os.path.abspath(__file__))
    pdf_files_dir = os.path.join(__dir__, "pdfs")
    output_dir = os.path.join(__dir__, "output")
    pdf_suffixes = ["pdf"]
    image_suffixes = ["png", "jpeg", "jp2", "webp", "gif", "bmp", "jpg"]

    doc_path_list = []
    for doc_path in Path(pdf_files_dir).glob('*'):
        if guess_suffix_by_path(doc_path) in pdf_suffixes + image_suffixes:
            doc_path_list.append(doc_path)



    # 【關鍵修改 1】: 確保使用 modelscope 來尋找本地緩存的模型
    # 即使您已下載,這一步有助於函式庫正確定位
    # os.environ['MINERU_MODEL_SOURCE'] = "modelscope"

    print(f"🚀 即將使用 'vlm-vllm-engine' 後端處理 {len(doc_path_list)} 個文件...")
    parse_doc(doc_path_list, output_dir, backend="vlm-vllm-engine")  # faster(engine).


    """如果您由于网络问题无法下载模型,可以设置环境变量MINERU_MODEL_SOURCE为modelscope使用免代理仓库下载模型"""
    # os.environ['MINERU_MODEL_SOURCE'] = "modelscope"

    """Use pipeline mode if your environment does not support VLM"""
    # parse_doc(doc_path_list, output_dir, backend="pipeline")

    """To enable VLM mode, change the backend to 'vlm-xxx'"""
    # parse_doc(doc_path_list, output_dir, backend="vlm-transformers")  # more general.
    # parse_doc(doc_path_list, output_dir, backend="vlm-vllm-engine")  # faster(engine).
    # parse_doc(doc_path_list, output_dir, backend="vlm-http-client", server_url="http://127.0.0.1:30000")  # faster(client).

这个代码的主要参数在于:

  • 模型加载 :由 MINERU_MODEL_SOURCE="modelscope"MODELSCOPE_CACHE 共同控制,固定从 ModelScope 下载 / 读取,缓存到自定义路径;
  • 后端选择 :默认用 vlm-vllm-engine(VLM 类,速度快),可通过修改 backend 参数切换为 pipeline(轻量)或其他 VLM 后端,适配不同文档类型和硬件条件。

四、本地实操,使用pipeline后台

这个过程一直报错缺少很多库,一直安装就可以了。

最后可以成功运行的代码:

python 复制代码
# Copyright (c) Opendatalab. All rights reserved.
import copy
import json
import os
from pathlib import Path
# 强制使用 CPU,禁用 CUDA
# 【核心修改1:自定义模型保存路径】
# 修改为你需要的模型缓存目录(确保路径存在且有读写权限)
os.environ['MINERU_MODEL_SOURCE'] = "modelscope"  # 从 ModelScope 下载模型
os.environ['MODELSCOPE_CACHE'] = "/home/agent/ltcode/mineru/model/MinerU_Pipeline"  # 自定义模型保存路径
from loguru import logger

from mineru.cli.common import convert_pdf_bytes_to_bytes_by_pypdfium2, prepare_env, read_fn
from mineru.data.data_reader_writer import FileBasedDataWriter
from mineru.utils.draw_bbox import draw_layout_bbox, draw_span_bbox
from mineru.utils.enum_class import MakeMode
from mineru.backend.vlm.vlm_analyze import doc_analyze as vlm_doc_analyze
from mineru.backend.pipeline.pipeline_analyze import doc_analyze as pipeline_doc_analyze
from mineru.backend.pipeline.pipeline_middle_json_mkcontent import union_make as pipeline_union_make
from mineru.backend.pipeline.model_json_to_middle_json import result_to_middle_json as pipeline_result_to_middle_json
from mineru.backend.vlm.vlm_middle_json_mkcontent import union_make as vlm_union_make
from mineru.utils.guess_suffix_or_lang import guess_suffix_by_path


def do_parse(
    output_dir,  # 结果输出目录
    pdf_file_names: list[str],  # PDF文件名列表
    pdf_bytes_list: list[bytes],  # PDF字节流列表
    p_lang_list: list[str],  # 每种PDF的语言,默认中文"ch"
    backend="pipeline",  # 解析后端,默认"pipeline"
    parse_method="auto",  # 解析方式,默认"auto"(此处会强制设为"ocr")
    formula_enable=True,  # 【核心配置1:启用公式解析】
    table_enable=True,  # 【核心配置2:启用表格解析】
    server_url=None,  # vlm-http-client后端的服务地址(Pipeline后端无用)
    f_draw_layout_bbox=True,  # 【核心配置3:启用布局边界框绘制】
    f_draw_span_bbox=True,  # 【核心配置4:启用元素边界框绘制】
    f_dump_md=True,  # 【核心配置5:启用Markdown输出】
    f_dump_middle_json=True,  # 【核心配置6:启用中间JSON输出】
    f_dump_model_output=True,  # 【核心配置7:启用模型原始输出】
    f_dump_orig_pdf=True,  # 【核心配置8:启用原始PDF保存】
    f_dump_content_list=True,  # 【核心配置9:启用内容列表输出】
    f_make_md_mode=MakeMode.MM_MD,  # Markdown生成模式,默认标准MM_MD
    start_page_id=0,  # 起始解析页码(0开始)
    end_page_id=None,  # 结束解析页码(None表示到最后一页)
):

    if backend == "pipeline":
        # 【关键:强制使用OCR解析】覆盖传入的parse_method,确保所有PDF用OCR分析
        parse_method = "ocr"
        # 截取指定页码范围的PDF
        for idx, pdf_bytes in enumerate(pdf_bytes_list):
            new_pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id)
            pdf_bytes_list[idx] = new_pdf_bytes

        # 【Pipeline全功能调用】启用公式、表格解析,使用OCR方式
        infer_results, all_image_lists, all_pdf_docs, lang_list, ocr_enabled_list = pipeline_doc_analyze(
            pdf_bytes_list, 
            p_lang_list, 
            parse_method=parse_method,  # 已强制为"ocr"
            formula_enable=formula_enable,  # 启用公式解析
            table_enable=table_enable  # 启用表格解析
        )

        # 逐个处理PDF的解析结果
        for idx, model_list in enumerate(infer_results):
            model_json = copy.deepcopy(model_list)
            pdf_file_name = pdf_file_names[idx]
            # 准备输出目录(自动创建images和md子目录)
            local_image_dir, local_md_dir = prepare_env(output_dir, pdf_file_name, parse_method)
            image_writer, md_writer = FileBasedDataWriter(local_image_dir), FileBasedDataWriter(local_md_dir)

            images_list = all_image_lists[idx]
            pdf_doc = all_pdf_docs[idx]
            _lang = lang_list[idx]
            _ocr_enable = ocr_enabled_list[idx]  # OCR已强制启用,此处为True
            # 生成Pipeline格式的中间JSON(含OCR文本、公式/表格信息)
            middle_json = pipeline_result_to_middle_json(
                model_list, images_list, pdf_doc, image_writer, _lang, _ocr_enable, formula_enable
            )

            pdf_info = middle_json["pdf_info"]
            pdf_bytes = pdf_bytes_list[idx]
            # 输出所有配置的结果文件
            _process_output(
                pdf_info, pdf_bytes, pdf_file_name, local_md_dir, local_image_dir,
                md_writer, f_draw_layout_bbox, f_draw_span_bbox, f_dump_orig_pdf,
                f_dump_md, f_dump_content_list, f_dump_middle_json, f_dump_model_output,
                f_make_md_mode, middle_json, model_json, is_pipeline=True
            )
    else:
        # VLM后端逻辑(本代码已禁用,无需关注)
        if backend.startswith("vlm-"):
            backend = backend[4:]
        f_draw_span_bbox = False
        parse_method = "vlm"
        for idx, pdf_bytes in enumerate(pdf_bytes_list):
            pdf_file_name = pdf_file_names[idx]
            pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id)
            local_image_dir, local_md_dir = prepare_env(output_dir, pdf_file_name, parse_method)
            image_writer, md_writer = FileBasedDataWriter(local_image_dir), FileBasedDataWriter(local_md_dir)
            middle_json, infer_result = vlm_doc_analyze(pdf_bytes, image_writer=image_writer, backend=backend, server_url=server_url)
            pdf_info = middle_json["pdf_info"]
            _process_output(
                pdf_info, pdf_bytes, pdf_file_name, local_md_dir, local_image_dir,
                md_writer, f_draw_layout_bbox, f_draw_span_bbox, f_dump_orig_pdf,
                f_dump_md, f_dump_content_list, f_dump_middle_json, f_dump_model_output,
                f_make_md_mode, middle_json, infer_result, is_pipeline=False
            )


def _process_output(
        pdf_info,
        pdf_bytes,
        pdf_file_name,
        local_md_dir,
        local_image_dir,
        md_writer,
        f_draw_layout_bbox,
        f_draw_span_bbox,
        f_dump_orig_pdf,
        f_dump_md,
        f_dump_content_list,
        f_dump_middle_json,
        f_dump_model_output,
        f_make_md_mode,
        middle_json,
        model_output=None,
        is_pipeline=True
):
    """处理所有输出文件(按配置生成对应结果)"""
    # 1. 生成带布局边界框的PDF(标注标题、段落、表格位置)
    if f_draw_layout_bbox:
        draw_layout_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_layout.pdf")

    # 2. 生成带元素边界框的PDF(标注文本块、公式、表格的具体位置)
    if f_draw_span_bbox:
        draw_span_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_span.pdf")

    # 3. 保存原始PDF(便于对比解析前后内容)
    if f_dump_orig_pdf:
        md_writer.write(f"{pdf_file_name}_origin.pdf", pdf_bytes)

    image_dir = str(os.path.basename(local_image_dir))

    # 4. 生成Markdown文件(核心结果,可直接阅读)
    if f_dump_md:
        make_func = pipeline_union_make if is_pipeline else vlm_union_make
        md_content_str = make_func(pdf_info, f_make_md_mode, image_dir)
        md_writer.write_string(f"{pdf_file_name}.md", md_content_str)

    # 5. 生成内容列表JSON(便于快速查看文档结构)
    if f_dump_content_list:
        make_func = pipeline_union_make if is_pipeline else vlm_union_make
        content_list = make_func(pdf_info, MakeMode.CONTENT_LIST, image_dir)
        md_writer.write_string(
            f"{pdf_file_name}_content_list.json",
            json.dumps(content_list, ensure_ascii=False, indent=4),
        )

    # 6. 生成中间JSON(结构化数据,含所有元素的位置、内容,用于二次开发)
    if f_dump_middle_json:
        md_writer.write_string(
            f"{pdf_file_name}_middle.json",
            json.dumps(middle_json, ensure_ascii=False, indent=4),
        )

    # 7. 生成模型原始输出JSON(调试用,查看模型检测的原始结果)
    if f_dump_model_output:
        md_writer.write_string(
            f"{pdf_file_name}_model.json",
            json.dumps(model_output, ensure_ascii=False, indent=4),
        )

    logger.info(f"✅ 解析完成!结果保存目录:{local_md_dir}")


def parse_doc(
        path_list: list[Path],
        output_dir,
        lang="ch",  # 【关键:指定OCR语言为中文,可改为"en"(英文)等】
        backend="pipeline",  # 固定为Pipeline后端
        method="auto",  # 会在do_parse中强制改为"ocr",此处仅为兼容参数
        server_url=None,  # Pipeline后端无用
        start_page_id=0,  # 起始页码
        end_page_id=None  # 结束页码
):
    """入口函数:读取文件列表,调用do_parse执行解析"""
    try:
        file_name_list = []
        pdf_bytes_list = []
        lang_list = []
        # 遍历待解析文件,读取字节流和文件名
        for path in path_list:
            if not path.exists():
                logger.warning(f"❌ 文件不存在:{path},已跳过")
                continue
            file_name = str(Path(path).stem)  # 取文件名(不含后缀)
            pdf_bytes = read_fn(path)  # 读取文件字节流
            file_name_list.append(file_name)
            pdf_bytes_list.append(pdf_bytes)
            lang_list.append(lang)  # 所有文件使用同一语言(可按需修改为不同语言)
        
        if not file_name_list:
            logger.error("❌ 无有效文件可解析,请检查pdfs目录下是否有PDF/图像文件")
            return

        # 调用核心解析函数do_parse
        do_parse(
            output_dir=output_dir,
            pdf_file_names=file_name_list,
            pdf_bytes_list=pdf_bytes_list,
            p_lang_list=lang_list,
            backend=backend,
            parse_method=method,
            start_page_id=start_page_id,
            end_page_id=end_page_id
        )
    except Exception as e:
        logger.exception(f"❌ 解析过程出错:{str(e)}")


if __name__ == '__main__':
    # 【核心配置2:指定文件目录和输出目录】
    __dir__ = os.path.dirname(os.path.abspath(__file__))
    pdf_files_dir = os.path.join(__dir__, "pdfs")  # 待解析文件目录(需手动创建,放入PDF/图像)
    output_dir = os.path.join(__dir__, "pipeline_ocr_output")  # 结果输出目录(自动创建)

    # 【核心配置3:支持的文件类型(PDF+常见图像格式)】
    pdf_suffixes = ["pdf"]
    image_suffixes = ["png", "jpeg", "jp2", "webp", "gif", "bmp", "jpg"]
    supported_suffixes = pdf_suffixes + image_suffixes

    # 读取pdfs目录下的所有支持类型文件
    doc_path_list = []
    if not os.path.exists(pdf_files_dir):
        os.makedirs(pdf_files_dir)
        logger.warning(f"⚠️  pdfs目录不存在,已自动创建:{pdf_files_dir},请放入待解析文件")
    else:
        for doc_path in Path(pdf_files_dir).glob('*'):
            suffix = guess_suffix_by_path(doc_path).lower()
            if suffix in supported_suffixes:
                doc_path_list.append(doc_path)

    # 打印解析前信息
    print(f"=====================================")
    print(f"🚀 开始解析:")
    print(f"📁 待解析文件目录:{pdf_files_dir}")
    print(f"📊 有效文件数量:{len(doc_path_list)}")
    print(f"🌐 解析方式:Pipeline后端 + OCR全量分析")
    print(f"🗂️  结果输出目录:{output_dir}")
    print(f"🗣️  OCR识别语言:中文(ch)")
    print(f"=====================================")

    # 【核心调用:启动Pipeline+OCR解析】
    if doc_path_list:
        parse_doc(
            path_list=doc_path_list,
            output_dir=output_dir,
            backend="pipeline",  # 固定使用Pipeline后端
            lang="ch",  # 可改为"en"(英文)、"japan"(日文)等
            start_page_id=0,  # 从第1页开始解析(0对应第1页)
            end_page_id=None  # 解析到最后一页
        )
    else:
        print(f"❌ 无有效文件可解析,请在 {pdf_files_dir} 目录放入PDF或图像文件")

模型加载路径的关键位置

控制层级 代码位置 / 函数 作用
1. 路径配置 脚本顶部 os.environ['MODELSCOPE_CACHE'] 直接指定模型保存 / 加载的根目录
2. 触发加载 do_parse 中的 pipeline_doc_analyze 调用 间接触发所有 Pipeline 模型的加载
3. 内部加载逻辑 MinerU 内部 auto_download_and_get_model_root_path 函数 根据配置的根目录,返回具体模型的加载路径

简单来说,只需关注脚本顶部的 MODELSCOPE_CACHE 配置 ------ 代码会自动从这里找模型、加载模型,无需修改其他隐藏的内部逻辑。

相关推荐
Larry_Yanan3 小时前
QML学习笔记(四十三)QML与C++交互:上下文属性暴露
c++·笔记·qt·学习·ui·交互
reasonsummer3 小时前
【办公类-115-02】20251018信息员每周通讯上传之文字稿整理(PDF转docx没有成功)
python·pdf
FserSuN3 小时前
GraphRAG 与 Neo4j 社区版:能力边界与适用场景学习总结
学习·neo4j
学不会就看3 小时前
PyTorch 张量学习
人工智能·pytorch·学习
ʚ希希ɞ ྀ3 小时前
SpringBoot的学习
java·spring boot·学习
霜绛4 小时前
Unity:UGUI笔记(一)——三大基础控件、组合控件
笔记·学习·unity·游戏引擎
代码or搬砖4 小时前
Git学习笔记(三)
笔记·git·学习
阿维的博客日记4 小时前
Redis学习笔记-QuickList
redis·笔记·学习
Moniane4 小时前
CSDN:打造专业的技术名片
学习