ETL com Python em 2026: APIs, Pandas, DuckDB e Jobs Confiáveis
Aprenda a montar pipelines ETL com Python em 2026: extração de APIs, validação, transformação, carga em DuckDB, logs, retries e boas práticas para portfólio.
ETL continua sendo uma das habilidades mais úteis para quem trabalha com Python em empresas brasileiras. Mesmo com tanta conversa sobre IA, agentes e dashboards, boa parte do valor real ainda depende de uma tarefa básica: buscar dados de lugares diferentes, limpar o que veio torto, salvar em um formato confiável e deixar isso rodando sem alguém abrir um notebook manualmente toda semana.
Em 2026, um pipeline ETL em Python não precisa começar com uma plataforma pesada. Para muitos times pequenos, consultorias, áreas financeiras, operações comerciais e projetos de portfólio, a combinação HTTPX + Pandas + DuckDB + logs estruturados resolve muito bem. Você consegue consumir APIs, tratar paginação, validar campos críticos, transformar tabelas, gerar arquivos Parquet ou uma base DuckDB local e depois servir esses dados para relatórios, Streamlit, FastAPI ou análises pontuais.
Este guia mostra uma arquitetura prática para criar ETLs pequenos e confiáveis. A ideia é fugir do script frágil que só roda na máquina do autor e chegar em um job que pode ser versionado, testado, agendado e explicado em entrevista. Se você ainda está começando com dados, leia também os guias de APIs públicas brasileiras com Python, Pandas e DuckDB para analytics.
O que é ETL?
ETL significa extract, transform, load: extrair, transformar e carregar dados. O fluxo clássico tem três etapas:
- extração: buscar dados em APIs, bancos, CSVs, planilhas, arquivos JSON ou sistemas internos;
- transformação: corrigir tipos, padronizar nomes, remover duplicados, calcular colunas e validar regras;
- carga: salvar o resultado em um destino, como DuckDB, PostgreSQL, Parquet, SQLite, S3 ou uma planilha final.
Na prática, bons ETLs também precisam de observabilidade. Um job que falha em silêncio é pior do que não ter job. O mínimo profissional inclui log claro, código de saída correto, contagem de linhas processadas, tratamento de erro e alguma forma de não duplicar dados quando o processo roda de novo.
Quando Python é uma boa escolha
Python é forte para ETL por três motivos. Primeiro, tem bibliotecas maduras para HTTP, arquivos, bancos e dados tabulares. Segundo, é legível para pessoas de dados, backend e operações. Terceiro, permite começar simples e evoluir conforme o volume cresce.
Use Python para:
- consumir APIs públicas ou privadas;
- transformar CSVs e planilhas recorrentes;
- gerar bases para dashboards internos;
- automatizar relatórios financeiros, comerciais ou operacionais;
- preparar dados para modelos de machine learning;
- criar pipelines de portfólio com dados brasileiros verificáveis.
Python não é obrigatório para tudo. Se o gargalo for throughput extremo, baixa latência ou processamento concorrente de muitos arquivos grandes, outras linguagens podem complementar bem. Em arquiteturas mistas, é comum usar Python para transformação e análise, enquanto serviços de ingestão de alta concorrência podem ser escritos em Go. O importante é escolher pela necessidade do sistema, não por moda.
Estrutura mínima do projeto
Um ETL profissional não precisa ter dezenas de pastas, mas precisa separar responsabilidades. Uma estrutura inicial boa é:
etl-clientes/
pyproject.toml
README.md
.env.example
src/
etl_clientes/
__init__.py
config.py
extract.py
transform.py
load.py
main.py
tests/
test_transform.py
data/
raw/
processed/
O arquivo extract.py sabe buscar dados. O transform.py sabe limpar e validar. O load.py salva o resultado. O main.py orquestra o fluxo. Essa divisão torna o projeto mais fácil de testar, reaproveitar e explicar.
Crie o ambiente com uv ou venv:
uv init etl-clientes
cd etl-clientes
uv add httpx pandas duckdb python-dotenv structlog
uv add --dev pytest
Se preferir venv, o raciocínio é o mesmo:
python -m venv .venv
source .venv/bin/activate
pip install httpx pandas duckdb python-dotenv structlog pytest
Extraindo dados de uma API
O exemplo abaixo usa uma função pequena para buscar páginas de uma API. Em produção, você deve adaptar autenticação, paginação e nomes de campos ao sistema real.
# src/etl_clientes/extract.py
from __future__ import annotations
import httpx
def buscar_pagina(base_url: str, pagina: int) -> list[dict]:
resposta = httpx.get(
f"{base_url}/clientes",
params={"page": pagina, "per_page": 100},
timeout=20,
)
resposta.raise_for_status()
dados = resposta.json()
return dados.get("items", [])
def extrair_clientes(base_url: str, max_paginas: int = 50) -> list[dict]:
todos: list[dict] = []
for pagina in range(1, max_paginas + 1):
itens = buscar_pagina(base_url, pagina)
if not itens:
break
todos.extend(itens)
return todos
Para um tutorial, isso parece simples. Para trabalho real, alguns detalhes fazem diferença: timeout explícito, raise_for_status(), parada quando a página vem vazia e limite máximo de páginas para evitar loop infinito. Se a API tiver rate limit, adicione retry com espera progressiva. Se tiver autenticação, leia tokens de variável de ambiente, nunca de código versionado.
Transformando com Pandas
A etapa de transformação deve deixar os dados previsíveis. Evite espalhar limpeza dentro da extração ou da carga. O ideal é que transform.py receba dados brutos e devolva um DataFrame pronto para salvar.
# src/etl_clientes/transform.py
from __future__ import annotations
import pandas as pd
COLUNAS_FINAIS = [
"id_cliente",
"nome",
"email",
"cidade",
"uf",
"criado_em",
]
def transformar_clientes(registros: list[dict]) -> pd.DataFrame:
df = pd.DataFrame.from_records(registros)
if df.empty:
return pd.DataFrame(columns=COLUNAS_FINAIS)
df = df.rename(
columns={
"id": "id_cliente",
"name": "nome",
"created_at": "criado_em",
}
)
df["email"] = df["email"].str.strip().str.lower()
df["uf"] = df["uf"].str.upper().str.strip()
df["criado_em"] = pd.to_datetime(df["criado_em"], errors="coerce", utc=True)
df = df.dropna(subset=["id_cliente", "email"])
df = df.drop_duplicates(subset=["id_cliente"], keep="last")
return df[COLUNAS_FINAIS].sort_values("id_cliente")
Essa função corrige nomes de coluna, padroniza email, normaliza UF, converte data, remove linhas inválidas e elimina duplicatas. Ela também é fácil de testar porque não depende de rede, banco ou arquivo.
Um teste simples já pega boa parte dos erros:
# tests/test_transform.py
from etl_clientes.transform import transformar_clientes
def test_transformar_clientes_normaliza_email_e_remove_duplicados():
registros = [
{"id": 1, "name": "Ana", "email": " [email protected] ", "cidade": "Recife", "uf": "pe", "created_at": "2026-05-01"},
{"id": 1, "name": "Ana", "email": "[email protected]", "cidade": "Recife", "uf": "PE", "created_at": "2026-05-02"},
]
df = transformar_clientes(registros)
assert len(df) == 1
assert df.iloc[0]["email"] == "[email protected]"
assert df.iloc[0]["uf"] == "PE"
Carregando em DuckDB
DuckDB é excelente para ETLs locais e analytics embarcado. Ele lê Parquet muito bem, executa SQL rápido e evita a complexidade de subir um servidor de banco para um projeto pequeno.
# src/etl_clientes/load.py
from __future__ import annotations
import duckdb
import pandas as pd
def carregar_clientes(df: pd.DataFrame, caminho_banco: str = "data/processed/etl.duckdb") -> None:
with duckdb.connect(caminho_banco) as conn:
conn.execute("CREATE SCHEMA IF NOT EXISTS marts")
conn.register("clientes_df", df)
conn.execute(
"""
CREATE OR REPLACE TABLE marts.clientes AS
SELECT * FROM clientes_df
"""
)
Para carga incremental, você pode trocar CREATE OR REPLACE por uma estratégia com staging table e MERGE, quando o destino suporta. Em pipelines pequenos, substituir a tabela inteira pode ser aceitável se a extração for rápida e idempotente. Em produção, documente a escolha: o leitor precisa entender se o job recalcula tudo ou só adiciona novidades.
Orquestrando o job
O main.py junta as etapas e registra o que aconteceu. Não precisa inventar framework antes da hora.
# src/etl_clientes/main.py
from __future__ import annotations
import os
import structlog
from dotenv import load_dotenv
from etl_clientes.extract import extrair_clientes
from etl_clientes.load import carregar_clientes
from etl_clientes.transform import transformar_clientes
log = structlog.get_logger()
def main() -> None:
load_dotenv()
base_url = os.environ["API_BASE_URL"]
registros = extrair_clientes(base_url)
df = transformar_clientes(registros)
carregar_clientes(df)
log.info("etl_clientes_finalizado", registros=len(registros), linhas=len(df))
if __name__ == "__main__":
main()
No .env.example, deixe apenas nomes de variáveis:
API_BASE_URL="https://api.exemplo.com"
API_TOKEN="preencha-localmente"
Nunca publique tokens reais. Para portfólio, explique no README como rodar o job com dados públicos ou dados fictícios. Isso mostra cuidado com segurança e facilita a avaliação por recrutadores.
Boas práticas que evitam dor de cabeça
Alguns cuidados pequenos separam um ETL amador de um ETL confiável:
- idempotência: rodar o job duas vezes não deve duplicar dados;
- logs úteis: registre origem, contagem de registros, duração e destino;
- timeouts: toda chamada externa precisa ter limite;
- validação: confira colunas obrigatórias, datas inválidas e chaves nulas;
- testes na transformação: regra de negócio em Pandas também quebra;
- dados brutos preservados: quando possível, salve uma cópia raw para auditoria;
- README claro: mostre entrada, saída, comandos e decisões técnicas.
Se o pipeline crescer, considere Prefect, Dagster ou Airflow. Mas não comece por eles se o problema cabe em um comando agendado no cron, GitHub Actions, Gitea Actions ou um worker simples. Ferramenta de orquestração resolve dependências, retries e histórico, mas também aumenta operação.
Ideias de ETL para portfólio brasileiro
Projetos bons têm contexto local e uma pergunta clara. Algumas ideias:
- coletar séries temporais do Banco Central do Brasil (BCB) e gerar indicadores mensais;
- cruzar dados de municípios do Instituto Brasileiro de Geografia e Estatística (IBGE) com uma base de vendas fictícia;
- baixar dados abertos de licitações e criar um painel por cidade;
- transformar planilhas públicas em Parquet e consultar com DuckDB;
- montar um job que atualiza dados para um dashboard em Streamlit;
- comparar preços, prazos ou indicadores de fontes públicas com histórico versionado.
O projeto fica mais forte quando você inclui testes, README.md, diagrama simples, exemplos de saída e uma seção explicando limitações. Para candidatura, conecte o ETL com uma entrega visível: dashboard, relatório, API ou notebook final. Veja também projetos de portfólio Python e como conseguir vaga Python com IA para transformar esse trabalho em evidência profissional.
Conclusão
ETL com Python em 2026 é menos sobre escrever um script gigante e mais sobre montar um fluxo previsível. Busque dados com timeout, transforme em funções testáveis, carregue em um destino simples, registre contagens e documente como rodar. HTTPX, Pandas e DuckDB já são suficientes para muitos problemas reais no Brasil.
Depois que o pipeline estiver confiável, você pode evoluir para Parquet, PostgreSQL, pgvector, orquestração, deploy em container e dashboards. Mas a base continua igual: dados entram de forma controlada, regras ficam explícitas, erros aparecem rápido e o resultado pode ser reproduzido por outra pessoa.
Equipe python.dev.br
Contribuidor do Python Brasil — Aprenda Python em Português