编程学习网 > 编程语言 > Python > Python 压缩 PDF,别只盯着“压缩成功”!
2026
08-06

Python 压缩 PDF,别只盯着“压缩成功”!


一个扫描版合同,原文件 38MB,邮件附件限制 20MB

我第一次拿 Python 处理时,脚本运行正常,输出文件也生成了。再看文件大小:37.6MB

这类结果最烦。代码没报错,PDF 也能打开,但基本等于没压。

PDF 不是普通文本文件,里面可能同时塞着文字、字体、矢量图、JPEG 图片和扫描页。单纯重新保存一遍,通常只能清掉少量冗余对象。真正占空间的,十有八九还是图片。

我现在处理这类需求,一般让 Python 负责参数校验、批量执行和结果统计,真正的重新采样交给 Ghostscript。这个组合不算花哨,但稳定。

先安装 Ghostscript

Linux 可以直接执行:

sudo apt install ghostscript

macOS

brew install ghostscript

Windows 安装后,确认命令行能找到 gswin64c.exe。这一步别跳,很多所谓的 Python 压缩失败,最后只是系统环境变量没配。

先写一个能实际放进脚本目录里用的版本:

from __future__ import annotations
import shutil
import subprocess
from pathlib import Path

def find_ghostscript() -> str:
    candidates = ("gs", "gswin64c", "gswin32c")
    for command in candidates:
        executable = shutil.which(command)
        if executable:
            return executable
    raise RuntimeError(
        "未找到 Ghostscript,请先安装并确认 gs 命令已加入环境变量"
    )

def readable_size(byte_count: int) -> str:
    value = float(byte_count)
    for unit in ("B", "KB", "MB", "GB"):
        if value < 1024 or unit == "GB":
            return f"{value:.2f}{unit}"
        value /= 1024
    return f"{byte_count}B"

def compress_pdf(
    source_file: str | Path,
    target_file: str | Path,
    quality: str = "ebook",
) -> None:
    source = Path(source_file).expanduser().resolve()
    target = Path(target_file).expanduser().resolve()
    if not source.is_file():
        raise FileNotFoundError(f"输入文件不存在:{source}")
    if source.suffix.lower() != ".pdf":
        raise ValueError(f"只处理 PDF 文件:{source.name}")
    if source == target:
        raise ValueError("输出文件不能覆盖原文件,先保留一份退路")
    presets = {
        "screen": "/screen",
        "ebook": "/ebook",
        "printer": "/printer",
        "prepress": "/prepress",
    }
    if quality not in presets:
        raise ValueError(f"不支持的压缩等级:{quality}")
    target.parent.mkdir(parents=True, exist_ok=True)
    gs = find_ghostscript()
    command = [
        gs,
        "-sDEVICE=pdfwrite",
        "-dCompatibilityLevel=1.4",
        f"-dPDFSETTINGS={presets[quality]}",
        "-dNOPAUSE",
        "-dQUIET",
        "-dBATCH",
        "-dDetectDuplicateImages=true",
        "-dCompressFonts=true",
        f"-sOutputFile={target}",
        str(source),
    ]
    result = subprocess.run(
        command,
        capture_output=True,
        text=True,
        timeout=300,
    )
    if result.returncode != 0:
        target.unlink(missing_ok=True)
        error = result.stderr.strip() or "Ghostscript 未返回错误详情"
        raise RuntimeError(f"PDF 压缩失败:{error}")
    if not target.is_file() or target.stat().st_size == 0:
        raise RuntimeError("命令执行结束,但输出文件为空")
    before = source.stat().st_size
    after = target.stat().st_size
    reduced = (1 - after / before) * 100
    print(f"原文件:{readable_size(before)}")
    print(f"新文件:{readable_size(after)}")
    print(f"缩小比例:{reduced:.1f}%")
    if after >= before:
        print("压缩后没有变小,这个 PDF 可能已经做过图片压缩")

if __name__ == "__main__":
    compress_pdf(
        source_file="合同扫描件.pdf",
        target_file="合同扫描件_压缩版.pdf",
        quality="ebook",
    )

这里我默认用 ebook,不是因为它名字好听,而是它在文件大小和可读性之间相对稳妥。

几个等级大概这样选:

screen    文件最小,图片损失明显,适合临时预览
ebook     日常发送、上传系统,通常先试这个
printer   更偏向打印质量,压缩幅度会小一些
prepress  保留质量优先,不适合单纯追求小文件

我不建议一上来就用 screen。身份证、合同小字、发票编号这些内容,一旦压糊了,文件再小也没用。压缩不是只看最后那行“缩小 80%”,至少要抽查首页、印章页和文字最密集的页面。

批量处理也不用重新写一套逻辑:

from pathlib import Path
source_dir = Path("待压缩")
target_dir = Path("压缩结果")
for pdf_file in source_dir.glob("*.pdf"):
    output_file = target_dir / f"{pdf_file.stem}_small.pdf"
    try:
        compress_pdf(pdf_file, output_file, quality="ebook")
    except Exception as exc:
        print(f"[失败{pdf_file.name}{exc}")
    else:
        print(f"[完成{pdf_file.name}")

实际跑批量任务时,我还会盯两个地方。

一个是超时。异常 PDF、超大扫描件或者损坏文件,可能长时间卡住,所以代码里加了 timeout=300。五分钟还没处理完,先停下来查文件,别让一个坏文件堵住整个任务。

另一个是压缩后的体积。文字型 PDF 本身就不大,压缩比例可能很低;扫描版 PDF 每页都是大图,通常才有明显空间。要是输出文件反而更大,不要急着继续调参数,很可能原文件已经优化过,再编码一次只是在折腾。

还有个坑容易漏:加密 PDF

碰到设置了权限或密码的文件,Ghostscript 可能直接失败。脚本里不要把异常吞掉,更不要生成一个空文件还当成功。保留错误日志和原文件,比强行输出重要。

PDF 压缩这件事,Python 代码并不难。难的是别把“文件生成了”当成任务完成。

我一般只认三个结果:文件能打开,关键页面看得清,体积确实变小。缺一个,这次压缩都不算完。

以上就是“Python 压缩 PDF,别只盯着“压缩成功”!的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。  

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取