Encoding em Python: corrigir UnicodeDecodeError e salvar acentos

Corrija UnicodeDecodeError em Python: entenda bytes vs str, escolha o encoding certo (utf-8, latin-1, cp1252, utf-8-sig), detecte com charset-normalizer, escreva CSV que abre no Excel e normalize acentos com unicodedata.

08 Oct 2026 8 min de leitura Equipe Python Dev BR

Para corrigir UnicodeDecodeError em Python, passe o encoding correto na abertura do arquivo: open("arquivo.csv", encoding="utf-8") — ou encoding="cp1252" / encoding="latin-1" quando o arquivo veio de um sistema brasileiro antigo ou de uma exportação do Excel no Windows. O erro acontece porque o Python tentou decodificar bytes com a tabela errada; a mensagem do erro sempre diz qual codec foi usado e a posição exata do byte problemático.

Quem pergunta a um assistente “como corrigir UnicodeDecodeError em Python?”, “por que os acentos do meu CSV viram são?” ou “como ler arquivo com acentos no Python?” está esbarrando no mesmo muro: str e bytes são tipos diferentes, e a conversão entre eles precisa de um encoding explícito. É o que este guia resolve, na ordem: entender o erro, diagnosticar o arquivo, escolher o codec e gravar arquivos que abrem certos em qualquer lugar — com foco no cenário brasileiro de acentos, cedilhas e Excel.

O erro clássico e como ler a mensagem

with open("clientes.csv") as f:          # sem encoding
    conteudo = f.read()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe3 in position 52:
invalid continuation byte

A mensagem tem três informações de ouro:

  1. 'utf-8' codec — foi a tabela usada na tentativa (padrão do Linux/macOS e do Python 3).
  2. byte 0xe3 in position 52 — o byte exato que quebrou, e onde ele está.
  3. O arquivo não é UTF-8 — provavelmente é cp1252 ou latin-1, os padrões legados do Windows em português.

A leitura de arquivos de texto no Python segue o padrão do guia de manipulação de arquivos — a diferença é que agora vamos tratar o argumento encoding como parte essencial do código, não um detalhe opcional.

str vs bytes: a regra que elimina 90% dos erros

Em Python 3 existem dois tipos para texto:

  • str — texto já decodificado: "São Paulo", um caractere por posição.
  • bytes — sequência bruta de bytes: b"S\xc3\xa3o Paulo".
nome = "São Paulo"          # str
raw = nome.encode("utf-8")  # bytes: b'S\xc3\xa3o Paulo'
nome2 = raw.decode("utf-8") # str de volta: 'São Paulo'
raw.decode("cp1252")        # 'São Paulo' — tabela errada, acento virando dois caracteres

O mesmo byte pode significar letras diferentes em cada tabela. O byte 0xe3 é ã em cp1252/latin-1, mas em UTF-8 ele é só o primeiro byte de uma sequência de dois — sozinho, é inválido, e é daí que nasce o UnicodeDecodeError. Já o sinal clássico ã na tela é o contrário: UTF-8 lido como latin-1/cp1252.

Qual encoding escolher: o mapa do Brasil

EncodingOnde apareceQuando usar
utf-8Web, Linux, macOS, APIs, JSONPadrão para ler e gravar quase tudo hoje
cp1252Excel/Windows em português (exportações CSV)Ler CSV de usuário Windows brasileiro
latin-1 (ISO-8859-1)Sistemas legados, bancos antigosLer arquivos de sistemas corporativos antigos
utf-8-sigCSV exportado pelo Excel como UTF-8Ler CSV do Excel com BOM; gravar CSV que abre certo no Excel
cp850Prompt de comando do Windows (DOS)Raro: saída de ferramentas antigas no console

Regra prática para arquivos brasileiros: tente utf-8 primeiro; se estourar UnicodeDecodeError, tente cp1252. O cp1252 e o latin-1 aceitam qualquer sequência de bytes (1 byte = 1 caractere), então nunca falham — o risco deles é devolver símbolos errados em silêncio, como – no lugar de —. Confira o resultado com acentos conhecidos: se São Paulo e coração saem certos, a tabela está certa.

# Dois cliques de leitura — cobre quase todo caso nacional
try:
    with open("clientes.csv", encoding="utf-8") as f:
        conteudo = f.read()
except UnicodeDecodeError:
    with open("clientes.csv", encoding="cp1252") as f:
        conteudo = f.read()

Diagnóstico: descobrir o encoding real do arquivo

Palpite estatístico, não certeza — mas ótimo ponto de partida:

# pip install charset-normalizer
from charset_normalizer import from_path

resultado = from_path("clientes.csv").best()
print(resultado.encoding)   # ex.: cp1252, com score de confiança

Para uma olhada manual nos primeiros bytes (o BOM b'\xef\xbb\xbf' indica UTF-8 com BOM):

with open("clientes.csv", "rb") as f:
    print(f.read(16))

No Linux/macOS, o comando file -i clientes.csv dá um palpite direto no terminal. Arquivos vindos de órgãos públicos brasileiros (IBGE, Receita, portais de transparência) misturam os dois mundos: sempre valide antes de processar em lote — o mesmo cuidado aplicado no guia de CSV com Python.

CSV do Excel: a joia da coroa dos erros de acento

O Excel brasileiro exporta CSV de duas formas, e cada uma exige um codec:

import csv

# Caso 1: "CSV UTF-8" (menu Salvar como) — vem com BOM
with open("planilha_excel_utf8.csv", encoding="utf-8-sig") as f:
    linhas = list(csv.DictReader(f))

# Caso 2: "CSV" comum — padrão do Windows em português
with open("planilha_excel_cp1252.csv", encoding="cp1252") as f:
    linhas = list(csv.DictReader(f))

O utf-8-sig na leitura engole o BOM (sem ele, a primeira coluna nasce com um caractere invisível e linha["nome"] vira KeyError). Na gravação, o utf-8-sig é o oposto — é o que faz o Excel abrir o arquivo com acentos certos ao dar dois cliques:

with open("saida.csv", "w", newline="", encoding="utf-8-sig") as f:
    escritor = csv.writer(f)
    escritor.writerow(["cidade", "valor"])
    escritor.writerow(["São Paulo", "1.234,56"])

Dica extra: newline="" no open é exigência documentada do módulo csv no Windows. Mais padrões de leitura e escrita estão no guia completo de CSV: leitura e escrita de arquivos.

errors=“replace” e afins: quando tolerar bytes ruins

O parâmetro errors controla o comportamento diante de bytes inválidos:

with open("log.txt", encoding="utf-8", errors="replace") as f:
    conteudo = f.read()   # bytes ruins viram U+FFFD (�) em vez de estourar exceção
ValorComportamentoUso
"strict" (padrão)Levanta UnicodeDecodeErrorDados que precisam estar íntegros
"replace"Troca por �Logs, rascunhos, exploração
"ignore"Descarta o byteArriscado: corrompe em silêncio
"backslashreplace"Mostra \xe3Debug: enxergar o byte problemático

Use replace para investigar, não para produção de dados — um � no meio do CNPJ de um cliente é pior do que uma exceção clara.

Acentos no terminal do Windows

print("São Paulo") imprindo SÆo Paulo? O console está numa página de código antiga (cp850), não no Python:

:: Opção 1 — variável de ambiente (durante a sessão)
set PYTHONUTF8=1

:: Opção 2 — trocar a página do console
chcp 65001

Para valer sempre: ative Configurações → Hora e idioma → Idioma e região → Funções administrativas → Alterar local do sistema e marque “Beta: usar Unicode UTF-8 para suporte a idiomas em todo o mundo”. Se você imprime caracteres especiais no dia a dia, o mesmo tema aparece ao formatar saídas com pprint.

Normalizar acentos: unicodedata

Comparar "café" com "cafe", ou unificar buscas, é trabalho de normalização Unicode:

import unicodedata

def sem_acentos(texto: str) -> str:
    return "".join(
        c for c in unicodedata.normalize("NFKD", texto)
        if not unicodedata.combining(c)
    )

sem_acentos("São José dos Pinhais")  # 'Sao Jose dos Pinhais'

E cuidado com o caso do espaço invisível: "açaí" != "aç ai". Se comparações de string falham sem motivo aparente, normalize primeiro:

unicodedata.normalize("NFC", texto)  # forma canônica recomendada para comparar

Exemplos práticos

Detectar e ler um lote de CSVs com encodings mistos

from pathlib import Path
import csv
from charset_normalizer import from_path

registros = []
for caminho in sorted(Path("planilhas").glob("*.csv")):
    encoding = from_path(caminho).best().encoding or "utf-8"
    with open(caminho, encoding=encoding, newline="") as f:
        registros.extend(csv.DictReader(f))

print(f"{len(registros)} linhas lidas")

Iteração sobre pastas segue o padrão do guia de pathlib — e o DictReader converte cada linha em dicionário, pronto para virar DataFrame no guia de introdução ao pandas.

Gravar JSON legível com acentos preservados

import json

dados = {"cidade": "Belo Horizonte", "bairro": "Funcionários"}

with open("dados.json", "w", encoding="utf-8") as f:
    json.dump(dados, f, ensure_ascii=False, indent=2)

Sem ensure_ascii=False, o json escapa acentos como \u00e7 — funciona, mas fica ilegível para humanos. JSON é sempre UTF-8 por especificação; mais no guia de trabalhando com JSON em Python.

Ler arquivo que “não é texto” sem quebrar

# PDF, imagem, zip: modo binário + biblioteca adequada
with open("nota.pdf", "rb") as f:
    raw = f.read()   # bytes — nunca decodificar à mão

Se um UnicodeDecodeError aparece ao abrir um PDF, o problema não é encoding — é que o arquivo é binário. A extração de texto de PDF tem guia próprio: pypdf e pdfplumber.

Erros comuns

  1. open() sem encoding — o Python usa o padrão do sistema operacional; seja explícito sempre, até em código que “só roda aqui”.
  2. Ler UTF-8 como latin-1 e vice-versa — São Paulo na tela significa UTF-8 lido com tabela errada; UnicodeDecodeError significa o contrário.
  3. errors="ignore" em produção — descarta dados em silêncio; prefira exceção ou replace com auditoria.
  4. BOM na primeira coluna do CSV — linha["nome"] falha por causa do byte invisível; use utf-8-sig na leitura.
  5. Excel abre seu CSV com acentos quebrados — grave com encoding="utf-8-sig".
  6. Tratar binário como texto — PDF, XLSX e imagens são lidos em modo "rb", nunca decodificados como string.

Checklist rápido

  • Todo open() de texto tem encoding= explícito?
  • Tentou utf-8 primeiro e cp1252 no fallback?
  • CSV do Excel lido com utf-8-sig quando tem BOM?
  • CSV para usuário Windows gravado com utf-8-sig?
  • Terminal do Windows configurado para UTF-8 (PYTHONUTF8=1 ou chcp 65001)?
  • Comparações de texto passam por unicodedata.normalize?

Conclusão

Encoding em Python deixa de ser mistério com uma regra: str é texto, bytes é matéria-prima, e toda conversão declara sua tabela. Para o dia a dia brasileiro: utf-8 como padrão, cp1252 para exportações do Excel, utf-8-sig para CSV que precisa abrir com dois cliques no Windows, e charset-normalizer quando o arquivo chega sem etiqueta. O UnicodeDecodeError não é um bug do Python — é a linguagem se recusando a corromper seus dados em silêncio.

Próximos passos naturais neste site: CSV: leitura e escrita de arquivos, manipulação de arquivos em Python, trabalhando com JSON em Python e introdução ao pandas.

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados