--:--:--

Python UnicodeDecodeError: como descobrir o encoding sem corromper o arquivo

Publicado em 2026-08-29T20:14:00Z · atualizado em 2026-08-29T19:14:29+00:00

O **UnicodeDecodeError** ocorre quando o Python tenta transformar bytes em texto usando uma codificação incompatível com o conteúdo. Um CSV exportado por sistema antigo pode estar em Windows-1252; um arquivo com BOM pode exigir `utf-8-sig`;

Transformação entre bytes, decoder, texto Unicode e validação
Imagem de apoio ao tema do artigo.

Python UnicodeDecodeError: como descobrir o encoding sem corromper o arquivo

O UnicodeDecodeError ocorre quando o Python tenta transformar bytes em texto usando uma codificação incompatível com o conteúdo. Um CSV exportado por sistema antigo pode estar em Windows-1252; um arquivo com BOM pode exigir utf-8-sig; um download incompleto pode nem ser texto. Trocar aleatoriamente utf-8 por latin-1 ou usar errors='ignore' faz o erro desaparecer, mas pode alterar nomes, códigos e separadores silenciosamente.

A correção confiável começa pelos bytes, pela origem do arquivo e pela posição informada na exceção. Este guia ensina a reproduzir o problema, inspecionar o trecho inválido, testar candidatos com validação de domínio, lidar com CSV e JSON e registrar uma política de encoding no pipeline.

Transformação entre bytes, decoder, texto Unicode e validação Legenda: decodificar é uma interpretação; a leitura só está correta quando o texto também passa pelas regras do arquivo.

Leia todos os campos da exceção

Uma mensagem comum é:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe7 in position 18:
invalid continuation byte

O codec tentado foi UTF-8, o byte problemático é 0xe7 e a posição é 18. Capture os atributos sem imprimir dados sensíveis:

from pathlib import Path

try:
    texto = Path('clientes.csv').read_text(encoding='utf-8')
except UnicodeDecodeError as erro:
    print('encoding:', erro.encoding)
    print('posição:', erro.start, erro.end)
    print('motivo:', erro.reason)
    print('bytes:', erro.object[erro.start:erro.end].hex())

Os campos delimitam o ponto em que a sequência deixou de ser válida. Isso não prova qual é o encoding correto, mas elimina adivinhação sem contexto.

Entenda bytes, texto e encoding

Um arquivo armazena bytes. Encoding é a regra que associa sequências de bytes a caracteres. A mesma sequência pode formar texto diferente em codificações diferentes, e algumas codificações aceitam qualquer byte. Por isso, “conseguiu abrir” não significa “abriu corretamente”.

No Python, bytes representa dados brutos e str representa texto Unicode:

dados = 'ação'.encode('utf-8')
print(dados)                  # bytes
texto = dados.decode('utf-8')
print(texto)                  # str

O contrato deve existir na fronteira: arquivo recebido, resposta HTTP, banco ou fila. Internamente, trabalhe com str; só codifique ao escrever ou transmitir. Misturar bytes e texto em várias camadas espalha conversões implícitas e dificulta o diagnóstico.

Reproduza o problema de forma controlada

Crie bytes em Windows-1252 e tente lê-los como UTF-8:

from pathlib import Path
import tempfile

with tempfile.TemporaryDirectory() as pasta:
    arquivo = Path(pasta) / 'dados.csv'
    arquivo.write_bytes('nome;cidade\nJoão;Vitória\n'.encode('cp1252'))
    try:
        arquivo.read_text(encoding='utf-8')
    except UnicodeDecodeError as erro:
        print(erro)
    print(arquivo.read_text(encoding='cp1252'))

O laboratório confirma que o código de leitura funciona quando a codificação corresponde aos bytes. Use dados fictícios para evitar expor informações pessoais. Depois compare a origem real: qual programa exportou, em qual sistema e com qual opção de codificação?

Inspecione os bytes ao redor da falha

Leia em modo binário e exiba uma janela pequena:

from pathlib import Path

dados = Path('clientes.csv').read_bytes()
inicio = max(0, 18 - 12)
fim = min(len(dados), 18 + 12)
print(dados[inicio:fim].hex(' '))
print(repr(dados[inicio:fim]))

Em UTF-8, caracteres fora do ASCII usam sequências específicas. Um byte isolado como e7 pode representar ç em cp1252, mas não é uma sequência UTF-8 completa. Já bytes c3 a7 formam ç em UTF-8. Esse padrão é evidência útil.

Não publique o trecho bruto se puder conter nome, documento ou token. Em logs de produção, registre hash do arquivo, tamanho, posição e bytes em hexadecimal limitados.

Verifique BOM antes de adivinhar

Alguns arquivos começam com uma marca de ordem de bytes, BOM. Assinaturas frequentes incluem:

from pathlib import Path

inicio = Path('entrada.txt').read_bytes()[:4]
print(inicio.hex(' '))

Valores comuns:

  • ef bb bf: UTF-8 com BOM;
  • ff fe: UTF-16 little-endian;
  • fe ff: UTF-16 big-endian;
  • ff fe 00 00 ou 00 00 fe ff: variantes UTF-32.

Para UTF-8 com BOM, encoding='utf-8-sig' remove a marca inicial do texto. Em CSV, ignorar o BOM pode produzir uma primeira coluna chamada \ufeffcodigo, causando falhas que parecem erro de cabeçalho.

Use a origem como fonte de verdade

A melhor identificação não vem de heurística, mas do contrato. Confira documentação do sistema exportador, cabeçalho Content-Type, configuração do banco ou acordo de integração. Exemplos:

Content-Type: text/csv; charset=utf-8

ou uma especificação que declare Windows-1252. Mantenha essa informação ao salvar o arquivo. Extensão .csv não define encoding; ela descreve estrutura tabular.

Se o provedor muda formatos sem aviso, guarde amostras fictícias e crie testes de contrato. A ferramenta de inspeção de texto pode revelar caracteres invisíveis depois que o texto foi lido, mas a identificação dos bytes deve ocorrer antes.

Teste candidatos sem aceitar qualquer resultado

Você pode tentar uma lista pequena baseada na origem e validar a estrutura:

from pathlib import Path

def decodificar_csv(caminho):
    dados = Path(caminho).read_bytes()
    candidatos = ('utf-8-sig', 'utf-8', 'cp1252')
    erros = []
    for encoding in candidatos:
        try:
            texto = dados.decode(encoding)
        except UnicodeDecodeError as erro:
            erros.append((encoding, erro.start))
            continue
        primeira = texto.splitlines()[0] if texto else ''
        if primeira == 'codigo;nome;cidade':
            return texto, encoding
    raise ValueError(f'Encoding não reconhecido; tentativas: {erros}')

A validação do cabeçalho impede que uma codificação permissiva seja aceita só porque não lançou exceção. Acrescente número de colunas, campos obrigatórios e caracteres de controle proibidos. Não tente dezenas de encodings aleatórios: quanto mais permissiva a busca, maior a chance de obter texto plausível e errado.

Por que latin-1 quase nunca falha — e pode enganar

ISO-8859-1 associa cada byte a um caractere, então qualquer sequência é decodificável. Isso o torna um escape tentador. Porém, bytes usados para aspas curvas, travessão e símbolo do euro em Windows-1252 ocupam uma faixa de controles em Latin-1. O texto pode conter caracteres estranhos ou invisíveis.

Use cp1252 quando a origem realmente for Windows ocidental e a documentação confirmar. Se o arquivo é UTF-8 corrompido ou contém dados binários, Latin-1 apenas mascara o problema.

Depois da leitura, procure controles inesperados:

import unicodedata

suspeitos = [
    (i, ch, unicodedata.category(ch))
    for i, ch in enumerate(texto)
    if unicodedata.category(ch) == 'Cc' and ch not in '\n\r\t'
]
print(suspeitos[:10])

Não use errors=ignore como padrão

errors='ignore' descarta bytes que não podem ser decodificados. Um nome, código ou valor pode mudar sem alerta. errors='replace' insere o caractere de substituição , preservando a indicação visual de que houve perda, mas ainda não deve alimentar uma importação definitiva sem revisão.

Para diagnóstico controlado:

texto_diagnostico = dados.decode('utf-8', errors='replace')
quantidade = texto_diagnostico.count('\ufffd')
print('substituições:', quantidade)

Use esse resultado para localizar problemas, não para sobrescrever o original. O comportamento strict, padrão do Python, é valioso porque interrompe a ingestão antes que dados parcialmente corrompidos cheguem ao banco.

Leia CSV com newline e encoding explícitos

O módulo csv espera texto, portanto abra o arquivo com a codificação correta e newline='':

import csv

with open('clientes.csv', 'r', encoding='utf-8-sig', newline='') as arquivo:
    leitor = csv.DictReader(arquivo, delimiter=';')
    for numero, linha in enumerate(leitor, start=2):
        if not linha.get('codigo'):
            raise ValueError(f'Código ausente na linha {numero}')
        print(linha['codigo'], linha['nome'])

Encoding e delimitador são problemas independentes. Um arquivo pode ser UTF-8 e usar ponto e vírgula; outro pode ser cp1252 e usar vírgula. A ferramenta de CSV do IATechNerds pode ajudar a conferir colunas em amostras sem dados confidenciais.

Valide também aspas, linhas com quantidades diferentes de campos e cabeçalhos duplicados. Uma leitura sem UnicodeDecodeError é apenas a primeira etapa.

Diferencie UnicodeDecodeError de JSONDecodeError

Ao carregar JSON a partir de arquivo binário ou texto, duas falhas podem ocorrer. A primeira é converter bytes em caracteres; a segunda é interpretar a sintaxe JSON:

import json
from pathlib import Path

dados = Path('config.json').read_bytes()
try:
    texto = dados.decode('utf-8-sig')
except UnicodeDecodeError as erro:
    raise RuntimeError(f'Arquivo não está em UTF-8 perto do byte {erro.start}') from erro

try:
    config = json.loads(texto)
except json.JSONDecodeError as erro:
    raise RuntimeError(f'JSON inválido na linha {erro.lineno}, coluna {erro.colno}') from erro

Separar as etapas produz uma mensagem acionável. Trocar encoding não corrige vírgula extra, comentário ou aspas ausentes. Da mesma forma, corrigir sintaxe não muda bytes inválidos.

Leia arquivos grandes em streaming

read_bytes() carrega tudo na memória. Para arquivos grandes, use o decoder incremental que open oferece:

from pathlib import Path

def processar(caminho, encoding='utf-8'):
    with Path(caminho).open('r', encoding=encoding, errors='strict', newline='') as f:
        for numero, linha in enumerate(f, start=1):
            try:
                validar_linha(linha)
            except ValueError as erro:
                raise ValueError(f'Falha estrutural na linha {numero}') from erro

Um erro de decodificação pode acontecer durante a iteração, não na abertura. Capture-o ao redor do loop. A posição da exceção pode ser relativa ao bloco interno, então registre também contagem de linhas e bytes processados quando o diagnóstico exigir precisão.

Não continue gravando no destino após falha. Use arquivo temporário e só confirme a importação quando todas as linhas passarem.

Converta uma vez e preserve o original

Depois de confirmar a codificação, normalize o arquivo para UTF-8 em um novo destino:

from pathlib import Path

origem = Path('legado.csv')
destino = Path('normalizado.csv')
texto = origem.read_text(encoding='cp1252', errors='strict')
destino.write_text(texto, encoding='utf-8', newline='')

Não sobrescreva o original na primeira tentativa. Compare quantidade de linhas, cabeçalho e valores-chave. Para uma migração auditável, registre hash, tamanho, encoding de entrada, encoding de saída e número de registros:

import hashlib

digest = hashlib.sha256(origem.read_bytes()).hexdigest()
print({'sha256': digest, 'origem': 'cp1252', 'destino': 'utf-8'})

O hash comprova qual arquivo foi processado, mas não valida a semântica do conteúdo.

Estratégia de conversão com original imutável, validação e destino UTF-8 Legenda: preservar a entrada permite repetir a conversão quando a hipótese de encoding ou as regras mudarem.

Mojibake: quando não há exceção, mas o texto está errado

Textos como João e informação indicam frequentemente que bytes UTF-8 foram interpretados como cp1252 ou Latin-1. Pode ocorrer dupla codificação: o texto errado é salvo novamente e passa a parecer válido.

Não aplique substituições globais como ã por ã. Isso cobre poucos caracteres e pode modificar texto legítimo. Volte aos bytes originais e reconstrua a cadeia de conversões. Em um caso controlado de texto UTF-8 lido como cp1252:

errado = 'João'
recuperado = errado.encode('cp1252').decode('utf-8')
print(recuperado)

Só use essa reversão depois de provar a transformação e mantenha o original. Conte quantos registros mudaram e revise amostras com caracteres diversos.

Teste acentos, símbolos e caracteres fora do ASCII

Um teste composto apenas por abc123 passa em muitas codificações. Inclua uma amostra representativa:

amostra = 'Ação; Vitória; R$ 10,00; €; “teste”; 😀'
for encoding in ('utf-8', 'cp1252'):
    try:
        dados = amostra.encode(encoding)
        volta = dados.decode(encoding)
        print(encoding, volta == amostra)
    except UnicodeError as erro:
        print(encoding, type(erro).__name__)

CP1252 não representa todos os caracteres Unicode, como vários emojis. Isso é diferente de decodificar um arquivo cp1252. UnicodeEncodeError acontece na saída; UnicodeDecodeError, na entrada. A direção importa para escolher a correção.

Crie fixtures com nomes, pontuação, moeda e quebra de linha que realmente aparecem no domínio, sempre fictícios.

Defina uma política de encoding no pipeline

Uma política robusta pode estabelecer:

  • APIs JSON usam UTF-8;
  • arquivos CSV recebidos devem declarar encoding e delimitador;
  • UTF-8 com ou sem BOM é tratado explicitamente;
  • formatos legados têm conversor separado e testes próprios;
  • errors='strict' é usado na ingestão oficial;
  • arquivos rejeitados vão para quarentena, sem processamento parcial;
  • logs guardam metadados técnicos, não conteúdo pessoal.

Inclua encoding='utf-8' em cada open relevante em vez de depender do padrão da plataforma. O padrão pode variar entre versões, modo UTF-8 e ambiente. Para entender normalização de texto após a leitura, consulte a ferramenta de Unicode.

Checklist para resolver sem perder dados

Antes de fechar o incidente, confirme:

  1. O original binário foi preservado.
  2. A posição e os bytes problemáticos foram registrados com segurança.
  3. BOM e documentação da origem foram verificados.
  4. Os candidatos de encoding vieram de hipóteses justificadas.
  5. O texto foi validado por cabeçalho, colunas e regras de domínio.
  6. ignore não foi usado na importação definitiva.
  7. CSV, JSON e delimitadores foram tratados na camada correta.
  8. A conversão gerou novo arquivo e métricas comparáveis.
  9. Testes incluem caracteres fora do ASCII.
  10. A política ficou documentada para próximas cargas.

O objetivo não é apenas abrir o arquivo, mas manter o significado de cada caractere e campo. Um pipeline que falha cedo com diagnóstico claro é mais confiável que um pipeline que importa qualquer sequência.

Acrescente uma validação de ida e volta quando o formato permitir: decodifique com a hipótese escolhida, codifique novamente com o mesmo codec e compare os bytes, considerando apenas transformações deliberadas como remoção de BOM. Essa igualdade não prova que a linguagem humana está correta, mas detecta perdas introduzidas pelo processo. Em cargas periódicas, acompanhe quantidade de arquivos rejeitados, encodings observados e linhas importadas; uma mudança súbita pode revelar atualização no sistema de origem. Preserve uma amostra fictícia representativa nos testes e evite depender de arquivos pessoais. Assim, a próxima ocorrência será identificada pelo contrato e pelas métricas, não por tentativa manual em produção.

Documentação primária

Autoria, revisão e método de produção

Autoria editorial: Equipe IATechNerds. Revisão técnica e editorial: pendente, obrigatória antes da publicação. Este conteúdo inédito foi produzido com apoio de IA generativa, documentação oficial do Python e exemplos fictícios reproduzíveis. Não foram usados arquivos reais de clientes. Encodings, validações, links internos e comportamento nas versões de Python suportadas pelo site devem ser conferidos por uma pessoa revisora antes da publicação.

Camada extra: como tomar uma decisão melhor neste cenário

Para levar Python UnicodeDecodeError: como descobrir o encoding sem corromper o arquivo além de uma receita de passos, vale transformar o procedimento em um pequeno método: observar, isolar, alterar uma variável e conferir o resultado.

O **UnicodeDecodeError** ocorre quando o Python tenta transformar bytes em texto usando uma codificação incompatível com o conteúdo. Um CSV exportado por sistema antigo pode estar em Windows-1252; um arquivo com BOM pode exigir `utf-8-sig`; A ideia central pode ser testada com quatro perguntas: o que foi observado, qual hipótese explica o sintoma, qual mudança é reversível e qual evidência prova que funcionou. Esse encadeamento evita que uma coincidência seja confundida com causa e torna o procedimento repetível por outra pessoa.

Integridade vem antes da aparência

Em Python, UnicodeDecodeError, encoding, CSV, o arquivo “abrir” não significa que os dados chegaram intactos. Antes de transformar, separe uma amostra com um registro normal, uma duplicidade, um valor vazio, um código com zero à esquerda e um caso de limite. Essa amostra funciona como teste de regressão: qualquer importação, limpeza ou conversão precisa preservar o que é informação e alterar somente o que é representação.

Compare contagem de linhas, quantidade de chaves únicas, somas de controle quando fizer sentido e tipos esperados. Datas, identificadores e casas decimais merecem atenção especial porque podem parecer visualmente corretos e ainda assim mudar de significado. Guarde o original sem sobrescrever e documente a regra aplicada; isso permite voltar atrás sem depender da memória.

Uma matriz simples para não misturar problemas

CamadaPerguntaValidação
EstruturaColunas e registros continuam presentes?Contagens e cabeçalhos
TipoCódigo, data e número mantiveram o significado?Amostra com casos-limite
ConteúdoHouve perda, truncamento ou duplicação?Comparação antes/depois

Quando automatizar

Automação vale a pena quando a regra já é clara e o teste consegue detectar erro. Se ainda existem exceções não descritas, automatizar apenas faz o erro chegar mais rápido a mais linhas. Comece com uma amostra pequena, registre a saída esperada e só então escale para o conjunto inteiro.

Checklist de fechamento

  • Registre o estado inicial antes de alterar qualquer coisa.
  • Faça uma mudança por rodada e anote o efeito.
  • Teste um caso normal, um caso-limite e o cenário que originalmente falhou.
  • Confirme que a solução continua válida depois de reiniciar, reabrir ou repetir o fluxo.
  • Guarde uma forma de voltar ao estado anterior quando a alteração for destrutiva.

Esse método acrescenta profundidade sem transformar o artigo em teoria abstrata: o leitor entende por que cada passo existe e como reconhecer quando a situação exige uma decisão diferente.

#Python #UnicodeDecodeError #encoding #CSV #dados