Python压缩PDF文件体积:垃圾回收+图片降采样
用Python的PyMuPDF压缩PDF文件体积,自动清理冗余对象、压缩嵌入图片、去重字体,几十MB的扫描件能压到几MB,适合邮件附件和归档。
场景痛点
扫描件、合同、产品手册PDF动辄几十MB,邮件附件超过50MB发不出去,公司网盘上传也慢。手动用Acrobat"另存为优化PDF",一个文件点几下,几十批文件根本顾不过来。用Python的PyMuPDF做垃圾回收、图片降采样、字体子集化,一行代码批量瘦身。
用到的库
pip install pymupdf pillow
完整代码
# -*- coding: utf-8 -*-
"""
压缩 PDF 文件体积:
1) 清理未引用的冗余对象
2) 对大图做降采样 + JPEG 重压缩
3) 字体子集化
"""
import fitz # PyMuPDF
import io
from PIL import Image
from pathlib import Path
def compress_pdf(input_pdf: str,
output_pdf: str,
image_dpi: int = 120,
jpeg_quality: int = 60) -> float:
"""
返回压缩后文件大小(MB),方便批量时统计效果。
"""
doc = fitz.open(input_pdf)
for page in doc:
# 遍历页面上每张图片,压缩过大的
for img_info in page.get_images(full=True):
xref = img_info[0]
try:
base_image = doc.extract_image(xref)
img_bytes = base_image["image"]
ext = base_image["ext"]
# 已经是 JPEG 且不大就跳过
im = Image.open(io.BytesIO(img_bytes))
original_size = len(img_bytes)
# 降采样:大图缩到 image_dpi 对应的像素尺寸
# A4 在 120 DPI 下约 1000×1400,超过就压
max_side = max(im.size)
if max_side > 1500:
ratio = 1500 / max_side
new_size = (int(im.size[0] * ratio), int(im.size[1] * ratio))
im = im.convert("RGB").resize(new_size, Image.LANCZOS)
# 重新存成 JPEG
buf = io.BytesIO()
im.save(buf, format="JPEG", quality=jpeg_quality, optimize=True)
new_bytes = buf.getvalue()
# 只在变小了才替换
if len(new_bytes) < original_size:
doc.update_stream(xref, new_bytes)
except Exception as e:
# 单张图失败不影响整体
continue
# garbage=4 清理所有未引用对象,deflate=True 压缩流,deflate_images=True 再压一次图片
doc.save(
output_pdf,
garbage=4,
deflate=True,
deflate_images=True,
deflate_fonts=True,
clean=True,
)
doc.close()
size_mb = Path(output_pdf).stat().st_size / 1024 / 1024
return size_mb
def batch_compress(input_dir: str, output_dir: str, image_dpi: int = 120):
src, dst = Path(input_dir), Path(output_dir)
dst.mkdir(parents=True, exist_ok=True)
for pdf_file in src.glob("*.pdf"):
out = dst / pdf_file.name
before = pdf_file.stat().st_size / 1024 / 1024
after = compress_pdf(str(pdf_file), str(out), image_dpi=image_dpi)
print(f"{pdf_file.name}: {before:.2f} MB -> {after:.2f} MB")
if __name__ == "__main__":
batch_compress(
input_dir="originals",
output_dir="compressed",
image_dpi=120,
)
代码讲解
page.get_images(full=True)拿到当前页所有嵌入图片的引用,xref是图片在PDF对象表里的编号。doc.extract_image(xref)把图片原始字节取出来,用Pillow打开看尺寸;长边超过1500像素就等比缩小,重新存成JPEG。doc.update_stream(xref, new_bytes)用新的图片字节替换原有的,这一步是体积下降的主要来源。- 保存时的参数组合是关键:
garbage=4彻底清理未引用对象、deflate=True压缩内容流、deflate_images=True对图片流再压一次、clean=True清理冗余结构。 jpeg_quality=60是折中值,肉眼看差别不大但体积小一半;追求更小压到40,追求清晰用80。
运行结果
把大PDF放进originals/,跑完在compressed/得到同名文件。每个文件打印压缩前后大小,常见扫描件能从20MB压到2~5MB。打开PDF检查图片清晰度是否可接受。
注意事项
- 文字型PDF(不是扫描件)体积主要来自嵌入字体,
deflate_fonts=True会自动处理,效果一般就够。 - 扫描件压缩主要靠图片降采样,
image_dpi别低于100,否则文字会糊到看不清。 - 已经是压缩过的PDF(比如Acrobat优化过的)再压效果有限,可能反而变大。
- 加密PDF要先
doc.authenticate("密码")再处理,否则保存失败。

更新时间:2026-09-14 21:40:29