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),导致版面错乱(文字溢出文本框)。
解决方案:
/usr/share/fonts/ 目录下,并运行 fc-cache -fv。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 进程,这会瞬间吃光服务器内存。40.5.2 僵尸进程清理
soffice 进程有时候会卡死(Hang)不退出。我们需要在代码中加入超时控制。
try:
subprocess.run(cmd, timeout=30, ...) # 设置 30秒 超时
except subprocess.TimeoutExpired:
# 强杀进程
# 注意:需要精细查找对应的 pid,避免杀错
pass
40.5.3 结果校验
转换成功不代表内容正确。
pypdf 读取生成的 PDF 页数,如果为 0 或远少于预期,标记为异常。本章面试题与知识点总结
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 产品的坚实基石。
夜雨聆风
