Conocimiento

Deduplicación de datos extraídos: resolución de entidades para productos, empresas y listados

El mismo producto, empresa o listado aparece muchas veces en distintas fuentes y ejecuciones. Cómo normalizar identificadores, hacer coincidencias de forma segura a gran escala y mantener un único registro limpio.

Matt Brown

Matt Brown

28 de septiembre de 2026 · 10 min de lectura

Recopilar de más de un sitio, o de un sitio más de una vez, y las duplicidades aparecen. El mismo producto figura en tres mercados con tres nombres ligeramente distintos. La misma empresa es “Acme Widgets Ltd” en un registro y “ACME WIDGETS LIMITED” en otro. El mismo anuncio aparece dos veces porque la paginación se desplazó mientras rastreabas, o porque un enlace llevaba un parámetro de seguimiento y el otro no.

Si no se corrigen, las duplicidades inflan los recuentos, dividen el historial entre registros y corrompen silenciosamente cualquier métrica construida sobre ellos. Esta guía trata cómo resolverlas: normalizar identificadores, emparejar primero por claves fuertes, emparejar de forma difusa con seguridad a gran escala, agrupar y elegir qué versión del registro prevalece.

Puntos clave

  • La mayoría de las duplicidades se detectan normalizando los identificadores antes de cualquier emparejamiento difuso: URLs canónicas, códigos de producto validados y nombres de empresa depurados.
  • Empareja primero por identificadores fuertes. Un código de barras válido o un número de registro vale más que cualquier grado de similitud de nombres.
  • Nunca compares cada registro con todos los demás. Un millón de registros generan unos 500 000 millones de pares; el bloqueo (blocking) reduce eso a algo manejable.
  • Decide qué error perjudica más. Para algunas tareas una fusión falsa es peor que una duplicidad no detectada; para otras es al revés.
  • Conserva la procedencia. El registro fusionado debe seguir sabiendo de qué fuentes proviene.

Tres tipos de duplicidad

TipoEjemploCómo se detecta
Recolección repetidaLa misma URL obtenida dos veces, o páginas de resultados solapadasURL canónica o identificador de fuente
Misma entidad, distinta fuenteUn producto listado en tres mercadosIdentificadores compartidos, luego emparejamiento difuso
Contenido casi duplicadoEl mismo artículo sindicado con pequeñas edicionesSimilitud de contenido, tratada en detección de cambios a gran escala

Esta guía se centra en los dos primeros casos, donde el objetivo es un registro por cada producto, empresa o anuncio real.

Paso 1: normalizar identificadores

La normalización es barata y detecta más duplicidades que cualquier emparejamiento ingenioso.

URLs. La misma página llega bajo muchas URLs. Pon el host en minúsculas, elimina www., quita parámetros de seguimiento como utm_*, gclid y fbclid, ordena los parámetros de consulta restantes y elimina las barras finales. http://www.Shop.com/p/123/?utm_source=x&b=2&a=1 y https://shop.com/p/123?a=1&b=2 acaban siendo la misma clave.

Códigos de producto. Los Global Trade Item Numbers (los números detrás de los códigos de barras UPC y EAN) llevan un dígito de control, que permite rechazar códigos mal tecleados o mal extraídos antes de confiar en ellos. El método de GS1 pondera los dígitos 3, 1, 3, 1 y así sucesivamente, empezando desde el dígito contiguo al dígito de control, los suma y toma la cantidad necesaria para redondear al siguiente múltiplo de diez. En el ejemplo resuelto del propio GS1, el cuerpo de 11 dígitos 61414121022 obtiene un dígito de control de 0. Rellena los códigos válidos hasta 14 dígitos para que una versión de 13 dígitos y otra de 14 dígitos del mismo código se comparen como iguales.

Nombres de empresa. Uniformiza mayúsculas y acentos, elimina la puntuación y quita sufijos legales como Ltd, Limited, Inc, GmbH y SA antes de comparar. “Acme Widgets Ltd.” y “ACME WIDGETS LIMITED” se convierten ambas en acme widgets. Cuando una empresa tenga un número de registro o un Legal Entity Identifier, usa eso en lugar del nombre; la resolución de empresas a identificadores se trata en monitorizar la huella pública de tus proveedores.

Paso 2: emparejar primero por claves fuertes

Una vez normalizados los identificadores, las coincidencias exactas sobre ellos son rápidas y fiables a la vez. Dos registros con el mismo código de barras válido son el mismo producto. Dos registros con la misma URL canónica son la misma página. Dos empresas con el mismo número de registro son la misma empresa, se llamen como se llamen.

Los datos estructurados también ayudan aquí. Muchas páginas de producto publican códigos de barras y SKU en JSON-LD, lo cual es mucho más fiable que extraerlos de la página visible; ver deja de analizar HTML.

Paso 3: emparejamiento difuso, pero solo dentro de bloques

Los registros sin identificadores compartidos necesitan emparejamiento difuso por nombres, direcciones o descripciones. La trampa es la escala. Comparar cada registro con todos los demás crece con el cuadrado del conjunto de datos: un millón de registros producen aproximadamente 500 000 millones de pares.

El bloqueo (blocking) resuelve esto. Agrupa los registros por una clave barata que las coincidencias verdaderas casi siempre comparten, como país más la primera palabra del nombre normalizado, o marca más categoría de producto, y compara solo dentro de cada grupo. Una buena clave de bloqueo reduce las comparaciones en órdenes de magnitud perdiendo pocas coincidencias verdaderas. Comprueba qué se pierde muestreando pares entre bloques de vez en cuando.

Dentro de un bloque, una puntuación de similitud de cadenas y un umbral deciden las coincidencias. Empieza estricto, alrededor de 0.9, y relaja solo después de revisar qué fusionaría un ajuste más laxo.

Paso 4: agrupar con cuidado

Las coincidencias son por pares; las entidades son grupos. Una estructura union-find convierte pares en clústeres de forma eficiente. Pero también conlleva un riesgo: la transitividad. Si A coincide con B y B coincide con C, A y C acaban juntos aunque no compartan nada. Las cadenas largas de coincidencias débiles son la forma en que dos empresas distintas terminan fusionadas. Vigila los clústeres inusualmente grandes y revísalos antes de aceptarlos.

Todo el proceso cabe en un pequeño módulo:

import re
import unicodedata
from collections import defaultdict
from difflib import SequenceMatcher
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode

LEGAL_SUFFIXES = {"ltd", "limited", "inc", "incorporated", "llc", "gmbh", "ag", "sa", "sas",
                  "srl", "bv", "nv", "plc", "co", "corp", "corporation", "company", "oy", "ab"}
TRACKING = re.compile(r"^(utm_|gclid$|fbclid$|mc_|ref$|ref_)")


def gtin_valid(code):
    """GS1 check digit: weights 3,1,3,... from the digit next to the check digit."""
    digits = re.sub(r"\D", "", str(code or ""))
    if len(digits) not in (8, 12, 13, 14):
        return False
    body, check = digits[:-1], int(digits[-1])
    total = sum(int(d) * (3 if i % 2 == 0 else 1) for i, d in enumerate(reversed(body)))
    return (10 - total % 10) % 10 == check


def norm_name(name):
    text = unicodedata.normalize("NFKD", name or "").encode("ascii", "ignore").decode().lower()
    tokens = [t for t in re.findall(r"[a-z0-9]+", text) if t not in LEGAL_SUFFIXES]
    return " ".join(tokens)


def norm_url(url):
    parts = urlsplit((url or "").strip())
    query = urlencode(sorted((k, v) for k, v in parse_qsl(parts.query) if not TRACKING.match(k.lower())))
    host = parts.netloc.lower().removeprefix("www.")
    return urlunsplit(("https", host, parts.path.rstrip("/") or "/", query, ""))


def similar(a, b):
    return SequenceMatcher(None, a, b).ratio()


def cluster(records, threshold=0.9):
    """Group records that refer to the same entity. Returns lists of record indexes."""
    parent = list(range(len(records)))

    def find(i):
        while parent[i] != i:
            parent[i] = parent[parent[i]]
            i = parent[i]
        return i

    def union(i, j):
        parent[find(i)] = find(j)

    # 1. Exact matches on strong identifiers.
    by_key = defaultdict(list)
    for i, r in enumerate(records):
        if gtin_valid(r.get("gtin")):
            by_key["gtin:" + re.sub(r"\D", "", r["gtin"]).zfill(14)].append(i)
        if r.get("url"):
            by_key["url:" + norm_url(r["url"])].append(i)
    for ids in by_key.values():
        for j in ids[1:]:
            union(ids[0], j)

    # 2. Fuzzy name match, only within a cheap blocking key.
    blocks = defaultdict(list)
    for i, r in enumerate(records):
        name = norm_name(r.get("name"))
        if name:
            blocks[(r.get("country") or "", name.split()[0])].append((i, name))
    for members in blocks.values():
        for a in range(len(members)):
            for b in range(a + 1, len(members)):
                if similar(members[a][1], members[b][1]) >= threshold:
                    union(members[a][0], members[b][0])

    groups = defaultdict(list)
    for i in range(len(records)):
        groups[find(i)].append(i)
    return list(groups.values())

En un pequeño conjunto de prueba fusiona “Acme Widgets Ltd”, “ACME WIDGETS LIMITED” y un tercer registro que comparte la URL canónica de la empresa, fusiona dos anuncios de zapatos cuyos códigos de barras difieren solo en un cero inicial, y deja deliberadamente “Acme Widget Co” en Estados Unidos como entidad separada, porque la clave de bloqueo incluye el país. Si esta última decisión es correcta depende de tus datos, que es precisamente el punto del siguiente paso.

Paso 5: decidir qué registro prevalece

Un clúster son varias versiones de una entidad, y necesitas una sola. Reglas de supervivencia habituales:

  • Gana el más completo, campo a campo: toma el valor no vacío de la mejor fuente para cada campo en lugar de un registro entero.
  • Gana el más reciente para valores que cambian, como precio y disponibilidad.
  • Gana la fuente más fiable para valores como nombres oficiales y direcciones, por ejemplo un registro frente a un directorio.

Elijas lo que elijas, conserva todos los identificadores de fuente y URLs en el registro fusionado, junto con el momento en que se observó cada uno. Cuando una fusión resulte estar mal hecha, la procedencia es lo que te permite volver a separarla.

Medir precisión y exhaustividad

La resolución de entidades tiene dos tipos de error, y cuál importa más depende de la tarea.

ErrorQué ocurrePeor para
Fusión falsaDos entidades reales se convierten en unaDatos de empresas y adyacentes a personas, cumplimiento normativo, cualquier cosa legal
Duplicidad no detectadaUna entidad permanece como varios registrosRecuentos, dimensionamiento de mercado, comparación de precios

Etiqueta a mano unos cientos de pares candidatos, mide ambas tasas y ajusta los umbrales y las claves de bloqueo según el error que te importe. Una base de datos de leads B2B, como la de construir una base de datos de leads B2B, suele tolerar mucho mejor una duplicidad no detectada que dos empresas fusionadas por error. Un feed de comparación de precios, como un feed de precios competitivos en tiempo real, es al contrario: una duplicidad no detectada significa que un producto aparece dos veces con dos precios.

Elimina duplicados pronto, y en el origen

Las duplicidades cuestan dinero antes de costar precisión. Cada recuperación repetida de la misma URL canónica es ancho de banda o créditos gastados en vano, por lo que canonicalizar URLs pertenece a la frontera del rastreador, no solo al almacén de datos. El efecto sobre el coste unitario se trata en coste por registro limpio.

En resumen

La deduplicación es sobre todo normalización. Las URLs canónicas, los códigos de producto validados y los nombres de empresa depurados detectan la mayoría de las duplicidades antes de que se ejecute cualquier emparejamiento difuso. Después de eso, empareja por claves fuertes, aplica emparejamiento difuso solo dentro de bloques, agrupa vigilando las cadenas largas, conserva la procedencia en cada registro fusionado y mide los errores que importan para tu caso de uso.

Bien hecho, una cosa real del mundo se convierte en un registro, con su historial completo adjunto. Mal hecho, el conjunto de datos parece más grande y menos fiable al mismo tiempo.

Fuentes y referencias

¿Listo para empezar?

Prueba los proxies residenciales de Shifter, más de 205M IPs, más de 195 países, desde 0,75 $/GB.

Comenzar