De volta ao blog

Como Duplicar Dados Extraídos Com Resolução de Entidades

Isabella Garcia
Isabella Garcia

Web Data Collection Specialist

10-Sep-2026

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 ratio e 100,0 em token_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 Copy
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 Copy
[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 Copy
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 Copy
[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 Copy
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 Copy
[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 Copy
[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 Copy
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 Copy
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 Copy
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 Copy
[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.

Artigos mais populares

Catálogo