🎯 程序主要功能
该程序实现了一个 自动化文档处理流水线,核心目标是:
将指定目录中的 Microsoft Word(.docx)文档批量转换为高质量、小体积的图像型 PDF 文件,适用于归档、分享或嵌入系统等场景。
注意不是word直接转pdf,而是先转图片,再拼凑成pdf,这样可以有效的防止pdf的文字被复制。
✅ 具体功能包括:
-
批量处理
自动扫描
C:\temp目录下所有.docx文件(自动跳过 Word 临时文件如~$xxx.docx)。 -
Word → PDF 转换
利用 Microsoft Word 的 COM 接口,将每个
.docx文档导出为标准 PDF(保留原始排版)。 -
PDF → 高清 JPEG 图像
使用高分辨率(默认 150 DPI)将 PDF 每一页渲染为 JPEG 图片,兼顾清晰度与文件体积。
-
图像 → 压缩 PDF 合并
将所有 JPEG 页面重新组合成一个单一 PDF 文件,并启用:
- zlib 流压缩(
deflate=True) - 图像流压缩(
deflate_images=True) - 冗余对象清理(
garbage=3,clean=True)
- zlib 流压缩(
-
错误容错与重试机制
对于因 Word 卡顿或复杂内容导致失败的文档,自动重试最多 2 次,提升成功率。
-
输出结构清晰
- 图片临时目录:
xxx_pages/ - 最终 PDF:
xxx_combined.pdf - 显示生成文件大小(MB),便于监控
- 图片临时目录:
-
资源安全释放
严格管理 Word 进程、临时文件、内存对象,防止进程残留或磁盘堆积。
⚙️ 技术栈(依赖库)
| 技术 | 用途 | 安装命令 |
|---|---|---|
pywin32 |
调用 Windows 上的 Microsoft Word COM 接口,实现 .docx → PDF |
pip install pywin32 |
PyMuPDF (fitz) |
高性能 PDF 渲染、图像提取、PDF 合并与压缩 | pip install PyMuPDF |
Pillow (PIL) |
将位图数据保存为 JPEG 格式(控制质量与体积) | pip install Pillow |
| Python 标准库 | tempfile(临时文件)、pathlib(路径操作)、os(文件系统)等 |
内置 |
💡 运行环境要求:
- Windows 系统(因依赖 Word COM)
- 已安装 Microsoft Word(2010 或更高版本)
- Python 3.7+
📦 典型应用场景
- 项目文档自动化归档(可以代替出扫描文档再组成pdf)
- 将动态 Word 报表转为不可编辑的 PDF 快照
- 为无 Office 环境提供标准化文档交付格式
- 减少人工操作,提升批量处理效率
📌 总结一句话
本程序利用 Word + PyMuPDF + Pillow 构建了一条稳定、高效、低体积的 Word → 图像型 PDF 自动化转换流水线,专为 Windows 企业文档处理场景设计。
import os
import time
import tempfile
from pathlib import Path
import win32com.client
import fitz
from PIL import Image
TEMP_DIR = r"C:\temp"
DPI = 150
JPEG_QUALITY = 85
MAX_RETRIES = 2 # 失败重试次数
def pixmap_to_image(pix):
return Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
def safe_word_quit(app):
try:
if app:
app.Quit()
except:
pass
def process_docx(word_path, retry=0):
print(f"\n正在处理: {word_path.name} (尝试 {retry + 1}/{MAX_RETRIES + 1})")
safe_name = "".join(c if c.isalnum() or c in "._-" else "_" for c in word_path.stem)
output_folder = Path(TEMP_DIR) / f"{safe_name}_pages"
output_folder.mkdir(exist_ok=True)
temp_pdf = None
word_app = None
doc_obj = None
try:
# 创建临时 PDF
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
temp_pdf = tmp.name
# 启动 Word(关键:加延迟避免冲突)
word_app = win32com.client.Dispatch("Word.Application")
word_app.Visible = False
word_app.DisplayAlerts = False # 禁用弹窗
doc_obj = word_app.Documents.Open(
str(word_path.resolve()),
ReadOnly=True,
PasswordDocument="", # 无密码
AddToRecentFiles=False
)
doc_obj.ExportAsFixedFormat(OutputFileName=temp_pdf, ExportFormat=17)
doc_obj.Close(SaveChanges=False)
doc_obj = None
safe_word_quit(word_app)
word_app = None
# PDF → JPEG
pdf_doc = fitz.open(temp_pdf)
zoom = DPI / 72.0
mat = fitz.Matrix(zoom, zoom)
image_paths = []
for i in range(pdf_doc.page_count):
page = pdf_doc.load_page(i)
pix = page.get_pixmap(matrix=mat, alpha=False)
img = pixmap_to_image(pix)
img_path = output_folder / f"page_{i+1:03d}.jpg"
img.save(img_path, "JPEG", quality=JPEG_QUALITY, optimize=True)
image_paths.append(str(img_path))
print(f" → 已保存: {img_path}")
pdf_doc.close()
# 合并为 PDF
if image_paths:
output_pdf = Path(TEMP_DIR) / f"{safe_name}_combined.pdf"
new_pdf = fitz.open()
for img_path in image_paths:
img_doc = fitz.open(img_path)
w = img_doc[0].get_pixmap().width
h = img_doc[0].get_pixmap().height
img_doc.close()
page = new_pdf.new_page(width=w, height=h)
page.insert_image(fitz.Rect(0, 0, w, h), filename=img_path)
new_pdf.save(str(output_pdf), garbage=3, deflate=True, deflate_images=True, clean=True)
new_pdf.close()
print(f" → 已生成压缩 PDF: {output_pdf}")
print(f" 文件大小: {output_pdf.stat().st_size / (1024*1024):.2f} MB")
# 可选:删除图片文件夹
# import shutil; shutil.rmtree(output_folder)
return True
except Exception as e:
print(f" ❌ 处理失败: {e}")
if retry < MAX_RETRIES:
time.sleep(2) # 等待 2 秒再重试
return process_docx(word_path, retry + 1)
return False
finally:
# 强制清理
if doc_obj:
try:
doc_obj.Close(SaveChanges=False)
except:
pass
safe_word_quit(word_app)
if temp_pdf and os.path.exists(temp_pdf):
try:
os.remove(temp_pdf)
except:
pass
def main():
temp_dir = Path(TEMP_DIR)
if not temp_dir.exists():
print(f"目录 {TEMP_DIR} 不存在!")
return
# 只处理真正的 .docx,排除 ~$ 临时文件
docx_files = [
f for f in temp_dir.glob("*.docx")
if f.is_file() and not f.name.startswith("~$")
]
if not docx_files:
print(f"{TEMP_DIR} 中没有找到有效的 .docx 文件。")
return
print(f"共发现 {len(docx_files)} 个文档待处理。")
success_count = 0
for word_path in docx_files:
if process_docx(word_path):
success_count += 1
print(f"\n✅ 完成!成功: {success_count}/{len(docx_files)}")
if __name__ == "__main__":
main()