Deduzir qual é o “mesmo cliente” numa lista de 10 mil fornecedores é um problema de resolução de entidades — e a lição mais contraintuitiva desse trabalho é que a parte difícil nunca foi comparar strings. É decidir o que fazer com o score. Neste guia, você vai ver como uma pipeline em Python limpa a bagunça de uma década de cadastro manual e por que dois estágios “entediantes” e determinísticos removem 76% das duplicatas antes de qualquer similaridade fuzzy entrar em cena.
Por que isso importa agora
Em 2026, equipes de dados herdam catálogos que misturam uma década de digitação manual, planilhas enviadas por e-mail e sincronizações de API. O mesmo site aparece como solartravelmag.com, https://www.solartravelmag.com/, blog.solartravelmag.com e, em dia ruim, SolarTravelMag.COM?utm=.... Um registro duplicado é como você paga a mesma fatura duas vezes — e registros divididos significam histórico de preço fragmentado. O problema tem teoria formal desde Fellegi e Sunter (1969), mas os tutoriais raramente lideram com o essencial: a comparação de strings é a parte fácil; decidir o destino do score é o trabalho.
Prós e contras da abordagem determinística
✅ Gratuito e rápido — normalização e colapso de subdomínios rodam em milissegundos, sem modelo e sem custo de inferência.
✅ Auditável — cada merge tem uma regra explícita; não há “caixa-preta” para explicar a um auditor.
✅ Precisão garantida — onde a regra dispara, ela está certa por construção.
✅ Reprodutível — os mesmos dados produzem exatamente o mesmo resultado.
✅ Reduz a carga humana — sobra um quarto do problema para revisão, em vez de tudo.
⚠️ Não resolve tudo — o quarto restante (typos de uma tecla e domínios de país) exige comparação fuzzy e julgamento humano.
⚠️ Exige disciplina de domínio — você precisa respeitar a Public Suffix List, ou transforma co.uk no seu maior fornecedor.
⚠️ Não há threshold mágico — nenhum ponto de corte merge com segurança no resíduo que sobra.
Tabela de requisitos
| Componente | Mínimo | Recomendado | Ideal |
|---|---|---|---|
| Python | 3.10 | 3.12 | 3.12 |
| Hardware | 1 vCPU, 2 GB | 2 vCPU, 4 GB | 4+ vCPU |
| Bibliotecas | rapidfuzz, tldextract | + numpy | + pandas para revisão |
| Conhecimento | Python básico | Manipulação de strings | Noções de deduplicação |
| Tempo estimado | 2 h | 1 h | 30 min + revisão |
Passo a passo
1. Normalize com regras deliberadamente burras
O primeiro estágio não usa modelo nem score: minúsculas, remover o esquema (https://), cortar caminho e query string e descartar o www. inicial. Numa lista de 11.531 linhas, isso derruba para 9.080 hosts únicos em 0,03 segundos — 2.451 duplicatas eliminadas sem nenhum julgamento.
import re
def norm_host(raw: str) -> str:
s = raw.strip().lower()
s = re.sub(r"^[a-z]+://", "", s)
s = s.split("/")[0].split("?")[0]
if s.startswith("www."):
s = s[4:]
return s.rstrip(".")
O porquê de cada linha: remover o esquema e o www. elimina variações de superfície; cortar caminho e query mata parâmetros de rastreamento. É “burro” de propósito porque regras óbvias são fáceis de auditar.
2. Colapse subdomínios com a Public Suffix List
O domínio registrado de blog.solartravelmag.com é solartravelmag.com — mas o de solartravelmag.co.uk não é co.uk. Quem divide por pontos se machuca aqui. A única forma correta é usar a Public Suffix List, o catálogo mantido de todos os sufixos sob os quais o público pode registrar nomes. A biblioteca tldextract empacota isso:
import tldextract
# fixar o snapshot do sufixo embutido garante reprodutibilidade
extract = tldextract.TLDExtract(suffix_list_urls=())
registered = {}
for host, row in hosts.items():
ext = extract(host)
reg = ext.domain + "." + ext.suffix if ext.suffix else host
registered.setdefault(reg, row)
Outros 873 hosts colapsam, deixando 8.207 domínios registrados. Dois estágios determinísticos, zero decisões subjetivas — e 3.324 das 4.351 linhas duplicadas já se foram. Guarde esse número: 76% do problema nunca precisou de score de similaridade.
3. Use força bruta (sim, é rápido o suficiente)
8.207 domínios formam 33,7 milhões de pares possíveis. Parece hora de indexação esperta — não é, pelo menos não por velocidade. O RapidFuzz calcula a matriz completa em uma chamada:
import numpy as np
from rapidfuzz import fuzz, process
M = process.cdist(labels, labels, scorer=fuzz.ratio,
dtype=np.uint8, workers=-1)
Dois segundos, 67 MB. Nesse tamanho, força bruta é um gole de café.
4. Aplique blocking por duas razões que não são velocidade
Blocking importa, primeiro, por escala: com 100 mil fornecedores a mesma matriz vira 10 bilhões de células e 10 GB. Segundo, e subestimado: uma matriz de scores não é uma lista de tarefas. Você quer pares candidatos que possa ranquear e rotear. Bloqueie comparando só domínios que compartilham uma chave:
from itertools import combinations
blocks = {}
for reg in domains:
label = label_of[reg] # a parte à esquerda do sufixo
for key in ("P" + label[:2], "S" + label[-3:]):
blocks.setdefault(key, set()).add(reg)
pairs = set()
for members in blocks.values():
pairs.update(combinations(sorted(members), 2))
Meça o preço do blocking em vez de torcer. Só a chave de prefixo (2 letras) captura 93,0% dos pares verdadeiros; adicionar a chave de sufixo (3 letras finais) sobe para 99,8%, ao custo de crescer o conjunto de candidatos de 864,6 mil para 1,69 milhão de pares. Sem ground truth, estime o recall do blocking numa amostra rotulada antes de confiar.
5. Entenda os “dois morros” que não se separam
Abaixo de score 80 não há nenhuma duplicata verdadeira; acima de 97 também é limpo — com uma ressalva enorme. A faixa entre 88 e 97 é uma mistura genuína, e por razão estrutural: um typo de uma tecla num domínio de 14 caracteres marca baixos 90, e um fornecedor diferente cujo nome está a uma edição do seu também. A string não distingue as duas situações, porque são o mesmo evento de string com donos diferentes por trás.
6. Descubra que nenhum threshold merge com segurança
Auto-merge em 85 tem precisão de 0,345 — dois em cada três merges estão errados. Em 90, precisão 0,764 (um em quatro errado). Em 95, precisão 0,895 e recall já caiu para 0,738. Só em 98 a precisão chega a 1,0, com recall de 0,485. E a ressalva: todos os 551 pares nessa zona perfeita são o mesmo nome sob domínios de país diferentes (brand.com vs brand.de). O matcher tem certeza do texto, mas saber se brand.de é o braço alemão do mesmo publisher ou uma empresa não relacionada exige conhecimento de negócio. Esses pares vão para um humano, por regra.
Tabela comparativa: precisão × recall por threshold
| Threshold | Precisão | Recall | Resultado |
|---|---|---|---|
| 85 | 0,345 | — | 2 em 3 merges errados |
| 90 | 0,764 | — | 1 em 4 errado |
| 95 | 0,895 | 0,738 | recall em queda |
| 98 | 1,0 | 0,485 | só domínios de país |
Integração com o ecossistema
Instale as duas dependências essenciais e você tem a pipeline inteira:
pip install rapidfuzz tldextract numpy
Para a fila de revisão, exporte o ranking para CSV e ordene por valor financeiro — pares que envolvem colocações caras recebem olhos primeiro, e a cauda longa pode esperar uma sexta-feira calma:
import csv
with open("review_queue.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["a", "b", "score", "valor"])
for a, b, s in sorted(queue, key=lambda t: -t[2]):
w.writerow([a, b, s, valor_de(a) + valor_de(b)])
Casos de uso reais
• Contas a pagar — deduplicar fornecedores evita pagar a mesma fatura duas vezes e unifica histórico de preço negociado.
• CRM — mesclar contas de clientes que chegaram por formulários, compras e suporte, sem fundir pessoas diferentes.
• Programas de afiliados — parceiros que cadastram o mesmo site com grafias variadas.
• Marketplaces — cruzar preços entre fontes só funciona se você sabe que duas linhas descrevem o mesmo produto.
• Compliance e LGPD — registros divididos dificultam atender a solicitações de titulares e auditorias.
Comparação de custo
| Abordagem | Custo | Tempo | Quando usar |
|---|---|---|---|
| Local (RapidFuzz) | R$ 0 | segundos | listas até ~50 mil |
| Deduplicação cloud (API) | por chamada | minutos | grandes volumes contínuos |
| Manual puro | horas de equipe | dias | nunca, se der para automatizar |
Troubleshooting: erros comuns
❌ “Meu script une co.uk como fornecedor” → Causa: você dividiu o domínio por pontos em vez de usar a Public Suffix List. Solução: use tldextract e fixe o snapshot (suffix_list_urls=()).
❌ Matriz estoura a memória em listas grandes → Causa: força bruta sem blocking em 100 mil+ entidades. Solução: bloqueie por prefixo + sufixo antes de computar pares.
❌ Auto-merge em 90 funde fornecedores diferentes → Causa: um em cada quatro merges nesse threshold está errado. Solução: trate o score como fila de revisão, não como merge automático.
❌ Resultados mudam a cada execução → Causa: o tldextract baixa uma nova lista de sufixos. Solução: pin o snapshot com suffix_list_urls=().
❌ Typo no início do domínio escapa do blocking → Causa: chave de prefixo de 2 letras perde erros nas primeiras letras. Solução: adicione a chave de sufixo (3 últimas letras) para subir o recall a 99,8%.
❌ Merge irreversível contaminou o histórico de preço → Causa: sobrescreveu a linha original. Solução: mantenha as duas linhas-fonte, escreva um log de merge e nunca sobrescreva.
FAQ
Preciso de machine learning para isso? Não. Dois estágios determinísticos resolvem 76% do problema; o restante é fuzzy matching simples (RapidFuzz) + revisão humana.
Qual threshold devo usar? Nenhum, para merge automático. Use o score para ordenar uma fila de revisão e escolha o ponto de corte pelo tempo que sua equipe tem.
Por que não usar apenas similaridade de strings? Porque typos de uma tecla e nomes reais “vizinhos” produzem o mesmo score. A string não sabe quem é o dono.
O que é a Public Suffix List? O catálogo oficial de sufixos (como .co.uk) sob os quais o público registra domínios. É a forma correta de descobrir o domínio registrado.
Quando o blocking é obrigatório? A partir de ~50-100 mil entidades, quando a matriz completa fica grande demais para a memória.
Posso automatizar 100%? Em domínios, não com segurança — pares como brand.com vs brand.de exigem conhecimento de negócio.
Para onde isso vai
Com modelos de linguagem cada vez mais usados para “limpeza de dados”, a tentação será delegar a resolução de entidades a um LLM. Mas a lição do artigo vale para 2027 também: onde uma regra determinística resolve, ela é mais barata, mais rápida e mais auditável do que qualquer modelo. O futuro provável é híbrido — estágios determinísticos fazem o trabalho pesado, e um LLM assiste o humano na fila de revisão sugerindo contextos (e-mail, dados bancários) que transformam um 91 em certeza em dois segundos de atenção.
Descubra mais sobre noticiAI
Assine para receber nossas notícias mais recentes por e-mail.



