Scraping

Detección de cambios a escala: comparar páginas web sin ahogarse en el ruido

Las páginas cambian en cada carga: anuncios, marcas de tiempo, tokens. Cómo distinguir los cambios reales del ruido con normalización, diffs a nivel de campo y simhash, con código probado.

Chris Collins

Chris Collins

27 de septiembre de 2026 · 10 min de lectura

Cada tarea de monitorización acaba planteando la misma pregunta: ¿ha cambiado esta página? Suena como una comparación de hashes. No lo es. Las páginas modernas cambian en cada carga, con anuncios rotativos, marcas de tiempo, bloques de recomendaciones, tokens de sesión y contadores en vivo, así que una comparación ingenua reporta un cambio cada vez y el canal de alertas se llena de ruido hasta que todo el mundo lo silencia. El fallo opuesto es más silencioso y peor: un pipeline tan ajustado para ignorar el ruido que se le escapa la bajada de precio o el certificado eliminado que estaba construido para detectar.

Esta guía cubre cómo detectar cambios reales a escala: comparar campos en lugar de páginas, normalizar lo que no se puede evitar comparar, medir cuánto ha cambiado una página en lugar de si ha cambiado, y dirigir cada tipo de cambio al lugar adecuado.

Puntos clave

  • La comparación de bytes en bruto es inútil para la mayoría de los sitios. Descargamos la portada de un sitio de noticias dos veces, con 45 segundos de diferencia: los bytes eran distintos, y hasta el texto ya limpio era distinto.
  • Compara campos, no páginas, siempre que puedas. Un precio, un título o un estado de existencias han cambiado o no han cambiado.
  • Donde debas comparar páginas completas, normaliza primero y luego mide la similitud. Simhash, la técnica de fingerprinting que Google describió para la detección de casi duplicados entre 8.000 millones de páginas, convierte “¿ha cambiado?” en “¿cuánto ha cambiado?”.
  • Clasifica cada cambio: cambio de campo, cambio de contenido o ruido. Cada uno merece una respuesta distinta.
  • Ajusta por sitio y por tipo de página. Un umbral correcto para una página de producto es incorrecto para una portada de noticias.

Por qué el diffing ingenuo falla

Para ver el problema de forma concreta, descargamos la portada internacional de un gran sitio de noticias dos veces, con 45 segundos de diferencia. El HTML en bruto era distinto, como cabía esperar. Más interesante aún: tras eliminar scripts, estilos y marcado, y quitar marcas de tiempo y tokens, el texto visible seguía siendo distinto. Las páginas en vivo se actualizan continuamente. Un monitor que alertara ante cualquier diferencia habría alertado sobre esta página cada vez que se ejecutara.

Las fuentes de ruido son predecibles:

RuidoEjemploSolución típica
Scripts y datos incrustadosPayloads de analítica, estado JSON, tokens CSRFEliminar bloques script y style antes de comparar
Texto basado en el tiempo”Actualizado hace 3 minutos”, horas de reloj, fechasEliminar con patrones, o comparar campos que los excluyan
Módulos rotativosAnuncios, “tendencias ahora”, recomendacionesComparar solo la región o los campos que importan
Personalización y experimentosVariantes de test A/B, bloques específicos de ubicaciónRecoger desde un punto de vista fijo con ajustes consistentes
OrdenaciónListas que se reordenan en cada cargaOrdenar antes de comparar

Parte del ruido es en realidad una diferencia en quién preguntó. Una página cargada desde Alemania puede diferir de la misma página cargada desde Estados Unidos por razones que no tienen nada que ver con un cambio. Mantén fijo el punto de vista para cada página monitorizada, de modo que lo único que varíe entre ejecuciones sea el tiempo. Con un gateway residencial, eso significa fijar el país, por ejemplo customer-USERNAME-country-de, para cada ejecución de esa tarea.

Principio 1: compara campos, no páginas

El detector de cambios más fiable no compara páginas en absoluto. Extrae los campos que importan, como el precio, la disponibilidad, el título, una lista de certificados o una dirección, y compara esos. Un campo ha cambiado o no ha cambiado, y el ruido en el resto de la página es irrelevante.

Los datos estructurados facilitan esto más de lo que parece. Muchas páginas publican sus campos clave como JSON-LD, mucho más estable que el diseño que lo rodea; su extracción se trata en deja de analizar HTML. Cuando los campos provienen de selectores en su lugar, compara los valores extraídos, nunca el HTML que los rodea.

La comparación de campos también hace que las alertas sean útiles. “El precio cambió de 89,00 a 79,00” es accionable. “La página ha cambiado” no lo es.

Principio 2: normaliza lo que debas comparar

Parte de la monitorización realmente trata sobre páginas completas: términos de servicio, la página “acerca de” de un proveedor, una declaración de sostenibilidad, una página de políticas. Para esas, elimina lo que nunca importa antes de comparar:

  • Elimina los bloques que no son contenido: scripts, estilos, plantillas, SVG en línea.
  • Conserva solo el texto visible, con los espacios en blanco colapsados y las mayúsculas normalizadas.
  • Elimina fragmentos volátiles: horas de reloj, fechas ISO, tiempos relativos, tokens e identificadores hexadecimales largos.
  • Restringe a una región cuando una página tiene un área de contenido principal estable, como el cuerpo del artículo o el elemento principal.

La normalización elimina gran parte del ruido. No lo elimina todo, y por eso importa el siguiente paso.

Principio 3: mide cuánto, no si

En lugar de preguntar si el texto normalizado es idéntico, pregunta cuán similar es. Simhash es una buena herramienta para esto. Reduce un documento a una huella digital de 64 bits con una propiedad útil: los documentos similares obtienen huellas que difieren solo en unas pocas posiciones de bit. Google describió su uso para la detección de casi duplicados en “Detecting Near-Duplicates for Web Crawling” (WWW 2007), donde los autores validaron que “para un repositorio de 8.000 millones de páginas web, las huellas simhash de 64 bits y k = 3 son razonables”, lo que significa que las páginas cuyas huellas difieren en como máximo tres bits podrían tratarse como casi duplicados.

Eso convierte la detección de cambios en una cuestión de distancia. En nuestra prueba, las dos descargas de la portada de noticias, con 45 segundos de diferencia, resultaron estar a 2 bits de distancia: por debajo del umbral, así que ruido. La misma portada comparada con un artículo no relacionado resultó estar a 34 bits de distancia: contenido claramente distinto.

import hashlib
import re

VOLATILE = [
    r"\b\d{1,2}:\d{2}(?::\d{2})?\s?(?:am|pm|AM|PM)?\b",          # clock times
    r"\b\d{4}-\d{2}-\d{2}(?:T[\d:.]+Z?)?\b",                      # ISO dates
    r"\b\d+\s+(?:second|minute|hour|day)s?\s+ago\b",              # relative times
    r"\b(?:csrf|nonce|token|session|sid)[\w-]*[=:]\s*[\w-]+",      # tokens
    r"\b[0-9a-f]{24,}\b",                                         # long hex ids
]


def normalise(html):
    """Visible text only, with volatile fragments removed."""
    html = re.sub(r"(?is)<(script|style|noscript|svg|template)\b.*?</\1>", " ", html)
    text = re.sub(r"(?s)<[^>]+>", " ", html)
    for pattern in VOLATILE:
        text = re.sub(pattern, " ", text, flags=re.I)
    return re.sub(r"\s+", " ", text).strip().lower()


def simhash(text, bits=64):
    """Charikar-style simhash over word 3-grams."""
    words = text.split()
    grams = [" ".join(words[i:i + 3]) for i in range(max(1, len(words) - 2))]
    weights = [0] * bits
    for gram in grams:
        h = int.from_bytes(hashlib.blake2b(gram.encode(), digest_size=8).digest(), "big")
        for i in range(bits):
            weights[i] += 1 if h >> i & 1 else -1
    return sum(1 << i for i in range(bits) if weights[i] > 0)


def distance(a, b):
    return bin(a ^ b).count("1")


def compare(old_html, new_html, old_fields=None, new_fields=None, threshold=3):
    """Classify a change as none, noise, content or field-level."""
    old_fields, new_fields = old_fields or {}, new_fields or {}
    field_changes = {k: (old_fields.get(k), new_fields.get(k))
                     for k in set(old_fields) | set(new_fields)
                     if old_fields.get(k) != new_fields.get(k)}
    if field_changes:
        return {"kind": "field", "changes": field_changes}
    if old_html == new_html:
        return {"kind": "none"}
    d = distance(simhash(normalise(old_html)), simhash(normalise(new_html)))
    return {"kind": "content" if d > threshold else "noise", "distance": d}

Trata el umbral como un punto de partida, no como una constante. La cifra de WWW 2007 se eligió para encontrar duplicados entre miles de millones de páginas, no para monitorizar una página a lo largo del tiempo. Calíbralo por tipo de página: descarga cada página monitorizada varias veces en rápida sucesión, cuando nada significativo debería cambiar, y fija el umbral justo por encima de las distancias que observes. Las páginas cortas necesitan más cuidado, porque unas pocas palabras cambiadas mueven la huella de un documento pequeño más lejos que la de uno largo.

Principio 4: clasifica, luego dirige

Un detector que solo devuelve “ha cambiado” traslada el trabajo real a quien lee la alerta. Devuelve un tipo, y dirígelo según ese tipo:

TipoSignificadoA dónde va
fieldUn valor que rastreas ha cambiadoDirecto al conjunto de datos y, si importa, una alerta
contentLa sustancia de la página ha cambiado más allá del ruidoUna cola de revisión humana, con un diff de texto adjunto
noiseLa página se ha movido, pero dentro de la variación normalRegistrado para calibración, nunca alertado
noneIdéntico byte a byteNada

Dos refinamientos se rentabilizan solos. Conserva la instantánea anterior y un diff de texto legible junto a cada cambio content, para que un revisor pueda juzgarlo en segundos. Y registra la tasa de cada tipo por sitio: un sitio cuya distancia de ruido aumenta poco a poco durante semanas está cambiando sus plantillas, y un sitio que de repente no produce ningún cambio en absoluto puede estarte sirviendo una página obsoleta o bloqueada, un modo de fallo que se trata en la tasa de fallo silencioso.

Escalarlo

La detección de cambios a escala es sobre todo un problema de almacenamiento y planificación.

  • Almacena huellas y campos, no páginas completas, para la mayoría de las ejecuciones. Una huella de 64 bits y un puñado de campos son minúsculos. Conserva instantáneas completas solo cuando se detecta un cambio, o con una programación más lenta para auditoría.
  • Vuelve a visitar según el cambio esperado. Las páginas que rara vez cambian no necesitan comprobaciones cada hora. La planificación de rastreo consciente del coste explica cómo gastar un presupuesto de descargas donde el cambio es probable, y cada resultado none o noise es evidencia para ese modelo.
  • Usa primero señales baratas. Cuando un sitio envía cabeceras ETag o Last-Modified fiables, una solicitud condicional que devuelve 304 Not Modified responde a la pregunta con casi ningún ancho de banda.
  • Vigila al vigilante. Las páginas canario con patrones de cambio conocidos te dicen si el propio detector sigue funcionando.

Dónde se usa esto

La misma maquinaria sustenta trabajos muy distintos: la monitorización de precios y existencias, donde los cambios de campo son todo lo que importa, como en construir un feed de precios competitivo en tiempo real; la monitorización de proveedores y cumplimiento, donde importan los cambios de contenido de página completa, como en monitorizar la huella pública de tus proveedores y verificar afirmaciones ESG; y la preservación de evidencia cuando un cambio tiene relevancia legal.

La conclusión

“¿Ha cambiado esta página?” es la pregunta equivocada para la web moderna, porque la respuesta es casi siempre sí. Las preguntas útiles son “¿ha cambiado un campo que me importa?” y, donde debas comparar páginas completas, “¿ha cambiado la sustancia más allá de la variación normal de esta página?”

Compara campos siempre que puedas. Normaliza lo que no puedas evitar comparar, mide la similitud en lugar de la igualdad, calibra los umbrales por tipo de página, y dirige cada tipo de cambio al lugar que pueda actuar sobre él. El resultado es un feed de cambios en el que la gente confía, que es el único tipo que se lee.

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