Como Duplicar Dados Extraídos Com Resolução de Entidades
Web Data Collection Specialist
TL;DR:
- Quatro páginas de listagem retornaram 71 registros que resolveram para 66 entidades, com 5 grupos de duplicatas genuínas — os mesmos livros aparecendo tanto em uma listagem de categoria quanto no catálogo paginado.
- Normalizar antes de comparar é o que faz uma chave exata funcionar: a normalização de casos, a normalização Unicode e a remoção de pontuação transformam três representações de um título em uma chave.
- O bloqueio é mensurável, não vago. 66 entidades representam 2.145 comparações em pares; uma chave de bloqueio de quatro caracteres reduz isso para 154 em 48 blocos, uma redução de 92,8%.
- Escolha o escore antes do limite. O mesmo par de títulos teve um escore de 87,8 em
ratioe 100,0 emtoken_set_ratio. - A faixa de limite é mensurável em seus próprios dados: títulos ao vivo não relacionados atingiram o pico de 63,4, enquanto três representações de um livro marcaram 93,5 e acima.
- Em um único catálogo limpo, chaves exatas capturaram todas as duplicatas e a correspondência imprecisa não encontrou nada acima de 90 — a correspondência imprecisa ganha seu lugar entre fontes, não dentro de uma única.
- Colete os registros de múltiplas fontes com os quais isso se reconcilia com o plano gratuito Scrapeless.
Raspar um site e duplicatas são raras. Raspar o mesmo catálogo através de uma listagem de categoria e um índice paginado, ou os mesmos produtos em dois varejistas, e as duplicatas aparecem por construção — o rastreamento visitou o mesmo item por duas rotas e não tinha como saber.
A resolução de entidades é a etapa que transforma esses registros de volta em coisas. Ela acontece após a extração e antes do armazenamento, e é principalmente uma sequência de decisões baratas: o que conta como a mesma string, o que conta como o mesmo registro e qual versão sobrevive.
Cada número abaixo vem de uma coleção de 71 registros coletados de quatro páginas de listagem ao vivo.
Pipeline em um Relance
| Etapa | Pergunta | Mecanismo | Resultado medido |
|---|---|---|---|
| Normalizar | Esta é a mesma string? | conversão de caso, NFKD, remoção de pontuação | 66 chaves distintas de 71 registros |
| Chave exata | Este é o mesmo registro? | agrupar por chave normalizada | 5 grupos de duplicatas, 10 registros em 5 |
| Bloquear | Quais pares valem a pena comparar? | prefixo de chave de 4 caracteres | 2.145 pares para 154, um corte de 92,8% |
| Fuzzy | Esta é a mesma coisa, escrita de forma diferente? | token_set_ratio |
93,5–100 em correspondências verdadeiras, 63,4 de teto em não relacionados |
| Sobrevivência | Qual registro vence? | regras de campo, manter a proveniência | uma entidade com seen_in e preços observados |
O fluxo é normalizar → chave exata → bloquear → comparar fuzzy → mesclar. Cada etapa é mais barata que a seguinte, então cada uma existe para reduzir o trabalho que a próxima tem que fazer.
Etapa 1: Normalizar Antes de Comparar
Dois registros descrevendo o mesmo produto raramente possuem strings idênticas em bytes. Caso, acentos e pontuação mudam.
python
import re
import unicodedata
def norm(text):
text = unicodedata.normalize("NFKD", text or "").casefold()
text = re.sub(r"[^a-z0-9 ]+", " ", text)
return re.sub(r"\s+", " ", text).strip()
A passagem NFKD importa mais do que parece. O anexo de normalização Unicode define várias formas, e uma decomposição de compatibilidade é o que faz uma é pré-composta e uma e simples mais acento combinante serem consideradas iguais — as duas grafias são visualmente idênticas e diferentes em bytes, que é exatamente o caso que produz uma duplicata que ninguém pode ver na saída.
A conversão de caso em vez de minúsculas é o correspondente de correspondência, e a nota do W3C sobre normalização de caracteres é a referência para o motivo pelo qual as duas diferem para texto não-ASCII.
Nos registros coletados:
text
[1] collected 71 records from 4 listing pages
raw distinct titles 66
normalised distinct titles 66
Idêntico aqui, porque este catálogo está limpo. Isso é importante saber em vez de assumir — executar a comparação informa se a normalização está fazendo algum trabalho em seus dados antes de você construir algo sobre isso.
Etapa 2: Agrupar por uma Chave Exata
Com uma chave normalizada, a primeira passagem é uma agrupamento, não uma comparação. É O(n) e captura cada duplicata que concorda exatamente.
python
from collections import defaultdict
by_key = defaultdict(list)
for record in records:
by_key[norm(record["title"])].append(record)
dupe_groups = {k: v for k, v in by_key.items() if len(v) > 1}
text
[2] exact-key duplicates: 5 group(s), 10 records collapse to 5
Sharp Objects x2 ['mystery', 'catalogue1']
In a Dark, Dark Wood x2 ['mystery', 'catalogue2']
In Her Wake x2 ['catalogue2', 'thriller']
The Elephant Tree x2 ['catalogue2', 'thriller']
Behind Closed Doors x2 ['catalogue2', 'thriller']
Observe de onde vêm as duplicatas: cada grupo abrange duas páginas de listagem diferentes. Nenhuma página única continha uma duplicata. Essa é a forma geral — as duplicatas são uma propriedade do rastreamento, não da página, então um scraper que lê apenas uma listagem não as verá e um scraper que lê quatro verá.
Use um identificador estável como a chave sempre que a página publicar um. Um ID de produto, um ISBN ou um caminho de URL canônico supera um título, porque títulos são textos de marketing e mudam sem que o produto mude. Sistemas de identificadores publicados existem precisamente para que partes independentes possam concordar sobre a identidade — a especificação do namespace ISBN URN é o exemplo do mundo dos livros, e uma página raspada que expõe um já resolveu o problema de correspondência para você.
Etapa 3: Bloquear Antes de Comparar Pares
A comparação difusa é par a par, e par a par é quadrática. Para 66 entidades, isso significa 2.145 comparações; para 10.000, são pouco menos de 50 milhões.
O bloqueio reduz o campo, comparando apenas registros que já compartilham algo barato:
python
blocks = defaultdict(list)
for entity in merged:
blocks[norm(entity["title"])[:4]].append(entity)
blocked_pairs = sum(len(b) * (len(b) - 1) // 2 for b in blocks.values())
text
[4] 66 entities
all-pairs comparisons 2145
blocked on 4-char key 154 across 48 blocks
reduction 92.8%
A troca é explícita: um registro cujo título começa de maneira diferente nunca é comparado, então uma chave de bloqueio que é muito agressiva oculta correspondências reais. Bloquear com base nos quatro primeiros caracteres perde um par como The Elephant Tree contra Elephant Tree porque o artigo mudou. Respostas comuns são bloquear com base em um prefixo de token ordenado, em um identificador numérico ou em várias chaves de uma vez e tomar a união dos pares candidatos.
Fase 4: Correspondência Difusa, e Quando Não É Necessária
Executar a passagem difusa sobre este catálogo produziu um resultado que vale a pena relatar honestamente:
text
[5] fuzzy near-duplicates above 90 (token_sort_ratio)
brute force 2145 pairs in 2.5 ms -> 0 candidate(s)
Nada. Após normalização e agrupamento exato, um catálogo limpo não tinha quase duplicatas restantes. Uma passagem difusa aqui seria um código que nunca é acionado.
A correspondência difusa ganha seu lugar quando registros chegam de fontes que formatam títulos de maneira diferente. Tomando um título real e renderizando-o da forma como três listagens diferentes o apresentariam:
python
from rapidfuzz import fuzz
VARIANTS = [
"A Study in Scarlet (Sherlock Holmes #1)",
"A Study In Scarlet - Sherlock Holmes Book 1",
"A Study in Scarlet, Sherlock Holmes #1 [Paperback]",
]
keys = [norm(v) for v in VARIANTS]
print("distinct exact keys:", len(set(keys)))
for i in range(len(keys)):
for j in range(i + 1, len(keys)):
print(f"ratio {fuzz.ratio(keys[i], keys[j]):5.1f} | "
f"token_sort {fuzz.token_sort_ratio(keys[i], keys[j]):5.1f} | "
f"token_set {fuzz.token_set_ratio(keys[i], keys[j]):5.1f}")
text
distinct exact keys: 3
ratio 93.5 | token_sort 93.5 | token_set 100.0
ratio 87.8 | token_sort 87.8 | token_set 100.0
ratio 85.1 | token_sort 82.8 | token_set 93.5
Três chaves para um livro — a fase de chave exata não pode ajudar aqui. E o escore altera a resposta mais do que o limiar. ratio compara as strings como sequências e é arrastado para baixo pelo sufixo [Paperback]; token_set_ratio compara os conjuntos de tokens, portanto, palavras extras não custam nada e as duas primeiras variantes têm um escore limpo de 100.
Reconciliação de registros de várias fontes? O plano gratuito Scrapeless cobre solicitações suficientes para coletar o segundo catálogo que faz as duplicatas aparecerem.
Escolhendo o Limiar a partir de Seus Próprios Dados
Um limiar só é defensável contra separação medida. Dois números delimitam aqui:
| Medição | Escore |
|---|---|
Maior token_sort_ratio entre dois títulos vivos genuinamente diferentes |
63.4 |
Menor token_set_ratio entre três renderizações de um livro |
93.5 |
Qualquer coisa entre esses dois separa os conjuntos de maneira limpa com base nesses dados. O método generaliza: escore uma amostra de correspondências conhecidas e uma amostra de não correspondências conhecidas, observe onde as distribuições param de se sobrepor e coloque o limiar na lacuna. Um único número global copiado de um artigo é um palpite sobre os dados de outra pessoa.
Onde as distribuições se sobrepõem, a resposta honesta é uma faixa de revisão — mesclagem automática acima do limite superior, rejeição automática abaixo do limite inferior e aguardar o que cair entre eles. A vinculação de registros estatísticos tem tratado o problema dessa forma por décadas, e a pesquisa de vinculação de registros do Censo dos EUA é a referência padrão para a estrutura probabilística.
Fase 5: Sobrevivência
Decidir que dois registros são os mesmos deixa a questão do que o registro mesclado diz. Descartar o perdedor silenciosamente joga fora a evidência de que a correspondência aconteceu.
python
def survivor(group):
best = sorted(group, key=lambda r: (r["href"] is None, len(r["href"] or "")))[0]
return {**best,
"seen_in": sorted({g["source"] for g in group}),
"prices": sorted({g["price"] for g in group})}
text
[3] 71 records -> 66 entities
merged example: 'Sharp Objects' seen_in=['catalogue1', 'mystery'] prices=['£47.82']
Duas propriedades desse registro mesclado são importantes. seen_in mantém a proveniência, então uma mesclagem errada pode ser rastreada depois em vez de ser invisível. E prices é um conjunto em vez de um único valor: quando duas fontes discordam, a discordância é a parte interessante, e colapsá-la para o registro que aconteceu de ser classificado primeiro a destrói.
Regras em nível de campo superam um vencedor de registro inteiro. A descrição mais longa, o timestamp mais recente, o registro mais completo, a fonte de maior confiança — escolhido por campo em vez de por registro — é o que impede uma mesclagem de herdar as lacunas de uma fonte.
Onde Isso Se Encaixa em um Pipeline
A deduplicação pertence à etapa de transformação, após a extração e antes da gravação. Executá-la antes significa normalizar strings que você ainda não analisou; executá-la depois significa que as duplicatas já estão na tabela e a correção se torna uma migração.
Coletar os mesmos produtos de várias fontes é o que torna o estágio necessário desde o início — o pipeline de precificação competitiva tem exatamente essa forma, e a Universal Scraping API é o que mantém a forma do registro consistente quando uma dessas fontes é renderizada no lado do cliente. Preços lista o custo das fontes adicionais.
Conclusão
A resolução de entidades é composta por quatro estágios baratos antes de um caro. A normalização decide o que conta como a mesma string, o agrupamento exato captura tudo que concorda — 5 grupos e 10 registros aqui — o bloqueio remove 92,8% das pares que ninguém precisa comparar, e apenas o que sobrevive a isso alcança o avaliador de similaridade.
Duas descobertas valem a pena serem levadas para seus próprios dados. O avaliador importa mais do que o limite: 87,8 contra 100,0 na mesma par. E meça a separação antes de escolher um número, porque a diferença de 63,4 para 93,5 que tornou a escolha óbvia aqui é uma propriedade deste catálogo, não uma constante.
Pronto para reconciliar registros de mais de uma fonte? Comece com o plano gratuito do Scrapeless e colete o segundo catálogo que torna os duplicados visíveis.
Perguntas Frequentes
Q: Como faço para remover duplicatas de dados raspados?
Normalize o campo-chave, agrupe-o e, em seguida, mescle cada grupo. A normalização de caixa, Unicode NFKD e a remoção de pontuação convertem strings visualmente idênticas em uma chave, e o agrupamento é O(n) em vez de par a par. Nos 71 registros acima, foram colapsados 10 registros em 5 entidades sem qualquer pontuação de similaridade. Aplique correspondência fuzzy apenas para o que sobrevive a essa passagem.
Q: O que é resolução de entidades?
Decidir quais registros se referem à mesma coisa do mundo real e consolidá-los em um único registro canônico. A deduplicação é a mesma operação dentro de uma única fonte; o termo é geralmente reservado para o caso mais difícil entre fontes cruzadas, onde não existe um identificador compartilhado e a decisão deve ser tomada a partir da similaridade de campo.
Q: O que é bloqueio e por que isso importa?
Comparar apenas registros que já compartilham uma chave barata, para que o estágio par a par não execute sobre tudo. 66 entidades são 2.145 pares possíveis; uma chave de prefixo de quatro caracteres reduziu isso para 154, uma redução de 92,8%. O custo é que registros cuja chave difere nunca são comparados, então uma chave de bloco que é muito rigorosa silencia correspondências.
Q: Qual avaliador de correspondência fuzzy devo usar?
token_set_ratio para títulos que capturam palavras extras de diferentes fontes, uma vez que compara conjuntos de tokens e ignora extras — ele pontuou 100.0 onde ratio deu 87.8 na mesma par. Use ratio quando a posição e a ordem têm significado, como códigos ou endereços. Teste ambos contra correspondências conhecidas de seus próprios dados antes de escolher.
Q: Qual limite de similaridade devo definir?
Meça-o em vez de copiá-lo. Faça uma pontuação de uma amostra de correspondências conhecidas e não correspondências conhecidas e coloque o limite onde as distribuições param de se sobrepor. Aqui, a maior pontuação entre diferentes livros foi 63,4 e a menor entre representações de um livro foi 93,5, então qualquer coisa nesse intervalo funcionou. Onde os dois se sobrepõem, auto-mesclar acima, auto-rejeitar abaixo e colocar o meio para revisão.
Q: Qual registro deve sobreviver a uma mesclagem?
Escolha por campo, não por registro. Leve a descrição mais longa, o preço mais recente, o endereço mais completo e mantenha a proveniência — a entidade mesclada acima retém seen_in e o conjunto completo de preços observados. Manter a lista de fontes torna uma má mesclagem rastreável; manter cada preço observado preserva a discordância entre fontes, que muitas vezes é o sinal que você queria.
Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.



