在本地 RAG 系统中使用 Marker:高精度 PDF 到 Markdown 的离线开源解决方案(2025 更新)
在本地 RAG(Retrieval-Augmented Generation)系统中,PDF 解析质量是决定最终问答准确率的关键(Garbage In, Garbage Out)。传统工具如 PyPDF2 或规则-based 提取器在处理多栏学术论文、LaTeX 公式、复杂表格时表现极差。
2025 年,Marker(由 VikParuchuri 开发)仍是学术文献 PDF 转 Markdown 的开源 SOTA 方案之一。它结合深度学习布局分析、高精度 OCR(默认 Surya)和公式重建,支持批量处理,输出干净的 Markdown + 结构化 JSON 元数据。相比 Nougat(更慢、专攻 arXiv),Marker 速度更快(4-10x)、泛化更好;相比 MinerU(表格强但配置复杂),Marker 安装更简单、VRAM 占用更低。
本节提供生产级批量 ETL 脚本,将 PDF 目录转换为 LLM 友好的 Markdown(用于 embedding)和 JSON(用于溯源引用)。
一、技术栈架构
Marker 将 PDF 的视觉层重构为语义层:
- 核心引擎:Marker(PyTorch-based)。
- OCR:默认 Surya(文档专用、高精度),可选 ocrmypdf/Tesseract。
- 布局分析:深度模型检测 Header/Footer/Image/Table/Equation/Code。
- 公式处理:自动转为 LaTeX($$ 块)。
- 表格处理:转为 HTML/Markdown 表格。
- 后处理:启发式拼接阅读顺序、清洗噪音、保留引用。
- 输入:批量 PDF 目录。
- 输出:结构化 Markdown + JSON(块级 bbox、页码、图像)。
- 硬件:NVIDIA GPU(VRAM ≥ 8GB 推荐,RTX 4070+ 可达峰值速度)。
二、环境构建
确保系统安装 CUDA Toolkit(推荐 12.1+)。
# 1. 创建虚拟环境(Python 3.10+)
conda create -n pdf_etl python=3.10
conda activate pdf_etl
# 2. 安装 PyTorch(根据 CUDA 版本调整)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
# 3. 安装 Marker(最新版支持多格式、JSON 输出)
pip install marker-pdf
# 4. 可选辅助库(进度条、JSON 加速)
pip install tqdm ujson
首次运行会自动下载模型权重(~3-5GB,到 ~/.cache/huggingface)。
三、生产级批量转换脚本
脚本支持增量处理、错误恢复、显存清理和可选参数。避免简单 for 循环,使用 tqdm 进度监控。
新建文件:pdf_etl_pipeline.py
import os
import glob
import ujson as json
import torch
from tqdm import tqdm
from marker.models import load_all_models
from marker.convert import convert_single_pdf
from marker.settings import settings # 可自定义设置
# --- 配置区域 ---
INPUT_DIR = "./data/pdfs" # PDF 输入目录
OUTPUT_DIR = "./data/processed" # 输出目录
BATCH_MULTIPLIER = 2 # 显存倍数(RTX 4090 可设 4-8,12GB 卡建议 1-2)
MAX_PAGES = None # None=全部,调试时设 10
FORCE_OCR = False # True=强制全 OCR(扫描件)
OUTPUT_FORMAT = "markdown" # markdown / json / html / chunks
# ----------------
def setup_models():
"""加载模型并检测设备"""
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"🚀 运行设备: {device.upper()}")
if device == "cpu":
print("⚠️ CPU 模式极慢,建议使用 GPU。")
# 加载所有模型(布局、OCR、公式等)
models = load_all_models()
settings.TORCH_DEVICE = device # 强制设备
return models
def process_batch(models):
"""批量 ETL 主流程"""
os.makedirs(OUTPUT_DIR, exist_ok=True)
pdf_files = glob.glob(os.path.join(INPUT_DIR, "*.pdf"))
if not pdf_files:
print(f"❌ {INPUT_DIR} 中未找到 PDF 文件")
return
print(f"📂 发现 {len(pdf_files)} 个文件,开始转换...")
success = 0
errors = []
for pdf_path in tqdm(pdf_files, desc="转换进度", unit="file"):
filename = os.path.basename(pdf_path)
doc_id = os.path.splitext(filename)[0]
md_path = os.path.join(OUTPUT_DIR, f"{doc_id}.md")
json_path = os.path.join(OUTPUT_DIR, f"{doc_id}.json")
# 增量跳过
if os.path.exists(md_path) and os.path.exists(json_path):
continue
try:
# 核心转换(返回文本、图像字典、元数据)
full_text, images, out_meta = convert_single_pdf(
filename=pdf_path,
models=models,
max_pages=MAX_PAGES,
batch_multiplier=BATCH_MULTIPLIER,
force_ocr=FORCE_OCR,
output_format=OUTPUT_FORMAT # 可单独输出 JSON
)
# 保存 Markdown
with open(md_path, "w", encoding="utf-8") as f:
f.write(full_text)
# 保存 JSON 元数据(块级页码、bbox、图像路径)
with open(json_path, "w", encoding="utf-8") as f:
json.dump(out_meta, f, ensure_ascii=False, indent=2)
# 可选:保存提取图像
img_dir = os.path.join(OUTPUT_DIR, doc_id + "_images")
if images:
os.makedirs(img_dir, exist_ok=True)
for img_name, img_obj in images.items():
img_obj.save(os.path.join(img_dir, img_name))
success += 1
except Exception as e:
error_msg = f"{filename} 处理失败: {str(e)}"
errors.append(error_msg)
torch.cuda.empty_cache() # 清理显存防 OOM 连锁
# 总结
print(f"\n✅ 完成!成功: {success}/{len(pdf_files)}")
if errors:
print(f"⚠️ 失败 ({len(errors)}):")
for err in errors:
print(f" - {err}")
if __name__ == "__main__":
if not os.path.exists(INPUT_DIR):
os.makedirs(INPUT_DIR)
print(f"📁 已创建 {INPUT_DIR},请放入 PDF 文件")
else:
models = setup_models()
process_batch(models)
运行:python pdf_etl_pipeline.py
四、核心优势
- 公式重建:像素公式 → 标准 LaTeX( .........),LLM 可直接理解数学逻辑。
- 阅读顺序修复:基于 bbox 排序,多栏/复杂布局转为自然单栏流。
- 表格高保真:HTML 表格,保留合并单元格。
- 块级元数据:JSON 包含页码、坐标 → RAG 可实现“引用高亮原文”。
- 速度:单 20 页论文 ~10-30 秒(GPU),批量更快。
五、替代方案与权衡(2025 更新)
| 方案 | 核心技术 | 适用场景 | 劣势 |
|---|---|---|---|
| Marker | Layout + Surya OCR + Heuristics | 通用学术/书籍(首选) | 极端复杂表格偶有丢失 |
| MinerU | VLM (1.2B-2.5B) + PDF-Extract-Kit | 复杂中文/扫描件/表格密集 | 配置复杂、VRAM 高 |
| Nougat | Swin + mBART | 纯 arXiv 公式密集 | 速度慢(1页/10s+)、泛化差 |
| Unstructured | YOLO + rules | 快速多模态提取 | 公式/表格精度低 |
六、下一步:接入向量数据库
转换后,使用 LangChain 切分 + 本地 embedding:
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
with open("./data/processed/paper.md", "r", encoding="utf-8") as f:
md_text = f.read()
docs = splitter.split_text(md_text) # 附带标题元数据
# 接下来:embedding (bge-m3) → upsert 到 Chroma/Qdrant/Milvus
Marker + 此管道,可在本地构建高质量 RAG 知识库
579

被折叠的 条评论
为什么被折叠?



