Como processar ficheiros CSV grandes em ETL com pandas
Trabalhar com ficheiros CSV grandes é uma tarefa muito comum em ETL, mas abrir um ficheiro de vários gigabytes de uma só vez pode esgotar a memória do computador e provocar o temido erro MemoryError. Isto acontece porque muitas rotinas de extração carregam tudo de uma vez, sem pensar no tamanho real dos dados. A solução é processar o ficheiro por partes (chunks) com pandas, lendo e transformando os dados aos poucos. Vamos ver, passo a passo, como processar ficheiros CSV grandes em ETL de forma simples e segura.
Pré-requisitos
- Python 3.9 ou superior instalado.
- A biblioteca pandas: instale com
pip install pandas. - Um ficheiro CSV grande para testar (por exemplo,
vendas.csvcom colunas comopaisevalor). - Noções básicas de Python (variáveis e ciclos
for).
Passo 1: Perceber o problema
O método habitual, pd.read_csv("vendas.csv"), carrega o ficheiro inteiro para a memória RAM. Se o ficheiro tiver 5 GB, o pandas precisa de vários gigabytes livres, muitas vezes mais do que o tamanho do ficheiro, porque cada coluna ocupa espaço extra. Em máquinas com pouca memória, o resultado é um MemoryError e o programa pára.
Quando a memória RAM se esgota, o sistema operativo começa a usar o disco como memória virtual (swap), e o programa fica extremamente lento antes de, muitas vezes, falhar. A ideia central é nunca ter o ficheiro todo na memória ao mesmo tempo: lemos um bloco de linhas, tratamos esse bloco, libertamos a memória e passamos ao seguinte.
Passo 2: Ler o CSV por chunks
O parâmetro chunksize do read_csv indica quantas linhas cada bloco deve ter. Em vez de um DataFrame, o pandas devolve um iterador que produz um DataFrame de cada vez:
import pandas as pd
# cada bloco tem 100 000 linhas
for chunk in pd.read_csv("vendas.csv", chunksize=100_000):
print(chunk.shape)
Cada chunk é um DataFrame normal do pandas. Pode aplicar-lhe tudo o que já conhece: filtros, novas colunas, agrupamentos.
Dica: comece com blocos de 100 000 linhas e ajuste conforme a memória disponível. Blocos maiores são mais rápidos, mas ocupam mais RAM; blocos mais pequenos são mais seguros em máquinas modestas.
Passo 3: Transformar e agregar cada bloco
Num pipeline ETL queremos transformar os dados e, muitas vezes, calcular um resumo. O exemplo seguinte limpa cada bloco (mantém apenas valores positivos) e vai somando o total de vendas por país, acumulando o resultado num dicionário:
import pandas as pd
total_por_pais = {}
for chunk in pd.read_csv("vendas.csv", chunksize=100_000):
# transformar: manter apenas vendas com valor positivo
chunk = chunk[chunk["valor"] > 0]
# agregar: somar o valor por pais neste bloco
soma = chunk.groupby("pais")["valor"].sum()
for pais, valor in soma.items():
total_por_pais[pais] = total_por_pais.get(pais, 0) + valor
resultado = pd.Series(total_por_pais).sort_values(ascending=False)
print(resultado)
Repare que só mantemos na memória o dicionário total_por_pais, que é pequeno. Este padrão (resumir cada bloco e somar os resumos) funciona para contagens, somas e médias ponderadas, e é a base de qualquer agregação que não caiba na memória de uma só vez.
Passo 4: Escrever o resultado sem esgotar a memória
Se o objetivo for gravar os dados já tratados noutro ficheiro, escreva cada bloco à medida que o processa. O truque é escrever o cabeçalho apenas no primeiro bloco e usar o modo "a" (append) nos seguintes:
import pandas as pd
primeiro = True
for chunk in pd.read_csv("vendas.csv", chunksize=100_000):
limpo = chunk[chunk["valor"] > 0]
limpo.to_csv(
"vendas_limpas.csv",
mode="w" if primeiro else "a",
header=primeiro,
index=False,
)
primeiro = False
Assim, o ficheiro de saída cresce bloco a bloco e nunca precisa de ter tudo na memória.
Passo 5: Reduzir ainda mais a memória (opcional)
Pode poupar bastante memória lendo apenas as colunas necessárias com usecols e indicando tipos mais leves com dtype:
import pandas as pd
for chunk in pd.read_csv(
"vendas.csv",
usecols=["pais", "valor"],
dtype={"valor": "float32"},
chunksize=100_000,
):
...
Ler menos colunas e usar float32 em vez de float64 reduz o espaço ocupado por cada bloco, permitindo blocos maiores ou máquinas mais modestas.
Verificar o resultado
Para confirmar que não perdeu dados, conte as linhas do ficheiro de saída, também por chunks, para não voltar a encher a memória:
import pandas as pd
total = sum(len(c) for c in pd.read_csv("vendas_limpas.csv", chunksize=100_000))
print("Linhas no ficheiro final:", total)
Compare este número com o que esperava depois da limpeza. Durante a execução, abra o Gestor de Tarefas (ou o comando htop no Linux) e confirme que a memória usada pelo Python se mantém estável, em vez de subir sem parar.
Conclusão
Com o parâmetro chunksize consegue processar ficheiros CSV muito maiores do que a memória disponível, mantendo o seu pipeline ETL estável e previsível. A partir daqui, experimente ajustar o tamanho do bloco ao seu hardware e explore alternativas como o polars ou o dask quando precisar de ainda mais velocidade. Qual é o maior ficheiro que já teve de processar, e quanto tempo demorou?