Todos os artigos
74 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Tutoriais

Como importar CSV sem corromper caracteres: encoding, BOM e delimitadores

UTF-8, latin1, cp1252, BOM — por que seus acentos aparecem como lixo e como corrigir de uma vez, no Excel, Python e linha de comando.

COVER · Tutoriais

Todo pipeline de dados tem aquela fase clássica: você recebe um CSV de alguém, abre no editor, e metade dos campos aparece como ção ou “nomeâ€. A exportação estava certa. O arquivo chegou correto. Só que o encoding não bate, e agora você tem lixo onde deveria ter "São Paulo" ou "descrição".

Esse problema não é raro — é o padrão quando times de dados, sistemas legados e Excel convivem no mesmo pipeline. Entender o que está acontecendo leva quinze minutos. Resolver sem entender pode levar dias.

O que é encoding e por que CSV não resolve isso pra você

CSV é um formato textual sem cabeçalho de metadados. Diferente de JSON (que também é texto, mas tem especificação de encoding implícita em UTF-8) ou de XLSX (que é um ZIP com XML que declara o encoding), um arquivo .csv não carrega informação sobre como foi codificado. Você simplesmente abre e torce para o encoding bater.

Os encodings mais comuns no Brasil:

  • UTF-8 — padrão da web, do Linux, do macOS, de praticamente toda stack moderna
  • ISO-8859-1 (latin1) — legado europeu, cobre acentos do português
  • Windows-1252 (cp1252) — superset do latin1, usado pelo Windows em BR; é o que o Excel gera por padrão quando você "Salvar Como CSV" em PT-BR

A diferença prática: ç em UTF-8 é dois bytes (0xC3 0xA7). Em latin1 e cp1252, é um byte (0xE7). Quando você abre um arquivo cp1252 como se fosse UTF-8, esses dois bytes são interpretados como dois caracteres separados — e você obtém ç.

Excel quebrando acentos: o problema mais comum

O Excel tem um comportamento bem documentado e bem irritante: ao abrir um CSV com duplo-clique no Windows, ele assume cp1252 (no Brasil) e não pergunta nada. Se o arquivo estiver em UTF-8, tudo que tem acento vira lixo.

A solução correta não é converter o arquivo — é usar o assistente de importação:

  1. No Excel, vá em Dados → Obter Dados → De Arquivo de Texto/CSV
  2. Selecione o arquivo
  3. Na tela de preview, mude Origem do Arquivo para 65001: Unicode (UTF-8)
  4. Ajuste o delimitador se necessário
  5. Carregue

Versões mais antigas do Excel usam o caminho Dados → Obter Dados Externos → De Texto com o mesmo passo de seleção de codificação no assistente. O resultado é idêntico.

Se o arquivo realmente estiver em cp1252 (comum em exportações de sistemas legados, ERPs antigos, planilhas geradas pelo Windows), selecione a opção correspondente — geralmente 1252: Europa Ocidental (Windows).

UTF-8 com BOM: a gambeta que pode ajudar ou atrapalhar

BOM (Byte Order Mark) é uma sequência de três bytes no início do arquivo (0xEF 0xBB 0xBF) que sinaliza explicitamente que o arquivo está em UTF-8. Não é obrigatório pela spec, mas alguns programas — incluindo o Excel — usam o BOM para detectar automaticamente o encoding ao abrir por duplo-clique.

Se você controla a geração do CSV e sabe que vai ser aberto no Excel, adicionar BOM é uma forma pragmática de evitar o problema:

# Python: escrever CSV com BOM para compatibilidade com Excel
import csv

with open('saida.csv', 'w', newline='', encoding='utf-8-sig') as f:
    writer = csv.writer(f)
    writer.writerow(['nome', 'cidade', 'descrição'])
    writer.writerow(['João', 'São Paulo', 'cliente ativo'])

O encoding utf-8-sig no Python escreve o BOM automaticamente. O arquivo continua sendo UTF-8 válido — a diferença é que o Excel vai detectar corretamente.

O lado ruim do BOM: alguns parsers antigos ou mal escritos tratam os três bytes como parte do conteúdo e você vê nome na primeira coluna. Ferramentas modernas (Python csv, pandas, PostgreSQL COPY) ignoram o BOM corretamente ou têm opção para isso.

Diagnosticando o encoding de um arquivo desconhecido

Antes de tentar converter, descubra o que você tem:

# Linux/macOS — file detecta encoding por heurística
file -i dados.csv
# saída esperada: dados.csv: text/plain; charset=utf-8

# Python — chardet faz detecção estatística
python -c "
import chardet
with open('dados.csv', 'rb') as f:
    print(chardet.detect(f.read(100_000)))
"
# {'encoding': 'ISO-8859-1', 'confidence': 0.73, 'language': ''}

A confiança abaixo de 0.9 é sinal de arquivo misto ou encoding incomum. Nesses casos, abra o arquivo em modo binário e inspecione os bytes manualmente — ou tente cada encoding candidato e veja qual produz texto legível.

Convertendo encoding na linha de comando

Quando o encoding está confirmado, a conversão é trivial:

# iconv: ferramenta padrão Unix
iconv -f windows-1252 -t utf-8 entrada.csv > saida.csv

# Python: mais flexível, lida com erros
python -c "
with open('entrada.csv', 'r', encoding='cp1252') as f_in, \
     open('saida.csv', 'w', encoding='utf-8') as f_out:
    f_out.write(f_in.read())
"

Para pipelines recorrentes, normalizar para UTF-8 na entrada é a decisão certa — uma vez só, antes de qualquer processamento. Isso elimina a classe inteira de problemas downstream.

Delimitadores: o problema que vem junto

Encoding e delimitador costumam aparecer juntos como fonte de corrupção. CSV não é um formato padronizado de verdade: vírgula, ponto-e-vírgula e tab são todos usados dependendo da região e do sistema de origem.

Sistemas europeus e brasileiros frequentemente usam ponto-e-vírgula como delimitador porque a vírgula é o separador decimal local. Um CSV com ; como delimitador que você tenta parsear com , vai parecer ter uma única coluna gigante com todos os campos concatenados.

# pandas: sempre especifique sep e encoding explicitamente
import pandas as pd

df = pd.read_csv(
    'dados.csv',
    sep=';',           # não confie no sniff automático para produção
    encoding='cp1252', # ou 'utf-8', dependendo da origem
    dtype=str          # evite conversão automática de tipos na leitura
)

O dtype=str merece destaque: pandas converte CPFs, CEPs e códigos com zeros à esquerda para inteiro por padrão. 01310-100 vira 1310100. Leia como string, valide depois.

Se você trabalha com ETL e está integrando diferentes fontes de CSV numa pipeline maior, o contexto de ETL vs ELT ajuda a entender onde colocar essa normalização de encoding — no extract ou no transform.

Inspecionando e convertendo arquivos no browser

Para arquivos pequenos ou inspeção rápida sem instalar nada, uso o CSV para JSON do Quick Tools para visualizar o conteúdo e verificar se os acentos estão chegando corretamente antes de processar. Se o arquivo abre com lixo no preview, o problema está no encoding da origem — e eu já sei onde corrigir antes de botar no pipeline.

Perguntas frequentes

Por que meu CSV abre errado no Excel mas está correto em outros programas?

O Excel no Windows assume cp1252 ao abrir CSVs por duplo-clique. Se o arquivo estiver em UTF-8, acentos e caracteres especiais aparecem corrompidos. A solução é importar pelo menu Dados → Obter Dados, não abrir diretamente. Lá você escolhe o encoding correto antes de carregar.

Como verificar se um arquivo CSV está em UTF-8 ou latin1?

Use file -i arquivo.csv no terminal (Linux/macOS) ou o módulo chardet no Python. Para Windows, o Notepad++ mostra o encoding na barra inferior e permite recodificar via menu Codificação. Um sinal visual rápido: se o arquivo tiver acentos e você os ver como ção, o arquivo provavelmente está em UTF-8 sendo lido como latin1. Se vir ????, pode ser qualquer encoding não suportado.

Devo sempre usar UTF-8 nos meus CSVs?

Sim, para qualquer sistema novo. UTF-8 é o encoding padrão da web, de todas as linguagens modernas e da maioria dos bancos de dados. Se você precisa de compatibilidade com Excel sem que o usuário configure nada, use utf-8-sig (UTF-8 com BOM) — o Excel detecta automaticamente e abre corretamente.

O que fazer quando o CSV tem encoding misto, com algumas linhas em UTF-8 e outras em latin1?

Isso acontece quando arquivos de fontes diferentes são concatenados sem normalização. A abordagem mais robusta é ler linha por linha, tentar UTF-8 primeiro e, em caso de erro de decodificação, tentar latin1/cp1252:

def decode_line(raw_bytes):
    try:
        return raw_bytes.decode('utf-8')
    except UnicodeDecodeError:
        return raw_bytes.decode('cp1252', errors='replace')

O errors='replace' substitui bytes indecodificáveis por ? em vez de lançar exceção — útil para não travar o pipeline em dados sujos.

Encoding correto não é opcional

A maioria dos bugs de encoding em pipelines de dados não é difícil de resolver — é difícil de diagnosticar porque o sintoma (dado corrompido) aparece longe da causa (arquivo aberto com encoding errado). Normalizar para UTF-8 na entrada do pipeline, especificar encoding e delimitador explicitamente no código e nunca confiar no sniff automático são três regras que eliminam essa categoria de problema de vez.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil