Compressão e arquivos ZIP

1. Introdução ao módulo zipfile

O módulo zipfile da biblioteca padrão do Python oferece ferramentas completas para criação, leitura, escrita e extração de arquivos ZIP. Suas classes principais são ZipFile (para manipular o arquivo ZIP em si) e ZipInfo (que armazena metadados de cada membro do arquivo).

Os modos de abertura seguem a mesma lógica de arquivos comuns:
- 'r' — leitura (padrão)
- 'w' — escrita (cria ou sobrescreve)
- 'a' — anexação (adiciona a um ZIP existente)
- 'x' — criação exclusiva (falha se o arquivo já existir)

O uso de context managers (with) é altamente recomendado para garantir o fechamento adequado do arquivo ZIP, mesmo em caso de exceções:

import zipfile

with zipfile.ZipFile('exemplo.zip', 'w') as zf:
    zf.write('dados.txt')
# O arquivo é fechado automaticamente ao sair do bloco

2. Criando arquivos ZIP

Para criar um arquivo ZIP, utilizamos o modo 'w'. O método .write() adiciona arquivos individuais ao ZIP:

import zipfile

with zipfile.ZipFile('meu_arquivo.zip', 'w') as zf:
    zf.write('documento.txt')
    zf.write('imagem.png')
    zf.write('pasta/relatorio.pdf')  # Preserva estrutura de diretório

Para adicionar diretórios inteiros preservando a estrutura de pastas, podemos percorrer recursivamente:

import os
import zipfile

def adicionar_diretorio(zip_obj, caminho, prefixo=''):
    for raiz, _, arquivos in os.walk(caminho):
        for arquivo in arquivos:
            caminho_completo = os.path.join(raiz, arquivo)
            arcname = os.path.relpath(caminho_completo, os.path.dirname(caminho))
            zip_obj.write(caminho_completo, arcname)

with zipfile.ZipFile('backup.zip', 'w') as zf:
    adicionar_diretorio(zf, 'meus_documentos')

O módulo oferece diferentes métodos de compressão através das constantes:
- ZIP_STORED — sem compressão (apenas armazenamento)
- ZIP_DEFLATED — compressão DEFLATE (padrão, boa relação tamanho/velocidade)
- ZIP_BZIP2 — compressão BZIP2 (melhor taxa, mais lento)
- ZIP_LZMA — compressão LZMA (alta taxa, disponível apenas em Python 3.3+)

import zipfile

with zipfile.ZipFile('compactado.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
    zf.write('grande_arquivo.log')

# Nível de compressão (0-9, apenas para DEFLATE)
with zipfile.ZipFile('otimizado.zip', 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
    zf.write('dados_binarios.dat')

3. Extraindo e lendo conteúdo de ZIPs

A extração completa é feita com .extractall(), que recria a estrutura de diretórios no destino:

import zipfile

with zipfile.ZipFile('meu_arquivo.zip', 'r') as zf:
    zf.extractall('pasta_destino')  # Extrai tudo
    zf.extract('documento.txt', 'outra_pasta')  # Extrai apenas um arquivo

Para navegar pelos membros do arquivo:

with zipfile.ZipFile('dados.zip', 'r') as zf:
    # Lista todos os nomes
    for nome in zf.namelist():
        print(f'Arquivo: {nome}')

    # Informações detalhadas com ZipInfo
    info = zf.getinfo('documento.txt')
    print(f'Tamanho original: {info.file_size} bytes')
    print(f'Tamanho comprimido: {info.compress_size} bytes')
    print(f'Data de modificação: {info.date_time}')

Para ler conteúdo diretamente em memória sem extrair para disco:

import zipfile
from io import TextIOWrapper

with zipfile.ZipFile('textos.zip', 'r') as zf:
    # Lê como bytes
    dados = zf.read('notas.txt')
    print(dados.decode('utf-8'))

    # Lê como texto (útil para arquivos grandes)
    with TextIOWrapper(zf.open('relatorio.csv', 'r'), encoding='utf-8') as arquivo:
        for linha in arquivo:
            print(linha.strip())

4. Trabalhando com metadados e segurança

A classe ZipInfo expõe diversos atributos úteis:

import zipfile
import datetime

with zipfile.ZipFile('info.zip', 'r') as zf:
    info = zf.getinfo('arquivo_importante.pdf')

    print(f'Nome: {info.filename}')
    print(f'Tamanho original: {info.file_size} bytes')
    print(f'Tamanho comprimido: {info.compress_size} bytes')
    print(f'Taxa de compressão: {info.compress_size / info.file_size:.2%}')
    print(f'CRC32: {hex(info.CRC)}')

    # Convertendo data para datetime
    data_mod = datetime.datetime(*info.date_time)
    print(f'Modificado em: {data_mod}')

O suporte nativo a senhas é limitado — apenas o método de criptografia ZIP 2.0 (fraco) é suportado:

import zipfile

# Criando ZIP com senha
with zipfile.ZipFile('protegido.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
    zf.setpassword(b'minha_senha')
    zf.write('confidencial.txt')

# Extraindo com senha
with zipfile.ZipFile('protegido.zip', 'r') as zf:
    zf.setpassword(b'minha_senha')
    zf.extractall('extraido')

Para validação de integridade:

with zipfile.ZipFile('dados.zip', 'r') as zf:
    # .testzip() retorna o nome do primeiro arquivo corrompido ou None
    resultado = zf.testzip()
    if resultado:
        print(f'Arquivo corrompido: {resultado}')
    else:
        print('Integridade OK')

5. Compressão avançada com shutil

O módulo shutil oferece funções de alto nível para compactação:

import shutil

# Criando ZIP a partir de um diretório
shutil.make_archive('backup', 'zip', 'pasta_para_backup')

# Extração simplificada
shutil.unpack_archive('backup.zip', 'destino')

# Formatos suportados: 'zip', 'tar', 'gztar', 'bztar', 'xztar'
print(shutil.get_archive_formats())
# [('bztar', "bzip2'ed tar-file"), ('gztar', "gzip'ed tar-file"), 
#  ('tar', 'uncompressed tar file'), ('xztar', "xz'ed tar-file"), 
#  ('zip', 'ZIP file')]

6. Manipulação de grandes volumes e streaming

Para escrita incremental, use o modo 'a' (anexação):

import zipfile

# Criando ZIP em etapas
with zipfile.ZipFile('logs.zip', 'a') as zf:
    zf.write('log_dia_1.txt')

# Mais tarde...
with zipfile.ZipFile('logs.zip', 'a') as zf:
    zf.write('log_dia_2.txt')

Para manipular arquivos ZIP em memória (sem gravar em disco), use BytesIO:

import zipfile
from io import BytesIO

# Criando ZIP em memória
buffer = BytesIO()
with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
    zf.writestr('dados.txt', 'Conteúdo importante')
    zf.writestr('notas.csv', 'id,nome,valor\n1,João,100')

# Lendo o ZIP da memória
buffer.seek(0)
with zipfile.ZipFile(buffer, 'r') as zf:
    print(zf.read('dados.txt').decode())

# Enviando por HTTP, por exemplo
# response = requests.post(url, files={'arquivo': buffer.getvalue()})

Para processar ZIPs muito grandes sem carregar tudo em memória:

import zipfile

def filtrar_arquivos_grandes(caminho_zip, limite_mb=10):
    """Extrai apenas arquivos menores que o limite."""
    with zipfile.ZipFile(caminho_zip, 'r') as zf:
        for info in zf.infolist():
            if info.file_size < limite_mb * 1024 * 1024:
                zf.extract(info.filename)
                print(f'Extraído: {info.filename}')
            else:
                print(f'Ignorado (grande): {info.filename}')

filtrar_arquivos_grandes('backup_completo.zip', limite_mb=50)

7. Interoperabilidade e casos práticos

Integração com pathlib.Path para gerenciamento moderno de caminhos:

from pathlib import Path
import zipfile

pasta = Path('documentos')
with zipfile.ZipFile('documentos.zip', 'w') as zf:
    for arquivo in pasta.rglob('*'):
        if arquivo.is_file():
            # Preserva estrutura relativa
            arcname = str(arquivo.relative_to(pasta.parent))
            zf.write(arquivo, arcname)

Lendo ZIPs diretamente de respostas HTTP:

import requests
import zipfile
from io import BytesIO

response = requests.get('https://exemplo.com/dados.zip')
response.raise_for_status()

with zipfile.ZipFile(BytesIO(response.content)) as zf:
    for nome in zf.namelist():
        if nome.endswith('.csv'):
            dados = zf.read(nome)
            print(f'Processando {nome} com {len(dados)} bytes')

Script simples de backup e compactação de logs:

import zipfile
import os
from datetime import datetime, timedelta

def compactar_logs_antigos(diretorio_logs, dias=7):
    """Compacta logs mais antigos que N dias."""
    data_limite = datetime.now() - timedelta(days=dias)
    nome_zip = f'logs_antigos_{datetime.now():%Y%m%d}.zip'

    with zipfile.ZipFile(nome_zip, 'w', zipfile.ZIP_DEFLATED) as zf:
        for arquivo in Path(diretorio_logs).glob('*.log'):
            data_mod = datetime.fromtimestamp(arquivo.stat().st_mtime)
            if data_mod < data_limite:
                zf.write(arquivo, arquivo.name)
                arquivo.unlink()  # Remove o original
                print(f'Compactado: {arquivo.name}')

    print(f'Backup criado: {nome_zip}')

compactar_logs_antigos('/var/log/app', dias=30)

Referências