如何快速实现任意PDF转换Markdown
2025/6/17大约 4 分钟
PDFConverter 使用说明
简介
MyPDFConverter
是一个用于批量处理 PDF 文件,将其内容(包括表格、图片等)提取并转换为 Markdown 文件的工具。支持图片上传至 MinIO,并自动替换 Markdown 内部图片链接。
依赖
- Python 3.x
- docling_core
- docling
- commonutils
- MinIO(可选,用于图片上传)
主要功能
- 批量处理指定目录下所有 PDF 文件
- 自动提取表格并保存为图片
- 支持 OCR(光学字符识别)
- 生成 Markdown 文件,图片可上传至 MinIO 并替换链接
代码
import logging
import time
from pathlib import Path
from docling_core.types.doc import ImageRefMode, PictureItem, TableItem
from docling.datamodel.base_models import FigureElement, InputFormat, Table
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.pipeline_options import PdfPipelineOptions
from docling.backend.pypdfium2_backend import PyPdfiumDocumentBackend
from docling.datamodel.pipeline_options import PdfPipelineOptions, EasyOcrOptions
from docling.document_converter import (
DocumentConverter,
InputFormat,
PdfFormatOption,
)
from docling.datamodel.pipeline_options import TableFormerMode
import re, os
from commonutils import clean_text_in_name
_log = logging.getLogger(__name__)
IMAGE_RESOLUTION_SCALE = 2.0
class MyPDFConverter:
def __init__(self, minio_util = None):
logging.basicConfig(level=logging.INFO)
self.setup_pipeline_options()
self.setup_document_converter()
self.minio_util = minio_util
def setup_pipeline_options(self):
# 使用EasyOcrOptions
#全英文 使用force_full_page_ocr=True
#否则 使用force_full_page_ocr=False
# 判断文件是否全英文
ocr_options = EasyOcrOptions(force_full_page_ocr=True, lang=["en","ch_sim"])
self.pipeline_options = PdfPipelineOptions(ocr_options=ocr_options)
self.pipeline_options.ocr_options = ocr_options
self.pipeline_options.do_ocr = True
# self.pipeline_options.do_table_structure = False
# self.pipeline_options.table_structure_options.do_cell_matching = False
# self.pipeline_options.table_structure_options.mode = TableFormerMode.ACCURATE
# self.pipeline_options.generate_table_images = False
self.pipeline_options.generate_picture_images = True
# self.pipeline_options.do_picture_classification = True
self.pipeline_options.images_scale = IMAGE_RESOLUTION_SCALE
def setup_document_converter(self):
self.doc_converter = DocumentConverter(
format_options={
InputFormat.PDF: PdfFormatOption(
pipeline_options=self.pipeline_options,
backend=PyPdfiumDocumentBackend
)
}
)
def process_directory(self, input_dir: str, output_dir: str, progress_callback=None):
"""处理指定目录下的所有PDF文件
Args:
input_dir (str): 输入目录路径
output_dir (str): 输出目录路径
"""
input_path = Path(input_dir)
if not input_path.exists():
raise FileNotFoundError(f"输入目录不存在: {input_dir}")
# 遍历目录找到所有PDF文件
pdf_files = list(input_path.glob("**/*.pdf"))
_log.info(f"在目录 {input_dir} 中找到 {len(pdf_files)} 个PDF文件")
for pdf_file in pdf_files:
try:
md_file = self.process_pdf(pdf_file, Path(output_dir))
if md_file:
self.parse_markdown(md_file)
if progress_callback:
progress_callback()
except Exception as e:
_log.error(f"处理文件 {pdf_file} 时出错: {str(e)}")
def process_pdf(self, input_doc_path: Path, output_dir: Path):
"""处理单个PDF文件
Args:
input_doc_path (Path): PDF文件路径
output_dir (Path): 输出目录路径
"""
if not input_doc_path.exists():
raise FileNotFoundError(f"输入文件不存在: {input_doc_path}")
start_time = time.time()
_log.info(f"开始转换PDF文档: {input_doc_path}")
try:
conv_res = self.doc_converter.convert(input_doc_path)
output_dir.mkdir(parents=True, exist_ok=True)
doc_filename = clean_text_in_name(conv_res.input.file.stem)
# 保存图片和表格
table_counter = 0
picture_counter = 0
for element, _level in conv_res.document.iterate_items():
if isinstance(element, TableItem):
table_counter += 1
element_image_filename = (
output_dir / f"{doc_filename}-table-{table_counter}.png"
)
if element.get_image(conv_res.document):
with element_image_filename.open("wb") as fp:
element.get_image(conv_res.document).save(fp, "PNG")
_log.info(f"已保存表格 {table_counter}: {element_image_filename}")
# if isinstance(element, PictureItem):
# picture_counter += 1
# element_image_filename = (
# output_dir / f"{doc_filename}-picture-{picture_counter}.png"
# )
# if element.get_image(conv_res.document):
# with element_image_filename.open("wb") as fp:
# element.get_image(conv_res.document).save(fp, "PNG")
# _log.info(f"已保存图片 {picture_counter}: {element_image_filename}")
md_filenamev2 = output_dir / f"{doc_filename}.md"
conv_res.document.save_as_markdown(md_filenamev2, image_mode=ImageRefMode.REFERENCED)
_log.info(f"已保存Markdown文件: {md_filenamev2}")
end_time = time.time() - start_time
_log.info(f"文档转换完成,用时 {end_time:.2f} 秒。")
return md_filenamev2
except Exception as e:
_log.error(f"转换过程中出错: {str(e)}")
raise
def parse_markdown(self, md_filename: Path):
## 获取文件名
doc_filename = md_filename.stem
## 获取文件的父目录
output_dir = md_filename.parent
with open(md_filename, 'r', encoding='utf-8') as f:
content = f.read()
# 解析markdown内容
# 找到所有图片 
# 提取图片路径
image_paths = re.findall(r'!\[Image\]\((.*?)\)', content)
for image_path in image_paths:
print(image_path)
if os.path.exists(image_path):
print(f"图片存在: {image_path}")
## upload image to minio
if self.minio_util:
minio_url = self.minio_util.upload_to_minio(image_path, doc_filename)
# 替换markdown内部图片地址为minio地址
original_img_markdown = f""
new_img_markdown = f''
content = content.replace(original_img_markdown, new_img_markdown)
else:
original_img_markdown = f""
new_img_markdown = f''
content = content.replace(original_img_markdown, new_img_markdown)
else:
print(f"图片不存在: {image_path}")
# 当前文件已经被打开, 需要新建一个文件
md_filename_new = output_dir / f"{doc_filename}_new.md"
# 保存markdown文件
with open(md_filename_new, 'w', encoding='utf-8') as f:
f.write(content)
使用方法
1. 初始化
from pdfconverterv3 import MyPDFConverter
# 如果需要上传图片到 MinIO,需传入 minio_util 实例
converter = MyPDFConverter(minio_util=None)
2. 批量处理目录下所有 PDF
input_dir = "输入PDF文件的目录路径"
output_dir = "输出Markdown文件的目录路径"
converter.process_directory(input_dir, output_dir)
input_dir
:待处理的 PDF 文件目录output_dir
:生成的 Markdown 文件及图片的输出目录
3. 单独处理一个 PDF 文件
from pathlib import Path
pdf_path = Path("example.pdf")
output_dir = Path("output")
converter.process_pdf(pdf_path, output_dir)
4. 解析并处理生成的 Markdown 文件
md_path = Path("output/example.md")
converter.parse_markdown(md_path)
进阶用法
- 支持自定义 MinIO 工具类,实现
upload_to_minio(image_path, doc_filename)
方法,即可自动上传图片并替换 Markdown 内部图片链接为 MinIO 地址。
注意事项
- 需保证依赖库已正确安装。
- 处理过程中如遇图片不存在或上传失败,会有日志提示。
- 生成的 Markdown 文件会自动处理图片链接,若未配置 MinIO,则图片路径为本地路径。
示例
if __name__ == "__main__":
converter = MyPDFConverter()
converter.process_directory(
"your_pdf_dir",
"your_output_dir"
)
日志
- 日志级别为 INFO,处理进度和错误会输出到控制台。