乐于分享
好东西不私藏

28_PDF压缩优化

28_PDF压缩优化

摘要:在实际业务中,用户上传的 PDF 文件往往体积庞大,包含未压缩的高清图片、冗余的字体数据以及无用的元数据。这不仅浪费了宝贵的存储空间(对象存储费用),还显著增加了网络传输延迟(CDN 费用和用户等待时间)。

本章将深入探讨 PDF 文件的 “瘦身” 技术。我们将分析 PDF 文件大小的构成,利用 pikepdf (QPDF) 进行无损压缩,使用 ghostscript 进行有损图像重采样,并结合字体子集化技术,构建一套智能的 PDF 压缩服务。

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

28.1 PDF 文件大小分析

28.1.1 为什么 PDF 这么大?

PDF 是一个容器格式,其体积主要由以下几部分构成:

1.图片资源 (Images):通常占比 80% 以上。很多扫描件直接嵌入了未压缩的 TIFF 或高质量 JPEG。
2.字体 (Fonts):嵌入了完整的字体文件(如 10MB 的中文字体库),而实际只用了几百个字。
3.冗余对象 (Dead Objects):每次修改 PDF(增量更新)都会在文件末尾追加新数据,旧数据依然存在但不可见。
4.元数据 (Metadata):缩略图、XML 结构数据等。

28.1.2 使用 pikepdf 分析

pikepdf 是基于 C++ 库 QPDF 的 Python 封装,速度极快且功能强大。

import pikepdf
import os

def analyze_pdf_structure(pdf_path: str):
    """
    简单分析 PDF 内部对象数量
    """
    with pikepdf.Pdf.open(pdf_path) as pdf:
        num_pages = len(pdf.pages)
        # 获取 PDF 版本
        version = pdf.pdf_version

        # 统计图片数量 (简化版,仅统计页面资源)
        image_count = 0
        for page in pdf.pages:
            if "/XObject" in page.Resources:
                xobjects = page.Resources["/XObject"]
                for xobj in xobjects.values():
                    if xobj.get("/Subtype") == "/Image":
                        image_count += 1

        file_size = os.path.getsize(pdf_path) / 1024 / 1024 # MB

        print(f"File: {pdf_path}")
        print(f"Size: {file_size:.2f} MB")
        print(f"Pages: {num_pages}")
        print(f"Images detected: {image_count}")

28.2 图片压缩策略

图片是压缩的核心。我们需要将高分辨率(300+ DPI)图片下采样到适合屏幕阅读的分辨率(如 150 DPI 或 72 DPI),并调整 JPEG 质量。

28.2.1 使用 Ghostscript (推荐)

Ghostscript 是处理 PDF 的瑞士军刀,虽然它是命令行工具,但效果远超纯 Python 库。

安装依赖

RUN apt-get update && apt-get install -y ghostscript

Python 封装

import subprocess
import shutil

def compress_pdf_ghostscript(
    input_path: str, 
    output_path: str, 
    quality: str = "ebook"
) -> bool:
    """
    调用 Ghostscript 压缩 PDF
    :param quality: screen (72dpi), ebook (150dpi), printer (300dpi), prepress (300dpi, color preserve)
    """
    if not shutil.which("gs"):
        raise RuntimeError("Ghostscript (gs) command not found.")

    gs_quality_map = {
        "screen": "/screen",
        "ebook": "/ebook",
        "printer": "/printer",
        "prepress": "/prepress",
        "default": "/default"
    }

    gs_setting = gs_quality_map.get(quality, "/default")

    cmd = [
        "gs",
        "-sDEVICE=pdfwrite",
        "-dCompatibilityLevel=1.4",
        f"-dPDFSETTINGS={gs_setting}",
        "-dNOPAUSE",
        "-dQUIET",
        "-dBATCH",
        f"-sOutputFile={output_path}",
        input_path
    ]

    try:
        subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        return True
    except subprocess.CalledProcessError as e:
        print(f"Ghostscript error: {e.stderr.decode()}")
        return False

28.2.2 纯 Python 方案 (pikepdf)

如果无法安装 Ghostscript,可以使用 pikepdf 遍历图像对象并重新编码。

from PIL import Image
from io import BytesIO

def compress_images_pikepdf(input_path: str, output_path: str, quality: int = 50):
    with pikepdf.Pdf.open(input_path) as pdf:
        for page in pdf.pages:
            # 递归查找图片对象并替换(逻辑较复杂,需处理各种色彩空间)
            # 这里仅展示思路:获取 Image XObject -> PIL -> Resize/Compress -> Replace
            pass

        # pikepdf save 时会自动移除未引用对象
        pdf.save(output_path)

工程建议:生产环境首选 Ghostscript,它的图像重采样算法非常成熟,且不容易破坏 PDF 结构。

28.3 字体子集化

28.3.1 什么是字体子集化 (Subsetting)?

如果一个 PDF 嵌入了 “微软雅黑” 字体文件(15MB),但文档中只用了 “你好世界” 4 个字。子集化技术会生成一个只包含这 4 个字字形的新字体文件(可能只有 10KB),从而大幅减小体积。

28.3.2 实现方式

Ghostscript 在处理 PDF 时(dPDFSETTINGS 模式),会自动尝试进行字体子集化。

如果你使用 pikepdfpypdf,它们通常保留原样,不做字体优化。这是选择 Ghostscript 的另一个重要原因。

28.4 元数据清理

隐私保护与体积优化同样重要。我们需要清除作者、编辑历史、缩略图等。

28.4.1 使用 pikepdf 清理

pikepdf 在保存时默认会重构对象树(Linearization / Fast Web View),这会自动剔除很多垃圾数据。

def optimize_pdf_structure(input_path: str, output_path: str):
    with pikepdf.Pdf.open(input_path) as pdf:
        # 1. 移除元数据
        with pdf.open_metadata() as meta:
            # 清空所有标准元数据项
            for key in list(meta.keys()):
                del meta[key]

        # 2. 保存并优化
        # linearize=True: 开启 "快速 Web 查看" (Fast Web View)
        # object_stream_mode=pikepdf.ObjectStreamMode.generate: 压缩对象流
        pdf.save(
            output_path, 
            linearize=True, 
            object_stream_mode=pikepdf.ObjectStreamMode.generate
        )

Fast Web View (Linearization): 这是一种特殊的 PDF 结构,允许浏览器在下载完 PDF 的前几 KB 后就开始显示第一页,而不需要等待整个文件下载完成。这对于大文件在 Web 端的体验至关重要。

28.5 压缩率控制

用户对压缩的需求是多样的:有的用户需要极致小的体积(用于邮件附件),有的用户需要保持高清晰度(用于打印)。我们需要提供分级选项。

28.5.1 分级配置

我们定义三种压缩模式:

1.High (轻度压缩):DPI 300, JPEG 质量 85。用于打印。
2.Medium (标准压缩):DPI 150, JPEG 质量 75。用于屏幕阅读 (推荐默认)。
3.Low (强力压缩):DPI 72, JPEG 质量 50。用于网络传输。

28.5.2 智能回退策略

有时候,压缩后的文件反而比源文件大了(例如源文件已经是极致压缩的黑白图,转为彩色 JPEG 后变大)。

import os

def smart_compress(input_path: str, output_path: str, mode: str = "ebook"):
    # 1. 执行压缩
    success = compress_pdf_ghostscript(input_path, output_path, quality=mode)

    if not success:
        raise Exception("压缩过程失败")

    # 2. 检查结果
    src_size = os.path.getsize(input_path)
    dst_size = os.path.getsize(output_path)

    # 3. 如果压缩后反而变大了,或者是 0 字节,则直接返回原文件(拷贝)
    if dst_size == 0 or dst_size >= src_size:
        print(f"压缩无效 (Src: {src_size} -> Dst: {dst_size}),保留原文件")
        shutil.copy(input_path, output_path)

    return output_path

本章面试题与知识点总结

1. 为什么 Ghostscript 是 PDF 压缩的首选工具?

参考答案

全能性:Ghostscript 不仅能处理图片下采样(Downsampling)和重编码(Re-encoding),还能处理字体子集化(Subsetting)和去除非标对象。纯 Python 库(如 pypdf)通常只能处理对象结构,难以处理图像像素数据和字体字形数据。
鲁棒性:Ghostscript 经过了几十年的工业验证,对各种损坏或不规范的 PDF 兼容性极好。

2. 什么是 PDF 的 “Fast Web View” (Linearization)?有什么作用?

参考答案

定义:这是一种优化过的 PDF 文件组织结构。标准的 PDF 将“目录”(XREF 表)放在文件末尾,浏览器必须下载完整个文件才能解析目录并渲染。线性化 PDF 将目录和第一页的数据移到了文件头部。
作用:允许浏览器(或 PDF 阅读器)进行流式渲染。用户打开 100MB 的 PDF 时,只要下载了前几十 KB,就能立即看到第一页,并在后台继续下载剩余页面。显著提升用户体验。

3. 如何在 Python 中判断 PDF 文件是否已压缩?

参考答案: 很难准确判断,因为“压缩”是一个相对概念。 但可以通过以下启发式方法检查:

1.图片过滤器:检查 /XObject 中的图片是否使用了 /DCTDecode (JPEG) 或 /JPXDecode (JPEG2000) 压缩。
2.对象流:检查是否使用了 /ObjStm (Object Streams),这是 PDF 1.5 引入的结构压缩技术。
3.统计法:计算 文件大小 / 页数,如果单页小于 50KB 且包含图片,通常说明已经是压缩过的。

下篇预告:掌握了 PDF 的压缩优化后,我们将进入 第 29 章:PDF 水印与加密。将探讨如何在不破坏原有内容的前提下,为 PDF 添加文字/图片水印,并设置高强度的安全密码。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 28_PDF压缩优化

评论 抢沙发

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