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

Comprimir imágenes en Python con Pillow, WebP y mozjpeg

Comprima imágenes JPEG, PNG y WebP en Python usando Pillow, cwebp y mozjpeg. Incluye ajustes de calidad, bucles de tamaño objetivo y un rápido script por lotes (batch).

Comprimir imágenes en Python con Pillow, WebP y mozjpeg

Última actualización: June 27, 2026

Tienes una carpeta de JPEGs tamaño cámara, un paso de compilación que los envía a los usuarios y un presupuesto de peso de página que sigue explotando. Comprimir esos archivos manualmente en un editor de imágenes no escala. Esta guía muestra el exacto Python que necesitas para reducir archivos JPEG, PNG y WebP según un horario, en un trabajo CI o dentro de un manejador de carga (upload handler).

Respuesta rápida: ¿cómo comprimes imágenes en Python?

Abre la imagen con Pillow y luego guárdala con las banderas de formato correctas. Para fotos, guárdalas como JPEG con quality=80, optimize=True y progressive=True. Para los archivos modernos más pequeños, guárdalos como WebP con quality=80, method=6. Para gráficos planos y capturas de pantalla, mantén PNG y pasa 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)

Esa función de cuatro líneas ya reduce la mayoría de los JPEGs de teléfonos y DSLR entre un 40 y un 70 por ciento. El resto de esta guía añade objetivos de tamaño, WebP, mozjpeg y un ejecutor por lotes (batch runner) para que puedas apuntarlo a un directorio y olvidarte del problema.

¿Qué necesitas antes de empezar?

Necesitas Python 3.9 o más reciente y Pillow, el fork mantenido de PIL. Pillow maneja JPEG, PNG y WebP directamente en todas las plataformas comunes.

pip install Pillow

Para la salida más pequeña también querrás dos codificadores de línea de comandos que Python pueda llamar: cwebp de libwebp y cjpeg de mozjpeg.

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

Verifica para qué sirve cada pieza antes de escribir código:

  • Pillow lee y escribe todos los formatos y realiza el redimensionamiento.
  • cwebp produce los archivos WebP más ajustados y expone más ajustes que Pillow.
  • mozjpeg re-codifica JPEGs más pequeños que el estándar libjpeg que usa Pillow.
  • Un ayudante de informes para que cada ejecución imprima cuántos bytes has ahorrado.

Aquí está el ayudante utilizado a lo largo de esta guía:

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}% más pequeño)")

Si solo necesitas una compresión puntual y no quieres instalar nada, el compresor de imágenes basado en navegador hace el mismo trabajo para un puñado de archivos.

¿Cómo comprimir un JPEG con Pillow?

Las tres banderas que importan para JPEG son quality, optimize y progressive. quality controla cuántos detalles descarta el codificador. optimize=True ejecuta una segunda pasada para construir una tabla Huffman más pequeña. progressive=True reordena el archivo para que pinte de arriba abajo a baja resolución primero, lo cual se siente más rápido en conexiones lentas y generalmente reduce un poco más el archivo.

Laptop on a wooden desk showing source code open in an editor

Elige un valor de calidad basado en dónde se usa la imagen, no un único número global:

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

Hay un detalle que confunde a casi todo el mundo: JPEG no tiene canal alfa. Si abres una captura de pantalla PNG con transparencia y la guardas directamente en JPEG, Pillow lanza cannot write mode RGBA as JPEG. La llamada convert("RGB") en la función anterior primero aplana la transparencia sobre un fondo sólido, razón por la cual se ejecuta antes de cada guardado de JPEG.

Comprimir PNG y convertir a WebP

PNG es sin pérdida (lossless), por lo que no puedes intercambiar calidad por tamaño como lo haces con JPEG. Lo que sí puedes hacer es ejecutar el optimizador de Pillow y, para cualquier cosa que sea realmente una foto, cambiar completamente el formato.

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 le dice al codificador WebP que dedique más tiempo a buscar un archivo más pequeño. Es más lento por imagen, pero vale la pena en un paso de compilación. Para una foto que comenzó como un PNG de 2 MB, WebP con calidad 80 suele quedar por debajo de los 200 KB mientras se ve idéntica a tamaño normal de visualización. Las propias mediciones de Google sitúan a WebP aproximadamente entre un 25 y un 35 por ciento más pequeño que JPEG comparable, según la WebP documentation.

Usa esta tabla para elegir un formato en lugar de adivinar:

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

Si todavía estás decidiendo entre formatos para un sitio completo, el desglose en WebP vs JPEG vs PNG cubre el soporte del navegador y las compensaciones con más profundidad.

Alcanzar un tamaño de archivo objetivo en un bucle

"Hacerlo por debajo de 200 KB" es un requisito común de herramientas de correo electrónico, marketplaces y límites de carga CMS. No puedes elegir un número de calidad que alcance un objetivo de bytes al primer intento, porque la misma calidad produce tamaños muy diferentes para una foto con mucho detalle versus una plana. Baja en bucle desde alta calidad hasta que caigas por debajo del límite:

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

El argumento floor detiene el bucle antes de que la calidad caiga en territorio borroso (smear territory). Si una imagen no puede alcanzar el objetivo por encima del límite, la función aún devuelve un valor para que puedas registrarlo y redimensionar la imagen en lugar de comprimir más. Para presupuestos fijos de bytes como 100 KB o 50 KB, funciona el mismo bucle; el recorrido en compress images without losing quality explica cuándo redimensionar supera bajar la calidad.

¿Cómo reducir JPEGs con mozjpeg y cwebp?

Pillow guarda los JPEGs con el codificador estándar libjpeg. mozjpeg es un codificador de drop-in de Mozilla que produce archivos más pequeños con la misma calidad visual, generalmente entre un 5 y un 15 por ciento menos. Debido a que Pillow no lo incluye, llama al binario cjpeg a través de subprocess. cjpeg de mozjpeg lee PPM de manera confiable, así que primero aplana la imagen con 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")

Para WebP, llamar a cwebp directamente te da controles que Pillow no expone, como -m 6 para máximo esfuerzo y -sharp_yuv para bordes de color más limpios:

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

Así es como se comparan las opciones para que puedas decidir qué instalar:

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

El proyecto mozjpeg documenta su codificador y sus pruebas en el mozjpeg repository, y la lista completa de opciones de guardado de Pillow se encuentra en la Pillow image file formats reference.

¿Cómo comprimir una carpeta por lotes rápidamente?

Una sola imagen es fácil. La tarea real es un directorio con cientos. Redimensiona cualquier cosa más ancha que lo que necesita tu diseño, luego comprime y ejecuta el trabajo a través de hilos (threads) para que una carpeta de 500 imágenes termine en segundos en lugar de minutos.

Developer at a multi-monitor workstation running code while wearing headphones

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)

Algunas notas prácticas de ejecutar esto en producción:

  1. Redimensiona antes de comprimir. Servir una foto de 6000 px a una columna de 1200 px desperdicia bytes que ninguna configuración de calidad puede recuperar.
  2. Pillow libera el GIL durante la codificación, por lo que los hilos dan un aumento real de velocidad aquí; no necesitas multiprocessing para la mayoría de las carpetas.
  3. Escribe en un directorio de salida separado para que una ejecución fallida nunca sobrescriba tus originales.
  4. Usa Image.LANCZOS para reducir la escala (downscaling); mantiene los bordes más nítidos que el filtro predeterminado.
  5. Registra cada archivo con el ayudante report y conserva los originales hasta que hayas revisado una muestra.

Si tu trabajo es recurrente en lugar de puntual, los patrones en batch image processing cubren colas (queues), reintentos (retries) y convenciones de nombres. Para una ejecución sin código sobre un zip de archivos, el batch tool y el image converter manejan los cambios de formato sin necesidad de un script.

¿Qué enfoque deberías elegir?

Haz coincidir el método con el trabajo en lugar de buscar siempre la herramienta más potente:

  • Fotos web en un paso de compilación: Pillow WebP con calidad 80, o cwebp con -m 6 cuando quieres el último porcentaje.
  • Un presupuesto estricto de bytes: el bucle compress_to_target, que recurre a un redimensionamiento cuando se alcanza el límite (floor).
  • Pipelines JPEG heredados que no puedes cambiar: re-codifica la salida con mozjpeg para ahorros gratuitos.
  • Capturas de pantalla y diagramas: mantén PNG con optimize=True, o WebP sin pérdida si tus navegadores lo soportan.

Para la teoría detrás de por qué funcionan estas configuraciones, la image compression guide explica lossy versus lossless y cómo las tablas de cuantización afectan el resultado.

Errores y soluciones comunes

Un puñado de excepciones causan la mayoría de los fallos:

  • cannot write mode RGBA as JPEG — llama a img.convert("RGB") antes de guardar en JPEG.
  • Image.DecompressionBombError — Pillow protege contra imágenes enormes; eleva el límite solo para archivos que confías con Image.MAX_IMAGE_PIXELS = 200_000_000.
  • OSError: broken data stream — el archivo fuente está truncado; establece ImageFile.LOAD_TRUNCATED_IMAGES = True para salvarlo, y luego vuelve a descargarlo cuando sea posible.
  • Salida más grande que la entrada — volviste a guardar un JPEG ya comprimido; comprime desde el original, no desde una exportación anterior.
  • Los colores se ven lavados — se guardó una fuente CMYK sin conversión; llama a convert("RGB") primero.
from PIL import Image, ImageFile
Image.MAX_IMAGE_PIXELS = 200_000_000
ImageFile.LOAD_TRUNCATED_IMAGES = True

Detailed close-up image of a python resting on perforated surface.

Conclusión clave

Pillow maneja el 90 por ciento de la compresión de imágenes en Python con tres banderas JPEG y un guardado WebP. Acude a mozjpeg y cwebp cuando necesites los archivos más pequeños posibles, envuelve todo en el bucle de tamaño objetivo cuando un presupuesto de bytes es fijo, y pasa una carpeta por la función de lote (batch) con hilos cuando el volumen crece. Conserva tus originales, redimensiona antes de comprimir e imprime el ahorro en cada ejecución para que siempre sepas lo que está haciendo realmente el pipeline.

Usa las herramientas gratuitas mientras sigues la guía.

Imagen de portada de PNG a WebP: Cómo convertir y reducir imágenes PNG

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

PNG a WebP: Cómo convertir y reducir imágenes PNG

Convierte tus imágenes PNG a WebP para reducir el tamaño de los archivos web. Analizamos cuándo usar WebP sin pérdidas o con pérdida, tamaños reales, y comandos cwebp/Pillow con respaldo PNG.