Volver al blog

Cómo deduplicar datos extraídos con resolución de entidad

Isabella Garcia
Isabella Garcia

Web Data Collection Specialist

10-Sep-2026

TL;DR:

  • Cuatro páginas de listado devolvieron 71 registros que se resolvieron en 66 entidades, con 5 grupos de duplicados genuinos: los mismos libros apareciendo tanto en un listado de categoría como en el catálogo paginado.
  • La normalización antes de comparar es lo que hace que una clave exacta funcione: el cambio de mayúsculas, la normalización Unicode y la eliminación de puntuación convierten tres representaciones de un título en una sola clave.
  • El bloqueo es medible, no vago. 66 entidades son 2,145 comparaciones de todos los pares; una clave de bloque de cuatro caracteres reduce eso a 154 en 48 bloques, una reducción del 92.8%.
  • Elige el marcador antes del umbral. El mismo par de títulos obtuvo 87.8 en ratio y 100.0 en token_set_ratio.
  • La banda de umbral es medible en tus propios datos: títulos en vivo no relacionados alcanzaron un pico de 63.4 mientras que tres representaciones de un libro puntuaron 93.5 y más.
  • En un solo catálogo limpio, las claves exactas atraparon cada duplicado y la coincidencia difusa no encontró nada por encima de 90: la coincidencia difusa se gana su lugar a través de fuentes, no dentro de una sola.
  • Recoge los registros de múltiples fuentes que se reconcilian con el plan gratuito de Scrapeless.

Raspar un sitio y los duplicados son raros. Raspar el mismo catálogo a través de un listado de categoría y un índice paginado, o los mismos productos a través de dos minoristas, y los duplicados llegan por construcción — el rastreo visitó el mismo artículo por dos rutas y no tenía forma de saberlo.

La resolución de entidades es el paso que convierte esos registros de nuevo en cosas. Se ejecuta después de la extracción y antes del almacenamiento, y es principalmente una secuencia de decisiones baratas: qué cuenta como la misma cadena, qué cuenta como el mismo registro y qué versión sobrevive.

Cada número a continuación proviene de una colección de 71 registros tomados de cuatro páginas de listado en vivo.

Pipeline at a Glance

Etapa Pregunta Mecánica Resultado medido
Normalizar ¿Es esta la misma cadena? cambio de mayúsculas, NFKD, eliminar puntuación 66 claves distintas de 71 registros
Clave exacta ¿Es este el mismo registro? agrupar por clave normalizada 5 grupos de duplicados, 10 registros a 5
Bloqueo ¿Qué pares valen la pena comparar? prefijo de clave de 4 caracteres 2,145 pares a 154, una reducción del 92.8%
Difusa ¿Es esto lo mismo, escrito de manera diferente? token_set_ratio 93.5–100 en coincidencias verdaderas, 63.4 techo en no relacionadas
Supervivencia ¿Qué registro gana? reglas de campo, mantener procedencia una entidad con seen_in y precios observados

El flujo es normalizar → clave exacta → bloquear → comparar difusamente → fusionar. Cada etapa es más barata que la siguiente, por lo que cada una existe para reducir el trabajo que la siguiente debe hacer.

Stage 1: Normalise Before Comparing

Dos registros que describen el mismo producto rara vez llevan cadenas idénticas en bytes. Las mayúsculas, acentos y puntuación cambian.

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()

El paso NFKD es más importante de lo que parece. El anexo de normalización Unicode define varias formas, y una descomposición de compatibilidad es lo que hace que un é precompuesto y un e simple más acentos combinados se comparen como iguales: las dos ortografías son visualmente idénticas y diferentes en bytes, que es exactamente el caso que produce un duplicado que nadie puede ver en la salida.

El cambio de mayúsculas en lugar de la conversión a minúsculas es el compañero de coincidencia, y la nota del modelo de caracteres W3C sobre normalización es la referencia sobre por qué las dos difieren para texto no ASCII.

Sobre los registros recogidos:

text Copy
[1] collected 71 records from 4 listing pages
    raw distinct titles        66
    normalised distinct titles 66

Idéntico aquí, porque este catálogo es limpio. Eso es bueno saberlo en lugar de asumir: ejecutar la comparación te dice si la normalización está haciendo algún trabajo en tus datos antes de que construyas algo sobre ellos.

Stage 2: Group on an Exact Key

Con una clave normalizada, el primer paso es un agrupamiento, no una comparación. Es O(n) y captura cada duplicado que coincide exactamente.

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']

Nota de dónde vienen los duplicados: cada grupo abarca dos páginas de listado diferentes. Ninguna página única contenía un duplicado. Esa es la forma general: los duplicados son una propiedad del rastreo, no de la página, así que un raspador que solo lea un listado no los verá y un raspador que lea cuatro sí los verá.

Utiliza un identificador estable como clave cada vez que la página publique uno. Un ID de producto, un ISBN o un camino de URL canónico supera a un título, porque los títulos son copias de marketing y cambian sin que el producto cambie. Los esquemas de identificador publicados existen precisamente para que las partes independientes puedan acordar sobre la identidad: la especificación del espacio de nombres ISBN URN es el ejemplo del mundo del libro, y una página raspada que expone uno ya ha resuelto el problema de coincidencia por ti.

Stage 3: Block Before Comparing Pairs

La comparación difusa es por pares, y por pares es cuadrática. Para 66 entidades, eso son 2,145 comparaciones; para 10,000, es poco menos de 50 millones.

El bloqueo reduce el campo comparando solo registros que ya comparten 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%

El comercio es explícito: un registro cuyo título comienza de manera diferente nunca se compara, así que una clave de bloque que sea demasiado agresiva oculta coincidencias reales. Bloquear los primeros cuatro caracteres pierde un par como The Elephant Tree contra Elephant Tree porque el artículo se movió. Las respuestas comunes son bloquear en un prefijo de token ordenado, en un identificador numérico, o en varias claves a la vez y tomar la unión de los pares candidatos.

Etapa 4: Coincidencia Difusa, y Cuándo No Se Necesita

Ejecutar la pasada difusa sobre este catálogo produjo un resultado que vale la pena informar honestamente:

text Copy
[5] fuzzy near-duplicates above 90 (token_sort_ratio)
    brute force 2145 pairs in 2.5 ms -> 0 candidate(s)

Nada. Después de la normalización y agrupación exacta, un catálogo limpio no tenía duplicados cercanos restantes. Una pasada difusa aquí sería un código que nunca se activa.

La coincidencia difusa gana su lugar cuando los registros llegan de fuentes que formatean títulos de manera diferente. Tomando un título real y representándolo de la manera en que lo llevarían tres listados diferentes:

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

Tres claves para un libro: la etapa de clave exacta no puede ayudar aquí. Y el puntaje cambia más la respuesta que lo que hace el umbral. ratio compara las cadenas como secuencias y se ve afectado por el sufijo [Paperback]; token_set_ratio compara los conjuntos de tokens, por lo que palabras adicionales no tienen costo y las dos primeras variantes marcan un limpio 100.

¿Conciliando registros de varias fuentes? El plan gratuito de Scrapeless cubre suficientes solicitudes para recoger el segundo catálogo que hace aparecer los duplicados.

Elegir el Umbral Desde Tus Propios Datos

Un umbral solo es defensible contra la separación medida. Dos números lo limitan aquí:

Medición Puntaje
Más alto token_sort_ratio entre dos títulos vivos genuinamente diferentes 63.4
Más bajo token_set_ratio entre tres representaciones de un libro 93.5

Cualquier cosa entre esos dos separa los conjuntos claramente en estos datos. El método se generaliza: puntuar una muestra de coincidencias conocidas y una muestra de no coincidencias conocidas, observar dónde las distribuciones dejan de superponerse y poner el umbral en el espacio. Un solo número global copiado de un artículo es una suposición sobre los datos de otra persona.

Donde las distribuciones se superponen, la respuesta honesta es una banda de revisión: auto-merge por encima del límite superior, auto-rechazo por debajo del límite inferior y encolar lo que cae entre ellos. La unión estadística de registros ha tratado el problema de esta manera durante décadas, y la investigación de enlace de registros de la Oficina del Censo de EE. UU. es la referencia estándar para el encuadre probabilístico.

Etapa 5: Supervivencia

Decidir que dos registros son el mismo deja la pregunta de qué dice el registro fusionado. Descartar al perdedor tira silenciosamente la evidencia de que la coincidencia ocurrió.

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']

Dos propiedades de ese registro fusionado son importantes. seen_in mantiene la procedencia, por lo que una fusión incorrecta es rastreable después en lugar de invisible. Y prices es un conjunto en lugar de un solo valor: cuando dos fuentes no están de acuerdo, la discrepancia es la parte interesante, y colapsarla en el registro que sucedió a clasificar primero la destruye.

Las reglas a nivel de campo superan a un ganador de todo el registro. La descripción más larga, la marca de tiempo más reciente, el registro más completo, la fuente de mayor confianza, elegidos por campo en lugar de por registro, es lo que evita que una fusión herede las lagunas de una fuente.

Donde Esto Se Encuentra en un Pipeline

La deduplicación pertenece al paso de transformación, después de la extracción y antes de la escritura. Ejecutarlo antes significa normalizar cadenas que aún no has analizado; ejecutarlo después significa que los duplicados ya están en la tabla y la solución se convierte en una migración.
Recoger los mismos productos de varias fuentes es lo que hace necesaria la etapa en primer lugar: el pipeline de precios competitivos tiene exactamente esa forma, y la API Universal de Scraping es lo que mantiene la forma del registro consistente cuando una de esas fuentes se renderiza del lado del cliente. La tarifa lista lo que cuestan las fuentes adicionales.

Conclusión

La resolución de entidades tiene cuatro etapas baratas antes de una costosa. La normalización decide qué cuenta como la misma cadena, el agrupamiento exacto captura todo lo que concuerda — 5 grupos y 10 registros aquí — el bloqueo elimina el 92.8% de los pares que nadie necesita comparar, y solo lo que sobrevive a eso llega al puntuador difuso.

Dos hallazgos valen la pena llevar a tus propios datos. El puntuador importa más que el umbral: 87.8 contra 100.0 en el mismo par. Y mide la separación antes de elegir un número, porque la brecha de 63.4 a 93.5 que hizo la elección obvia aquí es una propiedad de este catálogo, no una constante.

¿Listo para reconciliar registros de más de una fuente? Comienza con el plan gratuito de Scrapeless y recoge el segundo catálogo que hace visibles los duplicados.

FAQ

Q: ¿Cómo elimino duplicados de los datos extraídos?

Normaliza el campo clave, agrúpalo y luego fusiona cada grupo. La normalización de caso, NFKD de Unicode y la eliminación de puntuaciones convierten cadenas visualmente idénticas en una clave, y el agrupamiento es O(n) en lugar de por pares. En los 71 registros anteriores que colapsaron 10 registros en 5 entidades sin ninguna puntuación de similitud. Acude al emparejamiento difuso solo por lo que sobrevive a ese paso.

Q: ¿Qué es la resolución de entidades?

Decidir qué registros se refieren a la misma cosa del mundo real y consolidarlos en un registro canónico. La deduplicación es la misma operación dentro de una única fuente; el término suele reservarse para el caso más difícil entre fuentes, donde no existe un identificador compartido y la decisión debe tomarse a partir de la similitud de campos.

Q: ¿Qué es el bloqueo y por qué importa?

Comparar solo registros que ya comparten una clave barata, de modo que la etapa por pares no se ejecute sobre todo. 66 entidades son 2,145 pares posibles; una clave de prefijo de cuatro caracteres redujo eso a 154, una reducción del 92.8%. El costo es que los registros cuya clave difiere nunca se comparan, por lo que una clave de bloque que es demasiado estricta oculta silenciosamente coincidencias.

Q: ¿Qué puntuador de emparejamiento difuso debo usar?

token_set_ratio para títulos que recogen palabras adicionales de diferentes fuentes, ya que compara conjuntos de tokens e ignora extras — obtuvo 100.0 donde ratio dio 87.8 en el mismo par. Usa ratio cuando la posición y el orden tengan significado, como códigos o direcciones. Prueba ambos contra coincidencias conocidas de tus propios datos antes de elegir.

Q: ¿Qué umbral de similitud debo establecer?

Mídelo en lugar de copiarlo. Evalúa una muestra de coincidencias conocidas y no coincidencias conocidas y coloca el umbral donde las distribuciones dejan de superponerse. Aquí, la puntuación más alta entre diferentes libros fue 63.4 y la más baja entre representaciones de un libro fue 93.5, así que cualquier cosa en ese rango funcionó. Donde las dos se superponen, auto-fusionar por encima, auto-rechazar por debajo, y poner el medio para revisión.

Q: ¿Qué registro debe sobrevivir a una fusión?

Elige por campo, no por registro. Toma la descripción más larga, el precio más reciente, la dirección más completa, y conserva la procedencia: la entidad fusionada arriba retiene seen_in y el conjunto completo de precios observados. Mantener la lista de fuentes hace que una mala fusión sea rastreable; mantener cada precio observado preserva el desacuerdo entre fuentes, que a menudo es la señal que querías.

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar