pdf转md的一种思路

自己写的一套可行的流程

核心思路

将PDF转换为Markdown的过程分为四个关键步骤:

  1. PDF到Word转换
  2. Word到Markdown转换
  3. 水印去除
  4. 图片处理

下面我们将详细介绍每个步骤,并附上相关的代码实现。

具体方案及代码实现

1. PDF到Word转换

使用Microsoft Office将PDF转换为Word格式。这一步利用了Office强大的布局识别能力,能更好地处理复杂的PDF结构。

def convert_pdf_to_word(pdf_path, word_path):
    word = win32com.client.Dispatch("Word.Application")
    try:
        word.Visible = False
        doc = word.Documents.Open(pdf_path)
        doc.SaveAs(word_path, FileFormat=16)  # 16 表示 .docx 格式
        doc.Close()
    except Exception as e:
        print(f"PDF 转 Word 过程中发生错误: {str(e)}")
        raise
    finally:
        word.Quit()

2. Word到Markdown转换

采用Aspose.Words库将Word文档转换为Markdown。Aspose.Words在保持文档结构和格式方面表现出色。

def convert_word_to_markdown_and_clean(input_file, output_folder, original_filename):
    file_name = original_filename or os.path.splitext(os.path.basename(input_file))[0]
    images_folder = os.path.join(output_folder, "images")
    os.makedirs(images_folder, exist_ok=True)

    doc = aw.Document(input_file)
    save_options = aw.saving.MarkdownSaveOptions()
    save_options.images_folder = images_folder
    save_options.images_folder_alias = "./images"

    markdown_file = os.path.join(output_folder, f"{file_name}.md")
    doc.save(markdown_file, save_options)

3. 水印去除

使用正则表达式清理Aspose生成的水印和其他不必要的标记,确保输出的Markdown文件整洁。(当然,用aspose付费版可以忽略)

def post_process_markdown(text):
    patterns = [
        r'\n\*\*Evaluation Only\. Created with Aspose\.Words\. Copyright 2003-2024 Aspose Pty Ltd\.\*\*\n',
        r'!\[ref\d+\]',
        r'\n\*\*Created with an evaluation copy of Aspose\.Words\. To remove all limitations, you can use Free Temporary License \[https://products\.aspose\.com/words/temporary-license/\*\*\]\(https://products\.aspose\.com/words/temporary-license/\)\*\*\n',
        r'\*\*This document was truncated here because it was created in the Evaluation Mode\.\*\*'
    ]
    for pattern in patterns:
        text = re.sub(pattern, '', text)
    return text

4. 图片处理

  • 创建单独的images文件夹存储文档中的图片
  • 设置相对路径确保Markdown中的图片链接正确
  • 删除可能的水印图片(第一张图片必定是水印)
def convert_word_to_markdown_and_clean(input_file, output_folder, original_filename):
    # ... [前面的代码]

    # 删除images文件夹中的第一张图片(水印)
    image_files = sorted(glob.glob(os.path.join(images_folder, "*")))
    if image_files:
        watermark_image = image_files[0]
        os.remove(watermark_image)
        print(f"已删除水印图片: {watermark_image}")

局限

  • 依赖Microsoft Office,在某些环境(如服务器)中不便使用
  • 毕竟是正则去除水印,效果不稳定,但是免费

使用方法

  1. 完整代码在文章末尾,复制到本地
  2. 安装所需的Python库:
    pip install pywin32 aspose-words
    
  3. 运行脚本:
    python pdf_word_to_markdown.py
    
  4. 按照提示输入包含PDF和Word文件的输入文件夹路径和输出文件夹路径
  5. 确认开始转换
  • 确保您的系统上安装了Microsoft Office
  • 转换大文件可能需要一些时间,请耐心等待
  • 转换结果可能因原始文档的复杂程度而异

完整代码

以下是实现这个PDF到Markdown转换工具的完整Python代码:

import os
import win32com.client
import aspose.words as aw
import re
import time
import glob


def convert_pdf_to_word(pdf_path, word_path):
    """
    使用 Microsoft Word 将 PDF 文件转换为 Word 文档

    :param pdf_path: PDF 文件的路径
    :param word_path: 输出 Word 文件的路径
    """
    word = win32com.client.Dispatch("Word.Application")
    try:
        word.Visible = False
        print(f"正在打开 PDF 文件: {pdf_path}")
        doc = word.Documents.Open(pdf_path)
        print("PDF 文件已成功打开")
        print(f"正在保存为 Word 文件: {word_path}")
        doc.SaveAs(word_path, FileFormat=16)  # 16 表示 .docx 格式
        print("Word 文件已保存")
        doc.Close()
        print("Word 文档已关闭")

        # 等待文件创建完成
        for _ in range(10):  # 尝试10次,每次等待1秒
            if os.path.exists(word_path):
                print(f"Word 文件已成功创建: {word_path}")
                return
            time.sleep(1)

        if not os.path.exists(word_path):
            raise FileNotFoundError(f"Word 文件未能成功创建: {word_path}")

    except Exception as e:
        print(f"PDF 转 Word 过程中发生错误: {str(e)}")
        raise
    finally:
        word.Quit()


def post_process_markdown(text):
    """
    对转换后的 Markdown 文本进行后处理

    :param text: 原始 Markdown 文本
    :return: 处理后的 Markdown 文本
    """
    # 移除水印
    pattern = r'\n\*\*Evaluation Only\. Created with Aspose\.Words\. Copyright 2003-2024 Aspose Pty Ltd\.\*\*\n'
    cleaned_text = re.sub(pattern, '', text)

    ref_pattern = r'!\[ref\d+\]'
    cleaned_text = re.sub(ref_pattern, '', cleaned_text)

    new_watermark_pattern = r'\n\*\*Created with an evaluation copy of Aspose\.Words\. To remove all limitations, you can use Free Temporary License \[https://products\.aspose\.com/words/temporary-license/\*\*\]\(https://products\.aspose\.com/words/temporary-license/\)\*\*\n'
    cleaned_text = re.sub(new_watermark_pattern, '', cleaned_text)

    final_pattern = r'\*\*This document was truncated here because it was created in the Evaluation Mode\.\*\*'
    cleaned_text = re.sub(final_pattern, '', cleaned_text)

    return cleaned_text


def convert_word_to_markdown_and_clean(input_file, output_folder, original_filename):
    try:
        # 使用原始文件名(不包括扩展名)
        file_name = original_filename or os.path.splitext(os.path.basename(input_file))[0]

        # 创建images子文件夹
        images_folder = os.path.join(output_folder, "images")
        os.makedirs(images_folder, exist_ok=True)

        # 加载Word文档
        doc = aw.Document(input_file)

        # 设置保存选项
        save_options = aw.saving.MarkdownSaveOptions()
        save_options.images_folder = images_folder
        save_options.images_folder_alias = "./images"  # 设置图片文件夹别名为相对路径

        # 将文档转换为Markdown
        markdown_file = os.path.join(output_folder, f"{file_name}.md")
        doc.save(markdown_file, save_options)

        # 读取生成的Markdown文件
        with open(markdown_file, "r", encoding="utf-8") as file:
            markdown_content = file.read()

        # 应用后处理
        processed_content = post_process_markdown(markdown_content)

        # 保存处理后的内容到新文件
        with open(markdown_file, "w", encoding="utf-8") as file:
            file.write(processed_content)

        print(f"转换完成!结果保存在 {markdown_file}")

        # 删除images文件夹中的第一张图片(水印)
        image_files = sorted(glob.glob(os.path.join(images_folder, "*")))
        if image_files:
            watermark_image = image_files[0]
            os.remove(watermark_image)
            print(f"已删除水印图片: {watermark_image}")

    except Exception as e:
        print(f"发生错误: {str(e)}")
        raise


def convert_to_markdown(input_path, output_folder):
    # 获取输入文件的名称(不包括扩展名)
    file_name = os.path.splitext(os.path.basename(input_path))[0]

    # 创建以文件名命名的输出文件夹
    file_output_folder = os.path.join(output_folder, file_name)
    os.makedirs(file_output_folder, exist_ok=True)

    if input_path.lower().endswith('.pdf'):
        # 临时 Word 文件路径
        temp_word_path = os.path.join(file_output_folder, "temp_word_file.docx")
        try:
            # 步骤 1:将 PDF 转换为 Word
            convert_pdf_to_word(input_path, temp_word_path)
            print("PDF 已成功转换为 Word")

            # 步骤 2:将 Word 转换为 Markdown 并清理
            convert_word_to_markdown_and_clean(temp_word_path, file_output_folder, file_name)
        finally:
            # 删除临时 Word 文件
            if os.path.exists(temp_word_path):
                os.remove(temp_word_path)
                print(f"临时 Word 文件已删除: {temp_word_path}")
    elif input_path.lower().endswith(('.doc', '.docx')):
        # 直接将 Word 转换为 Markdown
        convert_word_to_markdown_and_clean(input_path, file_output_folder, None)
    else:
        print(f"不支持的文件格式: {input_path}")
        raise ValueError(f"不支持的文件格式: {input_path}")


def count_files(folder):
    pdf_count = len(glob.glob(os.path.join(folder, "*.pdf")))
    word_count = len(glob.glob(os.path.join(folder, "*.docx"))) + len(glob.glob(os.path.join(folder, "*.doc")))
    return pdf_count + word_count


def batch_convert(input_folder, output_folder):
    """
    批量转换文件夹中的所有PDF和Word文件为Markdown

    :param input_folder: 包含PDF和Word文件的输入文件夹路径
    :param output_folder: 保存Markdown文件的输出文件夹路径
    """
    # 确保输出文件夹存在
    os.makedirs(output_folder, exist_ok=True)

    # 获取输入文件夹中所有的PDF和Word文件
    input_files = glob.glob(os.path.join(input_folder, "*.pdf"))
    input_files.extend(glob.glob(os.path.join(input_folder, "*.docx")))
    input_files.extend(glob.glob(os.path.join(input_folder, "*.doc")))

    if not input_files:
        print(f"在 {input_folder} 中没有找到PDF或Word文件。")
        return

    total_files = len(input_files)
    successful_files = 0
    failed_files = []

    for index, input_file in enumerate(input_files, start=1):
        print(f"正在处理: {input_file} ({index}/{total_files}, {index / total_files * 100:.2f}%)")
        try:
            convert_to_markdown(input_file, output_folder)
            successful_files += 1
        except Exception as e:
            print(f"处理文件 {input_file} 时出错: {str(e)}")
            failed_files.append(input_file)
        print(f"完成处理: {input_file}")
        print("-" * 50)

        time.sleep(2)

    print(f"所有文件处理完成!成功转换 {successful_files} 个文件。")
    if failed_files:
        print(f"以下 {len(failed_files)} 个文件处理失败:")
        for file in failed_files:
            print(f"- {file}")


def main():
    print("欢迎使用PDF和Word到Markdown批量转换工具!")

    while True:
        input_folder = input("请输入包含PDF和Word文件的文件夹路径: ").strip()
        if os.path.isdir(input_folder):
            break
        else:
            print("输入的路径不是有效的文件夹,请重新输入。")

    while True:
        output_folder = input("请输入保存Markdown文件的输出文件夹路径: ").strip()
        if os.path.isdir(output_folder) or not os.path.exists(output_folder):
            break
        else:
            print("输入的路径不是有效的文件夹,请重新输入。")

    file_count = count_files(input_folder)
    print(f"\n输入文件夹: {input_folder}")
    print(f"输出文件夹: {output_folder}")
    print(f"待处理文件数量: {file_count}\n")

    confirm = input("确认开始转换吗?(y/n): ").strip().lower()
    if confirm == 'y':
        batch_convert(input_folder, output_folder)
    else:
        print("操作已取消。")


if __name__ == "__main__":
    main()