El módulo zipfile en Python forma parte de la biblioteca estándar y permite crear, leer, inspeccionar y extraer archivos ZIP sin instalar paquetes externos. Es útil para copias de seguridad, exportación de informes, distribución de documentos, procesamiento de cargas y automatizaciones que necesitan reunir muchos archivos en un único contenedor.
Aunque la API es sencilla, un flujo fiable debe controlar los nombres internos, el tamaño total, las entradas duplicadas, la integridad y las rutas de extracción. En esta guía aprenderás a usar ZipFile, elegir modos de apertura, escribir contenido en memoria, leer archivos sin extraer todo, inspeccionar metadatos y aplicar validaciones prácticas. El tema complementa otros recursos de la biblioteca estándar, como graphlib para dependencias, contextvars para contexto seguro, heapq para colas de prioridad y singledispatch para API extensibles.
Abrir un archivo ZIP
La clase principal es zipfile.ZipFile. El modo r abre un archivo existente para lectura, w crea o reemplaza, a agrega contenido y x crea un archivo nuevo y falla si el destino ya existe.
from zipfile import ZipFile
with ZipFile("datos.zip", "r") as archivo:
print(archivo.namelist())Conviene utilizar siempre un administrador de contexto. Los ZIP mantienen un directorio central con los metadatos de sus miembros y un archivo que no se cierra correctamente puede quedar incompleto.
Crear un paquete
Usa write() para añadir un archivo del disco. El argumento arcname define el nombre interno guardado en el ZIP.
from zipfile import ZIP_DEFLATED, ZipFile
with ZipFile("respaldo.zip", "w", compression=ZIP_DEFLATED) as archivo:
archivo.write("informes/ventas.csv", arcname="ventas.csv")
archivo.write("config/app.json", arcname="config/app.json")Elige nombres internos cortos y portables. Evita rutas absolutas y directorios específicos del servidor porque dificultan el traslado del archivo y pueden revelar estructura local.
Escribir contenido en memoria
Cuando los datos ya existen como texto o bytes, writestr() evita crear un archivo temporal.
from zipfile import ZIP_DEFLATED, ZipFile
contenido = "id,nombre\n1,Ana\n2,Caio\n"
with ZipFile("exportacion.zip", "w", ZIP_DEFLATED) as archivo:
archivo.writestr("clientes.csv", contenido)Esta técnica funciona bien para informes pequeños generados bajo demanda. Para salidas grandes, utiliza archivos temporales o procesamiento por bloques para no conservar todo el resultado en memoria.
Leer sin extraer
El método read() devuelve un miembro como bytes. open() devuelve un objeto similar a un archivo y permite procesar el contenido de forma gradual.
from zipfile import ZipFile
with ZipFile("datos.zip") as archivo:
with archivo.open("config.json") as entrada:
texto = entrada.read().decode("utf-8")
print(texto)Esto es útil para enviar el contenido directamente a un analizador de JSON, CSV, XML o texto sin escribir otra copia en el disco.
Inspeccionar miembros
infolist() devuelve objetos ZipInfo con nombre, tamaño original, tamaño comprimido, fecha y método de compresión.
with ZipFile("carga.zip") as archivo:
for item in archivo.infolist():
print(item.filename, item.file_size, item.compress_size)Revisa los metadatos antes de extraer. La aplicación puede rechazar extensiones no permitidas, demasiadas entradas, miembros demasiado grandes o relaciones de compresión poco habituales.
Validar rutas de extracción
El nombre de un miembro debe tratarse como entrada externa. Resuelve el destino final y comprueba que permanezca dentro del directorio autorizado.
from pathlib import Path
from zipfile import ZipFile
raiz = Path("recibidos").resolve()
with ZipFile("carga.zip") as archivo:
for item in archivo.infolist():
destino = (raiz / item.filename).resolve()
if raiz not in destino.parents and destino != raiz:
raise ValueError(f"Ruta inválida: {item.filename}")
archivo.extract(item, raiz)También conviene rechazar nombres absolutos, componentes innecesarios y tipos de archivo que la aplicación no necesite.
Aplicar límites de recursos
Un archivo comprimido pequeño puede expandirse hasta ocupar mucho más espacio. Define un máximo por miembro y otro para el paquete completo.
MAX_ARCHIVO = 50 * 1024 * 1024
MAX_TOTAL = 200 * 1024 * 1024
total = 0
for item in archivo.infolist():
if item.file_size > MAX_ARCHIVO:
raise ValueError("Miembro demasiado grande")
total += item.file_size
if total > MAX_TOTAL:
raise ValueError("El paquete supera el límite total")Cuenta también los bytes realmente copiados. Así la aplicación puede detenerse si los metadatos declarados no coinciden con el flujo procesado.
Comprobar integridad
testzip() lee los miembros y verifica su CRC. Devuelve el primer nombre con problemas o None cuando no encuentra errores.
from zipfile import BadZipFile, ZipFile
try:
with ZipFile("archivo.zip") as archivo:
dañado = archivo.testzip()
if dañado:
raise ValueError(f"Miembro dañado: {dañado}")
except BadZipFile:
print("El archivo no es un ZIP válido")En una API, registra los detalles técnicos internamente y devuelve al cliente un mensaje sencillo.
Elegir la compresión
ZIP_DEFLATED ofrece amplia compatibilidad y funciona bien con texto y datos estructurados. BZIP2 y LZMA pueden reducir más algunos contenidos, pero herramientas antiguas quizá no los soporten. Imágenes JPEG, vídeos y archivos ya comprimidos suelen mejorar poco.
El parámetro compresslevel controla el equilibrio entre velocidad y tamaño. Mide con datos reales antes de aumentar el nivel en tareas frecuentes.
Tratar nombres duplicados
Un ZIP puede contener varias entradas con el mismo nombre. Distintas herramientas pueden elegir entradas diferentes, por lo que conviene normalizar y registrar los nombres antes de extraer.
vistos = set()
for item in archivo.infolist():
nombre = item.filename.replace("\\", "/").casefold()
if nombre in vistos:
raise ValueError(f"Nombre duplicado: {item.filename}")
vistos.add(nombre)Considera diferencias entre mayúsculas y minúsculas, tipos de barra y representaciones Unicode equivalentes.
Extracción controlada
Para archivos externos, inspecciona y copia un miembro cada vez. Esto permite aplicar límites, mostrar progreso y eliminar resultados parciales si ocurre un error.
import shutil
for item in archivo.infolist():
destino = raiz / item.filename
if item.is_dir():
destino.mkdir(parents=True, exist_ok=True)
continue
destino.parent.mkdir(parents=True, exist_ok=True)
with archivo.open(item) as origen, destino.open("wb") as salida:
shutil.copyfileobj(origen, salida, length=1024 * 1024)Archivos con contraseña
La biblioteca estándar puede leer algunos ZIP con cifrado tradicional, pero no es la mejor opción para requisitos modernos de confidencialidad. La información sensible necesita herramientas actuales, una gestión correcta de claves y un modelo de amenazas claro.
Pruebas importantes
Incluye pruebas para archivos vacíos, datos ZIP inválidos, nombres duplicados, directorios, nombres Unicode, miembros grandes, límites totales y rutas no permitidas. Comprueba también que los archivos parciales se eliminen después de una excepción.
Buenas prácticas
- Usa
withen todas las operaciones. - Define nombres internos con
arcname. - Valida la ruta final antes de escribir.
- Limita cantidad de miembros, tamaño individual y tamaño total.
- Rechaza nombres duplicados y extensiones no permitidas.
- Comprueba la integridad cuando sea importante.
- Extrae en un directorio temporal aislado.
- No ejecutes automáticamente archivos extraídos.
- Registra errores y elimina resultados incompletos.
- Prueba paquetes normales y problemáticos.
Conclusión
zipfile en Python cubre los casos más habituales para crear y consumir archivos ZIP. Una implementación de producción debe combinar la API básica con inspección de metadatos, límites de recursos, validación de rutas y extracción controlada.
Al tratar cada miembro como entrada externa y verificar el destino antes de escribir, la aplicación se vuelve más predecible y resistente. Consulta la documentación oficial de zipfile y la guía de OWASP sobre extracción segura.







