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 配置 ------ 代码会自动从这里找模型、加载模型,无需修改其他隐藏的内部逻辑。

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
百事牛科技5 天前
保护文档安全:PDF限制功能详解与实操
windows·pdf
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode