复杂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_llmand--force_ocrflags will mostly solve these issues.注意:传入
--use_llm和--force_ocr参数可基本解决这些问题。When running with the
--use_llmflag, 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_keyto 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_urland--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 子模块,而 surya(marker 的依赖)还在尝试导入它。transformers.onnx在transformers>=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()
关键技术点:
- Prompt设计:明确要求LaTeX格式、描述图片内容
- 图片质量:2倍分辨率确保文字清晰
- 错误处理:API失败重试机制(代码中可添加)
- 成本控制:筛选必要页面,避免无效转换
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。
愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!