Fri Jun 26 2026 20:00:00 GMT-0400 (北美东部夏令时间)

使用 Pillow、WebP 和 mozjpeg 在 Python 中高效压缩图像

本教程展示如何使用 Pillow、cwebp 和 mozjpeg 在 Python 环境中批量压缩 JPEG、PNG 和 WebP 格式的图像。它不仅支持精细的画质设置,还实现了目标尺寸循环处理功能,并提供了一套快速可靠的批量脚本解决方案。

使用 Pillow、WebP 和 mozjpeg 在 Python 中高效压缩图像

上次更新: June 27, 2026

你有一个包含相机尺寸 JPEG 的文件夹,一个用于将它们发送给用户的构建步骤,以及一个不断超标的页面权重预算。手动在图像编辑器中压缩这些文件是无法规模化的。本指南展示了你需要使用的确切 Python 代码,可以在定时任务、CI job 或上传处理程序中缩小 JPEG、PNG 和 WebP 文件。

快速答案:如何在 Python 中压缩图像?

使用 Pillow 打开图像,然后使用正确的格式标志保存即可。对于照片,请使用 quality=80optimize=Trueprogressive=True 保存为 JPEG。对于最小的现代文件,请使用 quality=80, method=6 保存为 WebP。对于平面图形和截图,保留 PNG 并传递 optimize=True

from PIL import Image

def compress_jpeg(src, dst, quality=80):
    img = Image.open(src)
    if img.mode in ("RGBA", "P"):
        img = img.convert("RGB")
    img.save(dst, "JPEG", quality=quality, optimize=True, progressive=True)

这个四行函数已经能将大多数手机和 DSLR JPEG 文件缩小 40% 到 70%。本指南的其余部分添加了尺寸目标、WebP、mozjpeg 和批处理运行器,这样你就可以将其指向一个目录然后走开了。

开始前需要准备什么?

你需要 Python 3.9 或更高版本以及 Pillow,它是 PIL 的维护分支。Pillow 原生支持所有主流平台上的 JPEG、PNG 和 WebP。

pip install Pillow

为了获得最小的输出文件,你还需要两个 Python 可以调用的命令行编码器:来自 libwebp 的 cwebp 和来自 mozjpeg 的 cjpeg

brew install webp mozjpeg   # macOS; Linux: apt install webp, build mozjpeg

在编写代码之前,请了解每个组件的作用:

  • Pillow 读取和写入所有格式并执行尺寸调整。
  • cwebp 生成最紧凑的 WebP 文件,并提供了比 Pillow 更多的调优选项。
  • mozjpeg 重新编码 JPEG,其文件比 Pillow 使用的标准 libjpeg 更小。
  • 一个报告辅助工具,以便每次运行都能打印出节省了多少字节。

这是本指南中使用的辅助工具:

import os

def report(src, dst):
    before, after = os.path.getsize(src), os.path.getsize(dst)
    saved = (1 - after / before) * 100
    print(f"{before/1024:.0f} KB -> {after/1024:.0f} KB ({saved:.0f}% smaller)")

如果你只需要一次性压缩,并且不想安装任何东西,基于浏览器的 image compressor 可以为少量文件完成相同的任务。

如何使用 Pillow 压缩 JPEG?

对于 JPEG 最重要的三个标志是 qualityoptimizeprogressivequality 控制编码器丢弃的细节程度。optimize=True 会运行第二次传递来构建更小的 Huffman 表。progressive=True 会重新排列文件,使其首先以低分辨率从上到下绘制,这在连接缓慢时感觉更快,并且通常能使文件进一步缩小。

在木桌上展示源代码的笔记本电脑

根据图像的使用位置来选择一个质量值,而不是使用单一的全局数字:

Quality value Typical use What to expect
90-95 Hero images, print proofs Near-original, noticeably larger files
75-85 Most web photos The safe default, large savings
60-70 Thumbnails and previews Visible softening on fine detail
Below 50 Avoid for photos Blocky artifacts around edges

有一个陷阱几乎会困住所有人:JPEG 没有 alpha 通道。如果你打开了一个带有透明度的 PNG 截图并直接保存为 JPEG,Pillow 会抛出 cannot write mode RGBA as JPEG 的错误。前面函数中的 convert("RGB") 调用首先将透明度平铺到一个实心背景上,这就是它在每次 JPEG 保存之前运行的原因。

压缩 PNG 并转换为 WebP

PNG 是无损的,所以你不能像使用 JPEG 那样用质量换取尺寸。你能做的是运行 Pillow 的优化器,对于任何本质上是照片的内容,则完全切换格式。

def compress_png(src, dst):
    Image.open(src).save(dst, "PNG", optimize=True, compress_level=9)

def to_webp(src, dst, quality=80):
    img = Image.open(src)
    img.save(dst, "WEBP", quality=quality, method=6)

method=6 告诉 WebP 编码器花费更多时间搜索更小的文件。它在每张图像上的速度较慢,但在构建步骤中是值得的。对于一个最初为 2 MB PNG 的照片,WebP 在质量 80 下通常能稳定保持在 200 KB 以下,同时看起来与正常观看尺寸下完全相同。根据 Google 自己的测量,WebP 比可比较的 JPEG 小约 25% 到 35%,具体请参考 WebP documentation

使用此表选择格式,而不是猜测:

Source content Best format Why
Photograph WebP or JPEG Smooth gradients compress well lossy
Logo, icon, flat UI PNG or WebP lossless Sharp edges stay crisp
Screenshot with text PNG or lossless WebP Avoids fuzzy text from lossy passes
Image needing transparency WebP or PNG JPEG cannot store an alpha channel

如果你仍在为整个网站的格式选择而犹豫,WebP vs JPEG vs PNG 中的详细分析涵盖了浏览器支持和权衡取舍。

在循环中达到目标文件大小

“使其低于 200 KB”是来自电子邮件工具、市场和 CMS 上传限制的常见要求。你不能选择一个在第一次尝试就能达到字节目标的质量数字,因为相同的质量对于繁忙的照片和平面照片会产生截然不同的尺寸。从高质量开始循环,直到达到上限以下:

def compress_to_target(src, dst, target_kb=200, q=90, floor=35):
    img = Image.open(src).convert("RGB")
    while q >= floor:
        img.save(dst, "JPEG", quality=q, optimize=True, progressive=True)
        if os.path.getsize(dst) <= target_kb * 1024:
            return q
        q -= 5
    return q

floor 参数会在质量下降到模糊区域之前停止循环。如果图像无法在高于 floor 的情况下达到目标,函数仍然会返回,这样你就可以记录下来并调整图像尺寸而不是继续压缩。对于像 100 KB 或 50 KB 这样的固定字节预算,相同的循环是有效的;compress images without losing quality 中的操作指南解释了何时调整尺寸比降低质量更有效。

如何使用 mozjpeg 和 cwebp 使 JPEG 更小?

Pillow 使用标准 libjpeg 编码器保存 JPEG。mozjpeg 是来自 Mozilla 的一个即插即用的编码器,它能以相同的视觉质量生成更小的文件,通常可以节省额外的 5% 到 15%。由于 Pillow 没有捆绑它,你需要通过 subprocess 调用 cjpeg 二进制文件。mozjpeg 的 cjpeg 可靠地读取 PPM,所以先用 Pillow 将图像平铺:

import subprocess

def mozjpeg(src, dst, quality=80):
    Image.open(src).convert("RGB").save("_tmp.ppm")
    subprocess.run(["cjpeg", "-quality", str(quality), "-progressive",
                    "-optimize", "-outfile", dst, "_tmp.ppm"], check=True)
    os.remove("_tmp.ppm")

对于 WebP,直接调用 cwebp 可以提供 Pillow 没有暴露的旋钮,例如用于最大努力的 -m 6 和用于更清晰颜色边缘的 -sharp_yuv:

def cwebp(src, dst, quality=82):
    subprocess.run(["cwebp", "-q", str(quality), "-m", "6",
                    "-sharp_yuv", src, "-o", dst], check=True)

以下是选项的比较,帮助你决定安装什么:

Tool Best for Trade-off
Pillow General scripting, every format Not the smallest JPEG
mozjpeg Smallest progressive JPEG Extra binary, PPM round-trip
cwebp (libwebp) Tightest WebP control Separate binary to install
pillow-simd Faster resizing on x86 servers Harder to build and pin

mozjpeg 项目在其 mozjpeg repository 中记录了其编码器和基准测试,而 Pillow 保存选项的完整列表可以在 Pillow image file formats reference 中找到。

如何快速批量压缩一个文件夹?

单个图像很容易。真正的任务是包含数百个图像的目录。调整任何比你的布局需要的尺寸更宽的图像,然后进行压缩,并跨线程运行工作,这样一个 500 张图像的文件夹就能在几秒钟内完成,而不是几分钟。

戴着耳机在多显示器工作站上运行代码的开发人员

from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

def batch(in_dir, out_dir, quality=80, max_width=1920):
    out = Path(out_dir); out.mkdir(parents=True, exist_ok=True)
    files = [p for p in Path(in_dir).iterdir()
             if p.suffix.lower() in {".jpg", ".jpeg", ".png"}]
    def work(p):
        img = Image.open(p)
        if img.width > max_width:
            h = round(img.height * max_width / img.width)
            img = img.resize((max_width, h), Image.LANCZOS)
        img.convert("RGB").save(out / f"{p.stem}.jpg", "JPEG",
                                quality=quality, optimize=True, progressive=True)
    with ThreadPoolExecutor() as pool:
        list(pool.map(work, files))
    return len(files)

从生产运行中获得的几个实用注意事项:

  1. 在压缩之前调整尺寸。向 1200 px 列提供一张 6000 px 的照片,浪费的字节是任何质量设置都无法恢复的。
  2. Pillow 在编码过程中会释放 GIL,因此这里的线程确实能提高速度;对于大多数文件夹来说,你不需要多进程。
  3. 写入一个单独的输出目录,这样一次失败的运行就不会覆盖你的原始文件。
  4. 使用 Image.LANCZOS 进行降采样;它比默认滤波器更好地保持了边缘锐利度。
  5. 使用 report 辅助工具记录每个文件,并在你仔细检查样本之前保留原始文件。

如果你的任务是周期性的而不是一次性的,batch image processing 中的模式涵盖了队列、重试和命名约定。对于无需代码运行的 zip 文件批处理,batch toolimage converter 可以处理格式更改而不需要脚本。

应该选择哪种方法?

将方法与任务匹配,而不是总是使用最重的工具:

  • 构建步骤中的 Web 照片: Pillow WebP 在质量 80 下,或者当你需要最后几个百分比时使用 cwebp 和 -m 6
  • 严格的字节预算: compress_to_target 循环,当达到最低限制时回退到调整尺寸。
  • 无法更改的遗留 JPEG 流程: 使用 mozjpeg 重新编码输出以免费节省空间。
  • 截图和图表: 保留 PNG 并使用 optimize=True,或者如果浏览器支持则使用无损 WebP。

关于这些设置如何工作的理论基础,image compression guide 解释了有损与无损以及量化表如何影响结果。

常见错误和修复方法

少数几个异常导致了大多数运行失败:

  • cannot write mode RGBA as JPEG — 在保存为 JPEG 之前调用 img.convert("RGB")
  • Image.DecompressionBombError — Pillow 防止巨大的图像;只有对于你信任的文件,才提高限制:Image.MAX_IMAGE_PIXELS = 200_000_000
  • OSError: broken data stream — 源文件被截断;设置 ImageFile.LOAD_TRUNCATED_IMAGES = True 可以抢救它,然后尽可能重新下载。
  • 输出大于输入 — 你重新保存了一个已压缩的 JPEG;请从原始文件而不是之前的导出文件进行压缩。
  • 颜色看起来褪色了 — 源文件是 CMYK,但没有进行转换;首先调用 convert("RGB")
from PIL import Image, ImageFile
Image.MAX_IMAGE_PIXELS = 200_000_000
ImageFile.LOAD_TRUNCATED_IMAGES = True

在穿孔表面上休息的 Python 的详细特写图像

关键要点总结

Pillow 使用三个 JPEG 标志和一个 WebP 保存功能处理了 Python 中 90% 的图像压缩。当你需要最小可能的文件时,请使用 mozjpeg 和 cwebp;当字节预算固定时,用目标大小循环包裹所有操作;当数据量增大时,通过线程批处理函数运行整个文件夹。保留你的原始文件,在压缩之前调整尺寸,并在每次运行时打印节省的容量,这样你就能始终知道流程实际在做什么。

阅读指南的同时,欢迎使用这些免费工具。

PNG转WebP:如何高效转换与压缩PNG图片 的封面图片

Tue Mar 17 2026 20:00:00 GMT-0400 (北美东部夏令时间)

PNG转WebP:如何高效转换与压缩PNG图片

本文指导您如何将PNG格式转换为WebP,从而显著减小网页文件体积。我们将详细探讨无损WebP和有损模式的适用场景、提供实际测量大小对比,并演示使用cwebp及Pillow命令进行转换,同时保留PNG作为可靠的回退选项。

图片SEO优化:实用的2026年检查清单 的封面图片

Tue Mar 10 2026 20:00:00 GMT-0400 (北美东部夏令时间)

图片SEO优化:实用的2026年检查清单

这是一份针对2026年的实用图片SEO检查清单,涵盖了alt text、文件名、格式、压缩、Core Web Vitals、structured data以及测量等关键要素,帮助您提升图片的搜索引擎可见性和性能。