Saltar al contenido
crawlforgeEnglish
Mediciones28 de octubre de 20267 min de lecturaRead in English

El mismo estadio, dos veces

La 0.18.0 añade DUP-CONTENT-NEAR, que avisa de las páginas con el texto casi igual aunque el HTML no lo sea. Lo planificado no servía y el umbral habitual tampoco.

DUP-CONTENT-EXACT lleva en el catálogo desde la primera versión y casi nunca dice nada en un WordPress. Compara el HTML byte a byte, y basta un nonce, una fecha o un widget de entradas recientes para que dos páginas con el mismo texto tengan hashes distintos. Las páginas de etiqueta y de categoría que listan los mismos extractos, o dos entradas escritas una encima de otra, se le escapan siempre.

La 0.18.0 trae la regla que faltaba, DUP-CONTENT-NEAR. Avisa cuando dos o más páginas indexables comparten el 80 % o más de su texto visible. Es medium y es del nivel gratuito, aunque en el catálogo estaba apuntada como Pro. El nivel gratuito limita la escala, no lo que la herramienta te cuenta de tu sitio, y esta regla es justo de las que cuentan. El catálogo pasa a 65 reglas.

Para llegar al 80 % hubo que tirar dos cosas que parecían decididas: el algoritmo del plan y el umbral que usa casi todo el mundo.

SimHash no aguantaba la cuenta

El plan decía SimHash: una huella de 64 bits por página, y dos páginas son casi iguales si sus huellas se diferencian en pocos bits. Es barato y ocupa ocho bytes. Lo implementé primero y el fixture de la regla ya dio el aviso: dos páginas que comparten casi todo el texto salían a 4 bits, por encima del corte de 3 que había puesto.

Hice la cuenta antes de subir el corte. Dos páginas que comparten el 90 % del texto caen entre 6 y 12 bits de distancia, según cómo se repartan las diferencias. Con el corte en 9, un rastreo de 200.000 páginas tiene unas 35 parejas que coinciden por puro azar. Para tener el ruido bajo hay que bajar el corte, y entonces se pierden duplicados de verdad. Con 64 bits no hay corte que sirva para las dos cosas.

Lo cambié por MinHash: 128 valores por página, uno por permutación, de un byte cada uno. La proporción de valores que coinciden entre dos páginas estima directamente cuánto texto comparten. Son 128 bytes por página en una columna nueva, text_minhash. La migración es de las seguras: un fichero antiguo se abre y se reanuda igual, solo que sus páginas no tienen firma y la regla no las mira.

El texto se trocea en grupos de tres palabras seguidas, en minúsculas. Fuera quedan la navegación, el pie, los scripts y los <aside>, porque una barra lateral que se repite en todo el sitio haría parecidas a todas las páginas. Las que no llegan a 50 grupos no tienen firma: con tan poco texto el parecido no significa nada.

Una versión que pasaba los tests y estimaba mal

La primera versión de MinHash hacía las permutaciones con aritmética de 64 bits. Pasaba todos los tests unitarios. Al probarla contra especialmundial.com dio un 0,945 de parecido a una pareja de páginas, y yo quería saber si era verdad. Bajé las dos con curl, saqué sus grupos de tres palabras con unas líneas de Python y calculé el parecido exacto, sin estimar: 0,85.

Una diferencia de diez puntos justo en la zona del umbral no es ruido. Reescribí las permutaciones como multiplicaciones de 32 bits, que además el procesador hace de cuatro en cuatro, y la misma pareja dio 0,851. Ahora hay un test que genera 600 parejas con parecido conocido y mide dos cosas: que el error medio no pase de 0,01 y que la dispersión no supere en un 30 % la que predice la teoría.

El 90 % no encontraba nada

La herramienta más extendida del sector trae los casi duplicados al 90 % por defecto, y con ese umbral empecé. En especialmundial no salió nada real. Lo que sí salió fue un falso positivo: la lista de 222 estadios en español y la misma lista en inglés coinciden en un 96 %, porque los nombres propios no se traducen y la lista es casi solo nombres.

Eso tenía arreglo directo. Las dos páginas se declaran traducción una de la otra con hreflang, y dos páginas que se dicen alternativas no se juntan nunca en un grupo.

Lo que no tenía arreglo directo era el umbral. Saqué del rastreo todas las parejas con un parecido estimado entre 0,5 y 1, y comprobé a mano las cercanas al corte con el cálculo exacto:

Caso Parecido
La misma lista en dos idiomas 0,96
El mismo estadio publicado con dos slugs 0,72 a 0,85
Páginas de plantilla con datos distintos 0,62
Entradas distintas de colorvivo.com 0,25 como mucho

Los duplicados de verdad están entre 0,72 y 0,85. Son fichas de unas cien palabras que repiten el nombre del estadio escrito de dos formas, y cada variante del nombre cambia varios grupos de tres palabras. Con el 90 % no sale ninguno. El umbral quedó en 0,80, con esta tabla escrita en el comentario de la constante para que quien lo quiera mover sepa qué deja fuera.

Al bajar el umbral hubo que repartir otra vez la firma en bandas, que es lo que evita comparar cada página con todas. Con 16 bandas de 8 bytes se perdía un 5 % de las parejas al 0,80. Con 21 bandas de 6 se pierde menos del 1 %.

Lo que encontró

En especialmundial, dos grupos. El Bernabéu existe en /en/stadiums/estadio-santiago-bernabeu y en …-bernabeu-madrid, y el Durival de Britto en …-durival-britto-curitiba y …-durival-de-britto-curitiba. Son la misma ficha publicada dos veces, y hay que quitar una de cada pareja.

Por debajo del umbral hay más del mismo tipo, y no los oculto: San Petersburgo con saint-petersburg y st-petersburg, y las versiones en español de esos mismos estadios. Algunos estadios rebautizados tienen una página por cada nombre, el Comunale y el Artemio Franchi en Florencia o el Littoriale y el Dall’Ara en Bolonia. Puede que eso sea intencionado.

En colorvivo, cero, y me lo creo. Las etiquetas son noindex (121 de las 130 de la muestra) y la regla solo mira páginas indexables. Las nueve categorías que sí lo son no se parecen entre ellas.

Lo que cuesta

Calcular la firma ocurre al parsear cada página, que es la parte más caliente del motor. Medí el parseo de un artículo real de 2.700 palabras, 200 veces seguidas, con la 0.17.0 y con la 0.18.0: pasa de 0,46 a 0,57 milisegundos, un 24 % más. La versión de 64 bits costaba casi el triple. En un rastreo por HTTP, donde cada página tarda decenas de milisegundos en llegar, no se nota. La regla en sí tarda 73 milisegundos sobre las 2.327 páginas de especialmundial.

Esa medición la tuve que hacer aparte, porque el banco de regresión que corre antes de cada publicación dio empate. Sus páginas de prueba tienen tres palabras y nunca llegan a los 50 grupos que hacen falta para calcular una firma, así que el banco no ejecuta nada de lo que he añadido. Lo dejo apuntado: un cambio que toque el texto se mide con una página de verdad.

Una columna que decía lo que no era

Revisando el esquema salió una cosa más. La columna text_hash estaba documentada como «simhash para casi duplicados». Nunca lo fue: es un hash exacto del texto y solo se rellena en Pro. Corregí la documentación y no toqué la columna, que tiene otro uso. La firma nueva va en su propia columna para que un fichero viejo nunca se lea con la firma equivocada.

Los tests de la regla cubren el límite exacto entre 25 y 26 bytes distintos de la firma, las bandas, el HTML idéntico que ya cubre la otra regla, las páginas noindex, las firmas vacías o mal formadas, la transitividad de los grupos y la excepción de hreflang. Los de las bandas, el HTML idéntico, los <aside> y hreflang los vi fallar quitando lo que prueban. El CI pasó en Linux, Windows y macOS.

Se construye en público

Cada dos semanas: mediciones, fallos y ejemplos de uso. Nada más.

← Volver a la bitácora