Separadores invisíveis são caracteres que existem no arquivo mas não aparecem na tela: espaço não separável, espaço fino, hífen suave, marca de ordem de bytes (BOM), caracteres de largura zero e tabulações escondidas. Eles quebram a divisão de colunas, a contagem de palavras e a busca exata no momento em que o arquivo entra num pipeline de IA, porque um campo que parece igual a outro nunca é igual byte a byte.
Antes de enviar texto para uma IA, normalize o arquivo: remova o BOM, troque espaços não separáveis por espaço comum, elimine caracteres de largura zero e aplique uma normalização Unicode adequada (NFC para texto geral, NFKC para identificadores). Este guia mostra quais são esses caracteres, por que aparecem, o que estragam e como detectar e limpar.
Resumo rápido
- Espaço não separável (U+00A0) parece um espaço comum, mas é um caractere diferente, e nenhuma comparação exata o considera igual.
- O BOM (U+FEFF) costuma aparecer no início de arquivos exportados e pode virar um “primeiro caractere” que estraga a primeira linha ou o primeiro cabeçalho.
- Caracteres de largura zero (U+200B, U+200C, U+200D) e o hífen suave (U+00AD) não têm largura visível e sobrevivem a cópias de páginas e de PDFs.
- Normalização Unicode resolve diferenças de representação: NFC para texto geral, NFKC para identificadores e comparações flexíveis.
- Detecte com um editor que mostra invisíveis ou com hexdump, e limpe na entrada do pipeline, não no meio dele.
- Limpe uma vez, na ingestão, e teste com buscas exatas por códigos de produto e por nomes com acento.
Quais são esses caracteres e por que eles existem
A maioria desses caracteres não é erro: eles resolvem problemas reais de tipografia e de internacionalização. O problema aparece quando o arquivo passa por um pipeline que trata texto como dados estruturados.
| Caractere | Código Unicode | Para que serve | O que estraga |
|---|---|---|---|
| Espaço não separável | U+00A0 | Impedir que duas palavras se separem em duas linhas | Divisão por espaço, contagem de palavras, busca exata |
| Espaço fino | U+2009 | Espaçamento tipográfico mais estreito | Divisão por espaço, alinhamento de colunas |
| Espaço estreito sem quebra | U+202F | Espaço estreito e não separável | Comparação de strings, campos delimitados |
| Hífen suave | U+00AD | Marcar onde uma palavra pode ser quebrada | Busca exata, extração de nomes e códigos |
| Marca de ordem de bytes (BOM) | U+FEFF | Sinalizar codificação UTF-8/UTF-16 no início do arquivo | Primeiro cabeçalho, chave da primeira linha JSON |
| Espaço de largura zero | U+200B | Permitir quebra de linha sem adicionar espaço visível | Busca exata, comparação de identificadores |
| Não união de largura zero | U+200C | Controle de ligadura e escrita em certos idiomas | Contagem de caracteres, busca exata |
| União de largura zero | U+200D | Combinar emoji e ligaduras | Contagem de caracteres, igualdade de sequências |
| Tabulação | U+0009 | Alinhar colunas em texto puro | Campos separados, se o divisor esperado for outro |
Esses códigos pertencem a blocos diferentes. O espaço fino, o espaço estreito sem quebra e os caracteres de largura zero estão na faixa U+2000 em diante, cujo mapa oficial está no quadro de caracteres de pontuação geral Unicode (PDF oficial). O papel do BOM e das codificações está descrito nas diretrizes do W3C sobre uso do BOM em UTF-8.
Quando esses caracteres aparecem nos seus arquivos
Eles entram no arquivo por caminhos previsíveis:
- Exportação de editores de texto e de planilhas. Muitos exportadores gravam CSV com BOM para que o Excel reconheça o UTF-8. O resultado é um
\ufeffinvisível colado no primeiro cabeçalho da planilha. - Cópia de páginas web. Páginas usam espaço não separável e caracteres de largura zero para controlar quebra de linha. Copiar de um site traz esses caracteres junto.
- Cópia de PDF. A extração de PDF produz marcadores de posição, hífen suave de hifenização e espaços não separáveis que estavam na diagramação.
- Campos de formulário e sites de terceiros. Um catálogo copiado de outro site carrega o espaço não separável que separava número de unidade naquela página.
- Geração automática de relatórios. Ferramentas que formatam números com espaço fino entre grupos mantêm esse caractere no texto exportado.
O que eles estragam num pipeline de IA
Chunking
Quando um sistema dorado de IA divide um documento em pedaços, a divisão costuma se apoiar em pontuação e quebras de linha. Um espaço não separável no lugar de uma quebra pode manter junta uma unidade que deveria ser separada, ou separar o que deveria ficar junto. O efeito é sutil e aparece como trechos que começam ou terminam no meio de um valor, como destacado no guia de embeddings e o que eles representam.
Campos separados
Se o divisor esperado é a tabulação e o arquivo tem espaços não separáveis, as colunas grudadam. Se o divisor é a vírgula e o campo tem vírgula dentro, o mesmo acontece. É por isso que um CSV exportado de planilha costuma abrir com a primeira coluna “suja”: ela contém o BOM antes do primeiro cabeçalho.
Comparação de strings
Comparações exatas (==) usam a representação binária. Um “10 000” com espaço não separável nunca é igual a “10 000” com espaço comum, mesmo que pareçam idênticos na tela. O mesmo vale para nomes com acento representados de formas diferentes: a letra “é” pode ser um único caractere ou uma sequência de “e” mais acento combinante, e as duas codificações não são iguais byte a byte sem normalização.
Extração de valores
Um agente que extrai preços ou códigos de um campo espera um padrão numérico ou alfanumérico. Um espaço não separável no meio do número, ou um BOM na frente do primeiro valor, faz o padrão falhar e o valor sair vazio ou errado, sem erro visível.
Busca exata
Buscas por código de produto, número de nota ou identificador exigem igualdade exata. Qualquer invisível escondido no meio reprova a busca, e a causa não aparece na tela. É o caso mais comum de falha em pipeline de catálogo, como mostra o exemplo mais adiante.
Como detectar os invisíveis
Você tem quatro ferramentas práticas. Escolha a que combina com o volume do problema.
- Editor com visualização de caracteres. Editores de código oferecem renderizar espaços, tabulações e controles. É suficiente para inspecionar uma linha suspeita.
- hexdump. Mostrar os bytes revela a codificação real:
xxd arquivo.txt | headmostra, por exemplo, umefbbbfno início, que é o BOM em UTF-8. - Script de varredura. Um trecho curto de código percorre o arquivo e lista posições de caracteres especiais. É o caminho para milhares de arquivos.
- Filtro por código Unicode. Comparar cada caractere com uma lista de códigos permitidos, por exemplo, tudo que for letra, número, espaço comum, tabulação e pontuação básica, e apontar o que estiver fora.
Em Python, o módulo unicodedata da biblioteca padrão expõe o nome de cada caractere, o que ajuda a identificar o que apareceu; os detalhes estão na documentação oficial do módulo unicodedata. Uma varredura simples fica assim:
import unicodedata
SUSPEITOS = {"\u00a0", "\u2009", "\u202f", "\u00ad", "\u200b", "\u200c", "\u200d", "\ufeff"}
def varrer(caminho):
with open(caminho, "r", encoding="utf-8", errors="replace") as f:
for numero, linha in enumerate(f, start=1):
for coluna, ch in enumerate(linha, start=1):
if ch in SUSPEITOS or (not ch.isprintable() and ch != "\t"):
nome = unicodedata.name(ch, "SEM NOME")
print(f"linha {numero}, coluna {coluna}: U+{ord(ch):04X} {nome}")
varrer("catalogo.csv")
O script lê o arquivo como UTF-8, percorre caractere a caractere e imprime a posição, o código e o nome de cada invisível encontrado. unicodedata.name devolve o nome oficial do caractere e aceita um valor padrão quando o caractere não tem nome. O teste isprintable pega os controles que não passam pela lista, mantendo a tabulação de fora.
Como normalizar e limpar
A limpeza tem quatro etapas, na ordem: remover o BOM, trocar espaços não separáveis por espaço comum, remover caracteres de largura zero e aplicar a forma de normalização Unicode que faz sentido. As duas últimas dependem de julgamento, e é aqui que a normalização Unicode entra.
A normalização é um processo formal que escolhe uma representação única entre sequências de caracteres equivalentes. A norma UAX #15, Unicode Normalization Forms define quatro formas. As duas usadas no dia a dia são NFC e NFKC. Na NFC, o texto é representado na forma pré-composta, em que a maioria dos acentos vira um único caractere. Na NFKC, além disso, aplicam-se decomposições de compatibilidade, que depois são compostas. A recomendação prática, que aparece no FAQ oficial de normalização da Unicode, é usar NFC para texto geral e NFKC para identificadores e comparações flexíveis. A diferença entre equivalência canônica e de compatibilidade está no próprio UAX #15: a canônica diz que duas representações são o mesmo caractere; a de compatibilidade é mais fraca, diz que representam o mesmo conceito, mas podem ter aparência ou comportamento diferentes.
Atenção a um ponto: a NFKC “abre” distinções que em alguns contextos são propositais. Por isso ela serve para identificadores e busca, mas não deve ser aplicada a texto que precisa preservar detalhes tipográficos. A limpeza completa em Python fica assim:
import unicodedata
def normalizar(texto, forma="NFC"):
if texto.startswith("\ufeff"):
texto = texto[1:]
texto = texto.replace("\u00a0", " ").replace("\u2009", " ").replace("\u202f", " ")
for invisivel in ("\u200b", "\u200c", "\u200d", "\u00ad"):
texto = texto.replace(invisivel, "")
texto = texto.replace("\r\n", "\n").replace("\r", "\n")
return unicodedata.normalize(forma, texto)
with open("catalogo.csv", "r", encoding="utf-8") as entrada:
conteudo = normalizar(entrada.read(), forma="NFKC")
with open("catalogo_limpo.csv", "w", encoding="utf-8") as saida:
saida.write(conteudo)
unicodedata.normalize(forma, texto) aplica a forma pedida e é idempotente, aplicar de novo não muda o resultado. A escolha entre NFC e NFKC é sua: use NFC quando o texto será exibido, NFKC quando será comparado como identificador. Se a limpeza envolve milhares de arquivos, rode a varredura primeiro para saber o que existe e limpe só depois. Em bash, uma verificação rápida de BOM usa file ou xxd para olhar os primeiros bytes:
# mostra se o arquivo começa com BOM (efbbbf em UTF-8)
head -c 3 catalogo.csv | xxd
# remove BOM do começo de um arquivo, preservando o resto
sed -i '1s/^\xEF\xBB\xBF//' catalogo.csv
O primeiro comando mostra os três primeiros bytes, e efbbbf confirma a presença do BOM. O segundo remove apenas se ele estiver na primeira linha, que é a forma comum. Para espaços não separáveis e caracteres de largura zero em lote, o Python costuma ser mais claro e mais seguro do que sed, porque trabalha com a string desmontada em caracteres, não com bytes, como lembra a introdução da documentação do Python ao Unicode.
Exemplo: o código de produto que não era encontrado
Uma equipe de gestão de catálogo em Belo Horizonte exportou a lista de produtos de um portal de fornecedor e montou uma busca por código de produto para um assistente interno. O código tinha o formato de uma sigla seguida de número, e na tela do portal os códigos apareciam corretos, separados por espaço. A busca no sistema da equipe falhava para uma parte considerável dos itens e não havia erro: o assistente simplesmente respondia que não encontrou aquele código.
A causa estava no separador. O portal do fornecedor usava um espaço não separável entre a sigla e o número, para não deixar o código quebrar de linha em telas estreitas. Ao copiar e exportar, esse caractere veio junto. Na tela do editor da equipe ele parecia um espaço comum, então ninguém suspeitou. Mas a busca por igualdade exata comparava o código digitado, com espaço comum, com o código do catálogo, com espaço não separável, e os dois nunca batiam. O mesmo problema aparecia na contagem de caracteres do código e em qualquer divisão por espaço que tentasse separar sigla e número.
A correção foi inserir uma etapa de limpeza entre a exportação do fornecedor e a carga no sistema: remover o BOM do cabeçalho do CSV, trocar os espaços não separáveis por espaço comum e aplicar NFKC ao campo de código, que funciona como identificador. A busca passou a encontrar os códigos. O ganho não veio de trocar o modelo nem de reescrever prompts: veio de garantir que o texto que entra no pipeline é o texto que a comparação espera. A equipe passou a rodar a varredura de caracteres suspeitos como conferência antes de cada carga e a manter o resultado registrado, ligando a lição à rotina de conferência de respostas da IA.
Erros comuns
- Limpar dentro do pipeline. Fazer a troca no meio do processo, e não na entrada, deixa passar os invisíveis que entram pelas etapas intermediárias.
- Aplicar NFKC em tudo. A forma é apropriada para identificadores e busca, mas mexe em distinções de compatibilidade que podem ter significado em outros textos.
- Confundir “parece igual” com “é igual”. Dois textos visualmente idênticos podem ter bytes diferentes e falhar em qualquer comparação exata.
- Remover todo espaço. Trocar espaços não separáveis por vazio junta palavras que deveriam estar separadas. Em campos separados, use o separador correto, não a remoção.
- Esquecer o BOM. É o invisível mais frequente em CSV exportado e o que mais estraga o primeiro cabeçalho ou a primeira chave de um JSON.
- Limpar depois de indexar. Se os vetores foram gerados com o texto sujo, limpar o original não corrige o índice. Limpe antes de gerar embeddings e reindexe quando o texto muda.
- Não registrar a limpeza. Sem saber o que foi alterado, uma divergência futura entre original e processado fica sem explicação.
Perguntas frequentes
Qual a diferença entre NFC e NFKC?
NFC usa equivalência canônica: representa o mesmo caractere de uma forma única, geralmente pré-composta, sem mexer em caracteres que só são equivalentes por compatibilidade. NFKC aplica também as decomposições de compatibilidade, o que aproxima formas que se parecem, por exemplo, letras de largura completa e largura normal. A recomendação oficial é NFC para texto geral e NFKC para identificadores.
Preciso normalizar antes de gerar embeddings?
Sim, se o mesmo texto pode aparecer em codificações diferentes. O modelo de embeddings recebe o texto como está; duas codificações distintas do mesmo conteúdo tendem a gerar vetores parecidos, mas não iguais, e a diferença atrapalha buscas por igualdade e comparações exatas. Normalize antes de indexar e mantenha a mesma forma em consulta.
Remover o espaço não separável pode quebrar alguma coisa?
Só a aparência de quebra de linha naquele ponto, o que raramente importa em dados processados por IA. O ganho em comparação e busca compensa. Se precisar exibir o texto depois, o layout pode ser refeito na apresentação.
Como sei qual forma de normalização o sistema de destino espera?
Confira a documentação do sistema ou do fornecedor. Quando não há indicação, NFC é a escolha segura para texto geral. Para campos que funcionam como identificador, NFKC costuma ser a escolha certa, exatamente como recomenda o FAQ oficial da Unicode.
Dá para fazer essa limpeza sem programar?
Para poucos arquivos, um editor que mostra caracteres invisíveis e permite localizar e substituir resolve. Para catálogos e bases com muitos arquivos, um script de varredura e limpeza é mais confiável e reprodutível, e evita depender de quem lembra de conferir na mão.
Isso tem relação com a acentuação do português?
Tem. Nomes com acento podem vir como caractere único ou como letra mais acento combinante. As duas formas são canonicamente equivalentes, mas não são iguais byte a byte. Normalizar coloca tudo numa forma só.