乐于分享
好东西不私藏

27_PDF格式转换

27_PDF格式转换

摘要:PDF 虽然是文档交换的标准格式,但在编辑和数据提取方面却极其不便。用户经常需要将 PDF 转换为可编辑的 Word、可分析的 Excel 或用于演示的 PPT。

本章将深入探讨 Python 生态中实现这些转换的主流方案。我们将使用 pdf2docx 重构文档布局,利用 pdfplumber 提取表格数据,结合 python-pptx 生成演示文稿,并讨论在没有商业级 SDK(如 Adobe/Aspose)的情况下,如何通过开源方案达到生产可用的转换质量。

关联的小程序样例:薄荷百宝箱

27.1 PDF 转 Word (docx)

27.1.1 选型分析:为什么是 pdf2docx?

要将 PDF 转为 Word,最困难的不是提取文本,而是恢复布局(Layout Recovery)。我们需要保持段落、表格、图片在 Word 中的相对位置。

python-docx:只能写 Word,不具备解析 PDF 布局的能力。
pdf2docx:这是目前 Python 开源界最成熟的方案。它基于 PyMuPDF (fitz) 提取原始数据,利用算法重建段落、表格和图片,并生成 .docx 文件。

27.1.2 核心代码实现

app/plugins/pdf/to_word.py 中,我们封装转换逻辑。注意:这是一个计算密集型任务,必须在线程池或独立进程中运行。

import os
from pdf2docx import Converter
from app.utils.file_helper import get_temp_path

def convert_pdf_to_docx(pdf_path: str) -> str:
    """
    将 PDF 转换为 Word 文档
    :param pdf_path: 输入 PDF 文件路径
    :return: 输出 Docx 文件路径
    """
    # 1. 确定输出路径
    docx_path = pdf_path.replace(".pdf", ".docx")

    cv = None
    try:
        # 2. 初始化转换器
        cv = Converter(pdf_path)

        # 3. 执行转换
        # start=0, end=None: 转换所有页面
        # multi_processing=True: 开启多进程加速(注意在 Web 容器中可能需要关闭)
        cv.convert(docx_path, start=0, end=None, multi_processing=False)

    except Exception as e:
        # 转换失败需清理残留文件
        if os.path.exists(docx_path):
            os.remove(docx_path)
        raise RuntimeError(f"PDF转Word失败: {str(e)}")
    finally:
        # 4. 释放资源
        if cv:
            cv.close()

    return docx_path

常见坑

扫描版 PDF:如果 PDF 是纯图片扫描件,pdf2docx 只能将图片插入 Word,无法生成可编辑文本。这时需要先经过 OCR 层(见后续章节)。
复杂排版:对于双栏排版或复杂的杂志排版,开源工具的还原度通常不如商业软件(如 Acrobat)。

27.2 PDF 转 Excel

27.2.1 选型分析:为什么是 pdfplumber?

PDF 中的表格本质上只是一堆画线指令和绝对定位的文本。

camelot:擅长有线表格,但依赖 Ghostscript,部署麻烦。
pdfplumber:纯 Python 实现,基于 pdfminer.six。它提供了强大的可视化调试工具,能很好地处理有线和无线表格,是提取数据的首选。

27.2.2 核心代码实现

我们需要遍历每一页,提取表格数据,然后用 pandas 写入 Excel。

import pdfplumber
import pandas as pd
from typing import List

def convert_pdf_to_excel(pdf_path: str) -> str:
    excel_path = pdf_path.replace(".pdf", ".xlsx")

    all_tables = []

    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            # extract_tables 返回一个嵌套列表: [[[row1_col1, row1_col2], ...], ...]
            tables = page.extract_tables()
            for table in tables:
                # 清洗数据:去除 None 和换行符
                cleaned_table = [
                    [cell.replace('\n', ' ') if cell else '' for cell in row]
                    for row in table
                ]
                if cleaned_table:
                    all_tables.extend(cleaned_table)

    if not all_tables:
        raise ValueError("未在 PDF 中检测到有效表格")

    # 使用 pandas 生成 Excel
    df = pd.DataFrame(all_tables)
    # 假设第一行是表头(根据业务需求调整)
    # df.columns = df.iloc[0]
    # df = df[1:]

    df.to_excel(excel_path, index=False, header=False)

    return excel_path

27.3 PDF 转 PPT

27.3.1 技术难点与妥协

将 PDF 转为可编辑的 PPT(每个元素都是独立的 TextBox/Shape)在开源界是一个极高难度的任务,目前没有成熟的库能完美做到。

工程化方案“图片化 PPT”

1.将 PDF 每一页转为高清图片。
2.创建一个空白 PPT。
3.将图片铺满每一张幻灯片。

虽然不可编辑文字,但这满足了 90% 用户“在 PPT 中展示 PDF 内容”的需求。

27.3.2 核心代码实现

依赖:pdf2image (需要安装系统库 poppler) 和 python-pptx

from pdf2image import convert_from_path
from pptx import Presentation
from pptx.util import Inches
import os

def convert_pdf_to_ppt(pdf_path: str) -> str:
    ppt_path = pdf_path.replace(".pdf", ".pptx")

    # 1. 将 PDF 转为图片列表 (内存中)
    # dpi=150 权衡了清晰度和生成速度
    images = convert_from_path(pdf_path, dpi=150)

    # 2. 创建 PPT 对象
    prs = Presentation()

    # 获取默认幻灯片尺寸 (通常是 16:9 或 4:3)
    # 这里我们动态调整 PPT 尺寸以适应 PDF 页面
    if images:
        first_img = images[0]
        # python-pptx 默认单位是 Emu,这里简单设置宽高比例
        # 实际生产中建议固定 PPT 尺寸,让图片 center_inside
        prs.slide_width = int(first_img.width * 9525)  # pixel to emu approx
        prs.slide_height = int(first_img.height * 9525)

    for i, img in enumerate(images):
        # 3. 添加空白版式 (BLANK)
        slide_layout = prs.slide_layouts[6] 
        slide = prs.slides.add_slide(slide_layout)

        # 4. 保存临时图片文件 (python-pptx 需要文件路径或流)
        temp_img_path = f"{pdf_path}_temp_{i}.jpg"
        img.save(temp_img_path, 'JPEG')

        # 5. 插入图片,铺满幻灯片
        try:
            slide.shapes.add_picture(
                temp_img_path, 
                left=0, top=0, 
                width=prs.slide_width, 
                height=prs.slide_height
            )
        finally:
            if os.path.exists(temp_img_path):
                os.remove(temp_img_path)

    prs.save(ppt_path)
    return ppt_path

27.4 PDF 转图片

27.4.1 场景与选型

用于生成缩略图、长图预览或 OCR 预处理。

pdf2image:封装了 pdftoppmpdftocairo 工具,速度快,支持并发。

27.4.2 核心代码实现

from pdf2image import convert_from_path
import zipfile
from io import BytesIO

def convert_pdf_to_images(
    pdf_path: str, 
    fmt: str = 'png', 
    dpi: int = 200
) -> str:
    """
    将 PDF 转为图片压缩包
    """
    output_zip = pdf_path.replace(".pdf", ".zip")

    # thread_count: 开启多线程加速
    images = convert_from_path(pdf_path, dpi=dpi, fmt=fmt, thread_count=4)

    with zipfile.ZipFile(output_zip, 'w') as zf:
        for i, image in enumerate(images):
            # 将 PIL Image 转为 Bytes
            img_buffer = BytesIO()
            image.save(img_buffer, format=fmt.upper())

            # 写入 Zip
            zf.writestr(f"page_{i+1:03d}.{fmt}", img_buffer.getvalue())

    return output_zip

长图拼接技巧: 如果用户想要“生成长图”,可以使用 PIL (Image.new) 创建一个大画布,将 images 列表中的图片按顺序从上到下粘贴(Paste)上去。

27.5 转换质量控制

开源工具毕竟不是 Adobe Acrobat,面对复杂的 PDF 往往会出现格式错乱。我们需要建立一套质量控制机制。

27.5.1 失败降级策略

PDF 转 Word 为例:

1.优先尝试:使用 pdf2docx 进行可编辑转换。
2.捕获异常:如果转换报错(如加密、字体缺失)或用户反馈排版极其混乱。
3.降级处理:自动回退到“OCR 模式”或“图片模式”。将每一页转为图片,然后插入到 Word 中。虽然不可编辑,但至少保证了视觉一致性,让用户能看。

27.5.2 资源限制与保护

PDF 转换极其消耗 CPU 和内存。

分页处理:对于超过 50 页的 PDF,建议强制分页处理或放入队列排队,避免一个大任务卡死整个 Worker。
Poppler 隔离pdf2image 依赖系统级进程 pdftoppm。在 Docker 容器中,需确保安装了 poppler-utils,并限制子进程的内存使用。

27.5.3 字体问题

PDF 经常包含未嵌入的字体(Subset Fonts)。转换服务器必须安装常用的中文字体(如思源黑体、宋体),否则转换出的 Word 文档会出现乱码或方框。

# Dockerfile 示例:安装中文字体
RUN apt-get update && apt-get install -y fonts-noto-cjk

本章面试题与知识点总结

1. pdf2docx 转换 Word 的原理是什么?为什么它有时候会转换失败?

参考答案

原理pdf2docx 首先利用 PyMuPDF 提取 PDF 中的文本块、图片和矢量绘图指令。然后,它通过基于规则的算法(Rule-based Parsing)分析这些元素的位置关系,重构出段落(Paragraph)、表格(Table)和版式(Section),最后调用 python-docx 生成文件。
失败原因
扫描件:没有文本层,无法提取文字。
复杂布局:如报纸的多栏不规则排版,规则算法难以正确判定阅读顺序。
矢量表格:有些表格是用线条画出来的(无结构信息),算法可能无法识别其为 Table。

2. 在 Linux (Docker) 环境下使用 pdf2image 报错 Unable to get page count,怎么排查?

参考答案: 这通常是因为缺少系统依赖 poppler-utilspdf2image 只是一个 Python Wrapper,底层通过 subprocess 调用 pdftoppmpdftocairo 命令。 解决

1.确保 Dockerfile 中包含 RUN apt-get install -y poppler-utils
2.检查 PDF 文件是否损坏或加密。

3. 如何处理 PDF 转 Excel 时表格跨页的问题?

参考答案pdfplumber 是按页处理的,无法自动感知“这个表格跨页了”。 工程实践

1.提取所有页面的表格数据 List[List[str]]
2.表头启发式算法:检查每一页表格的第一行。如果第 N+1 页的第一行数据类型与第 N 页的表头不一致,且列数相同,大概率是同一个表格的延续。
3.合并:将后续页面的数据行(去除可能的重复表头后)追加到前一个表格的数据集中。
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 27_PDF格式转换

评论 抢沙发

1 + 3 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮