一、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使用的模型

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文件夾裏查看默認使用的模型

python使用pdfminer解析pdf文件的方法示例_git


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 原生邏輯處理)

根據源碼解決如何更換自己想用的模型:

模型自動下載的緩存地址,重要的就是這個地址要改成自己的模型:

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 模型管理的 “總入口”,解決了 “模型從哪裏下載、下載到哪裏、如何讀取本地模型” 的核心問題,實現了 “雲端自動下載” 與 “本地路徑讀取” 的無縫切換,適配不同使用場景(如無網絡環境用本地模型,有網絡自動拉取最新模型)。

二、虛擬環境配置

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 等)。

# 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

# 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後台

這個過程一直報錯缺少很多庫,一直安裝就可以了。

最後可以成功運行的代碼:

# 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 配置 —— 代碼會自動從這裏找模型、加載模型,無需修改其他隱藏的內部邏輯。