复杂PDF转Markdown实战:从Marker到多模态的处理全记录

复杂PDF转Markdown实战:从Marker到多模态的处理全记录

摘要:本文详细记录了将1000页+含大量复杂公式、图表的PDF电子书转换为Markdown格式的完整过程,用于内网RAG系统知识库。面对内网算力和模型能力限制,我探索了两种方案:第一阶段使用Marker工具直接转换,第二阶段采用"PDF→高清图片→多模态模型→Markdown"的间接转换策略。文章深入分析了技术选型、实操步骤、遇到的问题及解决方案,为类似场景提供可复用的实践经验。


背景

1.1 业务场景

公司内部RAG(检索增强生成)系统需要构建高质量知识库,来源是一本1000页+的专业技术PDF电子书。该书包含:大量数学公式(LaTeX格式),复杂图表和示意图,代码块和表格,中英文混排文本。

1.2 技术目标

完整转换PDF为Markdown;公式用LaTeX准确表示;图片内容用文字描述保留;去除无关内容(目录、参考文献等)


第一阶段:Marker工具直接转换

2.1 技术选型

经过调研,选择Marker作为首选工具:

  • 优势

    • 专为PDF转Markdown设计
    • 对复杂公式和排版支持出色
    • 可提取并保留PDF中图片
    • 自动去除页眉页脚等元素,避免正文分裂
  • 环境要求

    需要python3.10 和 pytorch。可以在CPU,GPU环境执行。但纯CPU环境转换速度较慢。

2.2 实施步骤

2.2.1 环境准备
bash 复制代码
# 创建conda环境
conda create -n marker python=3.10
conda activate marker

# 安装Marker
pip install marker-pdf

# 安装GPU版本PyTorch(根据CUDA版本)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128
2.2.2 转换命令
bash 复制代码
# 单文件转换
marker_single input.pdf output.md --batch_multiplier 2 --max_pages 50
2.2.3 参数说明
  • --batch_multiplier:控制批次大小,根据GPU显存调整
  • --max_pages:每批次处理页数,避免OOM
  • --output_format:输出格式(md/json/html)

2.3 遇到的问题

文本乱码,部分字符丢失;部分复杂公式识别错误,符号错位。

2.4 第一阶段总结

结论:Marker适合快速转换,总体保留原文档的排版结构和内容,去掉页眉页脚,保留正文的连续,文档中的图片也提取出来了。但出现的乱码和内容缺失也是严重问题,特别是公式的错误。对于文档不大,以上问题可以进一步结合人工优化,或者使用--for-ocr, --use-llm等手段进行优化。

通过查阅marker-pdf .PyPI网站:https://pypi.org/project/marker-pdf 获得以下信息:

PDF is a tricky format, so marker will not always work perfectly. Here are some known limitations that are on the roadmap to address:

PDF 是一种复杂的格式,因此标记工具并非总能完美运行。以下是我们计划解决的一些已知限制:

  • Very complex layouts, with nested tables and forms, may not work

    布局非常复杂的内容,包含嵌套表格和表单,可能无法正常处理

  • Forms may not be rendered well

    表单可能无法正常渲染

Note: Passing the --use_llm and --force_ocr flags will mostly solve these issues.

注意:传入--use_llm--force_ocr参数可基本解决这些问题。

When running with the --use_llm flag, you have a choice of services you can use:

当使用--use_llm标志运行时,你可以选择要使用的服务:

  • Gemini - this will use the Gemini developer API by default. You'll need to pass --gemini_api_key to configuration.Gemini - 默认将使用 Gemini 开发者 API。你需要在配置中传入 --gemini_api_key
  • Google Vertex - this will use vertex, which can be more reliable. You'll need to pass --vertex_project_id. To use it, set --llm_service=marker.services.vertex.GoogleVertexService.Google Vertex - 这将使用 vertex,它可能更可靠。你需要传入 --vertex_project_id。要使用它,请设置 --llm_service=marker.services.vertex.GoogleVertexService
  • Ollama - this will use local models. You can configure --ollama_base_url and --ollama_model. To use it, set --llm_service=marker.services.ollama.OllamaService.Ollama - 这将使用本地模型。你可以配置 --ollama_base_url--ollama_model。要使用它,请设置 --llm_service=marker.services.ollama.OllamaService
  • Claude - this will use the anthropic API. You can configure --claude_api_key, and --claude_model_name. To use it, set --llm_service=marker.services.claude.ClaudeService.Claude - 这将使用 Anthropic API。你可以配置 --claude_api_key--claude_model_name。要使用它,请设置 --llm_service=marker.services.claude.ClaudeService
  • OpenAI - this supports any openai-like endpoint. You can configure --openai_api_key, --openai_model, and --openai_base_url. To use it, set --llm_service=marker.services.openai.OpenAIService.OpenAI - 该配置支持任何类 OpenAI 的端点。你可以配置 --openai_api_key--openai_model--openai_base_url。要使用它,请设置 --llm_service=marker.services.openai.OpenAIService
  • Azure OpenAI - this uses the Azure OpenAI service. You can configure --azure_endpoint, --azure_api_key, and --deployment_name. To use it, set --llm_service=marker.services.azure_openai.AzureOpenAIService.Azure OpenAI - 该选项使用 Azure OpenAI 服务。你可以配置 --azure_endpoint--azure_api_key--deployment_name。要使用它,请设置 --llm_service=marker.services.azure_openai.AzureOpenAIService
shell 复制代码
marker_single input.pdf output.md --force_ocr

错误信息:File "~/miniconda3/envs/p2m/bin/marker_single", line 3, in from marker.scripts.convert_single import convert_single_cli File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/marker/scripts/convert_single.py", line 12, in from marker.config.parser import ConfigParser File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/marker/config/parser.py", line 7, in from marker.converters.pdf import PdfConverter File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/marker/converters/pdf.py", line 17, in from marker.builders.document import DocumentBuilder File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/marker/builders/document.py", line 5, in from marker.builders.line import LineBuilder File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/marker/builders/line.py", line 9, in from surya.ocr_error import OCRErrorPredictor File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/surya/ocr_error/init.py", line 7, in from surya.ocr_error.loader import OCRErrorModelLoader File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/surya/ocr_error/loader.py", line 7, in from surya.ocr_error.model.config import DistilBertConfig File "~/miniconda3/envs/p2m/lib/python3.12/site-packages/surya/ocr_error/model/config.py", line 5, in from transformers.onnx import OnnxConfig ModuleNotFoundError: No module named 'transformers.onnx'

错位原因:当前环境中安装的 transformers 库版本过高,移除了 transformers.onnx 子模块,而 suryamarker 的依赖)还在尝试导入它。transformers.onnxtransformers>=4.30` 中就已经被标记为弃用,并在较新版本中彻底删除。最有效的方法通过创建Python3.10虚拟环境处理。

通过--force_ocr处理乱码和内容丢失基本解决,公式的正确性也提高了。或许可以通过支持OpenAI风格的国内模型,获得更加满意的效果。

以下阶段二,区别于传统方案,基于能力强的视觉模型直接读图翻译,也是一种尝试[需要付费]。


第二阶段:PDF→图片→视觉模型直接转换

3.1 新策略设计

面对Marker的局限,设计了新的转换流程:

复制代码
PDF → 高清图片 → 筛选图片 → 视觉模型 → Markdown → 后处理 → 最终MD
3.3 实施过程

Step 1: PDF转高清图片

python 复制代码
import os
import shutil
from pdf2image import convert_from_path, pdfinfo_from_path
from tqdm import tqdm

# ---------------- 配置区域 ----------------
PDF_PATH = "/home/pdf/ld.pdf"          # PDF文件名,建议用绝对路径
OUTPUT_DIR = "/home/pdf/images"            # 图片保存文件夹
BATCH_SIZE = 20                        # 每批处理 20 页,防止内存爆炸
DPI = 300                              # 最佳 OCR 分辨率
JPEG_QUALITY = 95                      # 高质量 JPEG
# ---------------------------------------

def main():
    # 1. 确保输出目录存在
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    # 2. 检测总页数
    print("正在统计 PDF 页数......")
    try:
        info = pdfinfo_from_path(PDF_PATH)
        total_pages = info["Pages"]
    except Exception as e:
        print(f"❌ 读取页数失败,请检查文件路径或权限: {e}")
        return

    print(f"✅ 总页数: {total_pages} 页")

    # 3. 分批转换
    # 在 Ubuntu 下,不需要 poppler_path,系统会自动调用 poppler-utils
    for start_page in tqdm(range(1, total_pages + 1, BATCH_SIZE),
                            desc="⏳ 正在转换", unit="批"):
        end_page = min(start_page + BATCH_SIZE - 1, total_pages)

        try:
            # 只转换这一批次页面
            batch_images = convert_from_path(
                PDF_PATH,
                dpi=DPI,
                first_page=start_page,
                last_page=end_page,
                fmt='jpeg'
            )
        except Exception as e:
            print(f"\n❌ 处理第 {start_page}~{end_page} 页时出错: {e}")
            continue  # 跳过这一批继续

        # 保存图片并释放内存
        for i, img in enumerate(batch_images):
            page_num = start_page + i
            save_path = os.path.join(OUTPUT_DIR, f"page_{page_num:04d}.jpg")
            img.save(save_path, "JPEG", quality=JPEG_QUALITY)

        # 手动释放内存
        del batch_images

    print(f"\n✅ 转换完成!图片保存在: {os.path.abspath(OUTPUT_DIR)}")

if __name__ == "__main__":
    main()

参数说明

zoom=2.0:生成2倍分辨率图片,确保文字清晰,输出格式:PNG(无损压缩),命名规则:page_0001.png(便于排序)

Step 2: 图片筛选

手动去除,目录页,序言/致谢,参考文献,索引

Step 3: 调用视觉多模态模型

这里调用了qwen3.6-plus模型。

python 复制代码
import os
import pathlib
import dashscope
import time
dashscope.base_http_api_url = "https://dashscope.aliyuncs.com/api/v1"

prompt = """
    请将这张图片中的内容转换为Markdown格式,要求:
    1. 完整保留原文档格式(标题、段落、列表等)
    2. 所有数学公式必须用LaTeX格式表示(行内公式用$...$,块级公式用$$...$$)
    3. 图片中的图表用适当文字描述其内容
    4. 代码块用```语言 包裹
    5. 表格用Markdown表格格式
    6. 保持原文档的章节结构
    
    直接输出Markdown内容,不要添加额外解释。
    """

def image_to_markdown(image_path: str):
	messages = [
    {
        "role": "user",
        "content": [
            {"image": image_path},
            {"text": prompt}
		]
    }]
	response = dashscope.MultiModalConversation.call(
		api_key='sk-xxxx',
		model='qwen3.6-plus',
		messages=messages
	)
	return response.output.choices[0].message.content[0]["text"]
	

def main():
    FILE_PATH = '/home/pdf/images'
    OUTPUT = '/home/pdf/rd.md'
    with open(OUTPUT, 'a', encoding='utf-8') as f:
        for item in sorted(pathlib.Path(FILE_PATH).rglob("*")):
            print(f"✔开始处理{item.name}")
            f.write(f"{image_to_markdown(str(item.resolve()))}\n")
            f.flush()
            time.sleep(0.5)
        print(f"✔处理完成")
	
if __name__ == "__main__":
	main()

关键技术点

  1. Prompt设计:明确要求LaTeX格式、描述图片内容
  2. 图片质量:2倍分辨率确保文字清晰
  3. 错误处理:API失败重试机制(代码中可添加)
  4. 成本控制:筛选必要页面,避免无效转换

Step 4: 后处理Markdown

转换后的Markdown需要后处理, 可用Agent处理,也需要人工审核:清除页眉页脚,合并分页内容:表格、段落被分页切断,统一格式:确保标题层级、列表格式一致

结论:多模态方案在图像识别上效果突出,不仅能保持结构清晰,文字,表格,公式精确,图片还能直接通过语言描述总结,这几乎保留了原始文档的全貌。共转换977张高质量image,每张图片大概消耗4000 token ,共花费25元。适合对准确性要求高的场景。


最后

在工具选择上,快速原型开发可以优先使用 Marker,而追求高质量生产环境可以采用多模态大模型。为了平衡成本与质量,可以采用混合方案:先用 Marker 完成初步转换,再用多模态大模型对结果进行精修,同时辅以人工审核关键章节、筛选必要页面,并在质量可接受范围内适当降低图片分辨率。例如,可以通过 PIL 库压缩图片,设置合适的 quality 参数来减小图片体积。此外,也可以考虑使用更便宜的模型来进一步控制成本。

python 复制代码
# 使用PIL压缩图片

  from PIL import Image

  def compress_image(input_path, output_path, quality=85):
      img = Image.open(input_path)
      img.save(output_path, 'PNG', optimize=True, quality=quality)

未来的优化方向包括:自动化后处理,如在转换过程中直接忽略页眉页脚、开发自动表格合并算法;模型微调,利用专业书籍数据对模型进行针对性训练,以提升公式识别准确率;以及流水线优化,开发一站式转换工具,并支持更多输出格式如 JSON 和 HTML。


愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!

相关推荐
TechWayfarer1 小时前
账号安全实战:基于IP归属地基线的三原则异地登录风控模型
服务器·网络·python·安全·网络安全
dhashdoia2 小时前
2026年GPT-5.5与GPT-Image-2深度解析:国内部署指南
人工智能·python·gpt·ai作画·gpt国内部署
jayson.h2 小时前
正则表达式:从文件名提取器件编号
开发语言·python·正则表达式
2601_953660372 小时前
Java Map集合详解与实战
java·开发语言·python
一起逃去看海吧2 小时前
DIFY-02-ollama安装与接入大模型
python
YuanDaima20482 小时前
云计算基础与容器技术演进
java·服务器·人工智能·python·深度学习·云计算·个人开发
小小秃头怪2 小时前
径向网格构建
python
SEO-狼术2 小时前
Make Range Input Simpler
pdf·.net
爱跑步的程序员~2 小时前
RAG 技术全面解析:从原理到实践
python·ai·langchain·rag