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