乐于分享
好东西不私藏

40_文档格式转换

40_文档格式转换

摘要:在办公自动化和文档管理系统中,PDF 是最终交付的标准格式。无论用户上传的是 Word 合同、Excel 报表还是 PPT 演示文稿,系统都需要具备将其高质量转换为 PDF 的能力,以便于预览、归档和打印。

本章将深入探讨 Python 生态中文档转 PDF 的主流方案。我们将对比 LibreOffice(免费开源)与 Aspose/Microsoft Office(商业/依赖系统)的优劣,并重点讲解如何在 Linux 服务器(无头模式)下,利用 LibreOffice 构建稳定、高并发的文档转换服务。

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

40.1 Word 转 PDF

40.1.1 选型分析

在 Windows 开发环境下,大家习惯调用 win32com 操控 MS Word 进行转换。但在 Linux 服务器(Docker)环境下,这行不通。

方案 依赖 优点 缺点 适用场景
docx2pdf MS Word 完美还原 必须运行在 Windows/macOS,且安装 Office 个人脚本
python-docx 轻量 无法转 PDF(只能读写 docx) 仅用于文本提取
LibreOffice LibreOffice 免费开源,Linux 可用,还原度尚可 安装包大,转换速度较慢 生产服务器首选
Aspose Java/Net 完美还原 昂贵的商业授权 企业级付费项目

40.1.2 使用 LibreOffice (Headless Mode)

LibreOffice 提供了一个命令行工具 soffice,可以在不启动 GUI 的情况下进行格式转换。

环境准备 (Dockerfile)

# 基础镜像推荐 python:3.10-slim
RUN apt-get update && apt-get install -y \
    libreoffice \
    libreoffice-writer \
    fonts-noto-cjk \
    && apt-get clean

Python 封装

# app/plugins/document/converter.py
import subprocess
import os
from app.utils.file_helper import get_output_path

def convert_to_pdf(input_path: str) -> str:
    """
    通用转换函数:支持 docx, xlsx, pptx -> pdf
    """
    output_dir = os.path.dirname(input_path)

    # 构建命令行
    # --headless: 无界面模式
    # --convert-to pdf: 目标格式
    # --outdir: 输出目录
    cmd = [
        "libreoffice",
        "--headless",
        "--convert-to", "pdf",
        "--outdir", output_dir,
        input_path
    ]

    try:
        # 转换过程可能耗时数秒,必须异步或线程池执行
        subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        # LibreOffice 默认输出同名但后缀为 .pdf 的文件
        filename = os.path.splitext(os.path.basename(input_path))[0] + ".pdf"
        output_path = os.path.join(output_dir, filename)

        if not os.path.exists(output_path):
            raise RuntimeError("转换成功但未找到输出文件")

        return output_path

    except subprocess.CalledProcessError as e:
        raise RuntimeError(f"LibreOffice转换失败: {e.stderr.decode()}")

40.2 Excel 转 PDF

40.2.1 页面布局难题

Excel 是无限画布,而 PDF 是固定分页(A4)。直接转换常遇到以下问题:

列被切断:一个宽表格被切成两页,甚至切掉一半。
无表头:第二页没有表头,无法阅读。

40.2.2 预处理技巧

虽然 LibreOffice 能转,但为了更好的效果,建议先用 openpyxl 调整 Excel 的打印设置。

from openpyxl import load_workbook

def optimize_excel_layout(input_path: str):
    wb = load_workbook(input_path)
    for ws in wb.worksheets:
        # 1. 设置打印区域:自适应列宽
        ws.page_setup.fitToWidth = 1  # 强制挤进 1 页宽
        ws.page_setup.fitToHeight = 0 # 高度不限

        # 2. 设置纸张方向 (如果是宽表,自动横向)
        if ws.max_column > 8: 
            ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE

    # 保存为临时文件再送去转换
    temp_path = input_path.replace(".xlsx", "_opt.xlsx")
    wb.save(temp_path)
    return temp_path

然后调用 convert_to_pdf(temp_path)

40.3 PPT 转 PDF

40.3.1 转换效果

LibreOffice 对 PPTX 的转换效果通常很好,因为 PPT 本身就是固定版式(Slide)。

40.3.2 字体缺失问题

PPT 设计往往使用各种花哨的字体。在 Linux 服务器上,如果缺少对应字体,LibreOffice 会自动替换为默认字体(如 DejaVu Sans),导致版面错乱(文字溢出文本框)。

解决方案

1.字体映射:将 Windows 下的常用字体(宋体、微软雅黑、Arial、Times New Roman)打包,COPY 到 Docker 镜像的 /usr/share/fonts/ 目录下,并运行 fc-cache -fv
2.转图片:如果排版要求极高,可以考虑先将 PPT 转为图片(每页一张图),再合并为 PDF。虽然不可选中文本,但绝对保证样式不乱。

40.4 格式兼容性处理

40.4.1 旧版格式 (doc, xls, ppt)

LibreOffice 同样支持 Office 97-2003 的旧版格式。我们的 convert_to_pdf 函数可以直接兼容。

40.4.2 文本文件 (txt, md)

对于 .txt,LibreOffice 会将其作为 Writer 文档处理。 :编码问题。Linux 默认 UTF-8,如果用户上传 GBK 编码的 txt,会乱码。

增强处理:先检测编码并转为 UTF-8。

import chardet

def convert_txt_to_utf8(input_path: str):
    with open(input_path, 'rb') as f:
        raw = f.read()
        result = chardet.detect(raw)
        encoding = result['encoding'] or 'utf-8'

    if encoding.lower() != 'utf-8':
        content = raw.decode(encoding, errors='ignore')
        with open(input_path, 'w', encoding='utf-8') as f:
            f.write(content)

40.5 转换质量保证

40.5.1 并发控制

LibreOffice 启动极慢(1-3秒),且内存占用大(几百 MB)。

禁止高并发:绝对不能每个请求都 subprocess.Popen 一个 LibreOffice 进程,这会瞬间吃光服务器内存。
队列机制:使用 Celery 或 Redis 队列,限制同时运行的转换任务数为 1 或 2(根据 CPU 核数)。

40.5.2 僵尸进程清理

soffice 进程有时候会卡死(Hang)不退出。我们需要在代码中加入超时控制。

try:
    subprocess.run(cmd, timeout=30, ...) # 设置 30秒 超时
except subprocess.TimeoutExpired:
    # 强杀进程
    # 注意:需要精细查找对应的 pid,避免杀错
    pass

40.5.3 结果校验

转换成功不代表内容正确。

页数校验:利用 pypdf 读取生成的 PDF 页数,如果为 0 或远少于预期,标记为异常。
文件大小:如果生成的 PDF 只有几 KB(可能是空文件),报警。

本章面试题与知识点总结

1. 在 Linux 下进行文档转换,为什么首选 LibreOffice 而不是 Pandoc?

参考答案

Pandoc:主要用于标记语言(Markdown, HTML, LaTeX)之间的转换。它处理 Word (docx) 时是基于 XML 结构的解析,完全丢失样式和布局(页眉页脚、分页、复杂的表格样式)。
LibreOffice:是一个完整的办公套件,它拥有渲染引擎。转换 PDF 时,它是真正地“打印”文档,能最大程度保留原文档的视觉布局(WYSIWYG)。

2. 如何解决 Excel 转 PDF 时列宽被截断的问题?

参考答案: 这是因为 Excel 是流式布局,而 PDF 是分页布局。 工程方案

1.预处理:使用 openpyxl 修改 Excel 文件的 PrintSettings。设置 ws.page_setup.fitToWidth = 1,强制所有列缩放到一页宽。
2.调整方向:检测列数,如果列很多,自动将纸张方向设为横向(Landscape)。

3. LibreOffice 在 Docker 中运行有什么性能瓶颈?如何优化?

参考答案

启动慢:每次 convert-to 都会冷启动整个 LibreOffice 运行时,加载字体和配置,耗时 1-3 秒。
优化方案 – Unoconv / Unoserver: 使用 unoserver 启动一个常驻后台的 LibreOffice 监听服务(Listener)。 Python 转换时,通过 Socket 与这个常驻进程通信,复用进程,避免冷启动。这能将简单文档的转换时间从 3s 缩短到 0.5s。

结束语:恭喜你!至此,我们已经完成了从后端架构、数据库设计、文件处理到 AI 能力集成的全栈技术探索。希望这套 Python + FastAPI + uni-app 的实战架构能成为你构建下一个 AI 产品的坚实基石。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 40_文档格式转换

评论 抢沙发

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