Saltar al contenido
crawlforgeEnglish
Mediciones3 de agosto de 20269 min de lecturaRead in English

Cómo comprobar que tu rastreador no te está mintiendo

Le dimos las mismas 300 URLs a dos herramientas y comparamos campo por campo. De 1.800 comparaciones salió una diferencia, y era nuestra: llevaba meses ahí, invisible para 810 tests.

Un rastreador SEO tiene un problema curioso: no tiene forma de saber si lo que extrae es correcto. Puede tener miles de tests, y todos verdes, porque los tests comparan su salida contra lo que su autor esperaba. Si el autor entendió mal algo, el test consagra el error.

La única salida es contrastar contra otra implementación. Lo hicimos, y encontramos un fallo que llevaba meses ahí.

Rastrear no sirve para comparar

Lo primero que se le ocurre a cualquiera es rastrear el mismo sitio con las dos herramientas y ver si dan lo mismo. No funciona.

Cada rastreador decide por dónde empieza, qué enlaces sigue, cuándo se detiene, cómo normaliza las URLs y qué hace con los parámetros. Dos rastreos del mismo sitio dan conjuntos distintos de URLs, y lo que acabas comparando son dos recorridos, no dos extracciones. Casi todas las diferencias que veas serán de alcance.

La forma correcta es el modo lista: un fichero con URLs y la instrucción de rastrear exactamente esas. Casi todas las herramientas serias lo tienen, y trae una ventaja añadida, que no depende de límites de licencia por número de URLs porque tú eliges cuántas pones.

El montaje

Sacamos 300 URLs de un rastreo anterior. Todas internas, todas con estado 200, todas HTML:

sqlite3 crawl.sqlite "SELECT url FROM urls
  WHERE is_internal = 1 AND status_code = 200
    AND content_type LIKE 'text/html%'
  ORDER BY url LIMIT 300;" > lista.txt

El ORDER BY url no es cosmético: sin él, dos ejecuciones te dan conjuntos distintos y pierdes la capacidad de repetir la prueba.

Después, las dos herramientas contra ese mismo fichero. La nuestra:

crawlforge list lista.txt --concurrency 4 --out cf.sqlite

Y la otra en modo headless, que suele existir aunque esté poco documentado. Dos cosas que nos costaron tiempo: exige rutas absolutas, y el CSV que exporta lleva las cabeceras en el idioma de la interfaz. Con la aplicación en español, las columnas se llaman Dirección y Título 1.

Comparar campo por campo

Seis campos: código de estado, título, meta description, H1, canonical e indexabilidad. Mil ochocientas comparaciones.

Normalizar los espacios antes de comparar es imprescindible. Una herramienta puede colapsar espacios consecutivos y otra no, y eso llena el resultado de diferencias que no lo son:

def norm(s):
    return re.sub(r"\s+", " ", (s or "").strip())

La primera pasada nos dio 290 diferencias en el canonical, lo cual era demasiado bonito para ser verdad. Y no lo era: estábamos leyendo una columna que no existía en ese CSV, así que comparábamos todos los canonicals contra cadena vacía. La columna se llamaba Elemento de enlace canónico 1.

Corregido eso, el resultado real:

OK  estado           0 de 300
OK  título           0 de 300
OK  descripción      0 de 300
≠   h1               1 de 300
OK  canonical        0 de 300
OK  indexabilidad    0 de 300

La diferencia

Una, en el H1 de la portada:

<h1>Agencia Especializada en WordPress<br />con +25 años de experiencia</h1>

Nosotros devolvíamos Agencia Especializada en WordPresscon +25 años de experiencia.

El <br /> parte el contenido del elemento en dos nodos de texto. Nuestro parser los acumulaba y los pegaba sin nada en medio, así que las palabras de los extremos se juntaban. La otra herramienta lo hacía bien.

El mismo fallo afectaba a los textos de enlace: <a>Leer<em>más</em>aquí</a> salía como Leermásaquí.

Por qué 810 tests no lo vieron

Los fixtures que habíamos escrito para encabezados eran así:

<h1>Un título normal</h1>
<h2>Otro título</h2>

HTML de libro de texto. Ningún elemento dentro, ningún <br>, ningún <span> de estilo. Y como el test comparaba nuestra salida contra lo que nosotros esperábamos, y esperábamos lo que el código producía, todo cuadraba.

El arreglo fue insertar un separador al cerrar cada nodo de texto; el espacio sobrante lo limpia la normalización que ya se hacía después. Cuatro líneas.

Al escribir el test del caso cometimos el mismo error otra vez. La primera versión era:

<a href="/x">Leer <em>más</em></a>

Con el espacio delante del <em>. Ese test pasa con el fallo puesto, porque el espacio ya está en el texto, así que no probaba nada. Quitándolo, falla como debe.

Lo que se puede afirmar después

Repetida la prueba tras el arreglo: cero diferencias en 1.800 comparaciones.

Eso no significa que nuestra extracción sea correcta. Significa que coincide con la de otra implementación madura, que es una cosa distinta y bastante más útil de lo que parece: si vas a cambiar de herramienta, lo primero que necesitas saber es que los datos no se van a mover debajo de tus informes.

Conviene decir también lo que la prueba no cubre. Son 300 URLs de un solo sitio, todas con estado 200 y todas HTML. No dice nada sobre redirecciones, errores de servidor, contenido que no sea HTML ni sitios que montan el contenido con JavaScript. Y coincidir en seis campos no garantiza coincidir en los demás.

Hazlo con la tuya

Si mantienes cualquier herramienta que extrae datos de páginas web, esta prueba cuesta veinte minutos y encuentra cosas. La receta entera:

  1. Saca una lista de 200-300 URLs reales, ordenada, de un sitio con HTML variado.
  2. Pásala por las dos herramientas en modo lista.
  3. Normaliza espacios antes de comparar.
  4. Comprueba el nombre exacto de cada columna antes de fiarte de un cero.
  5. Cuando encuentres una diferencia, ve al HTML original y decide quién tiene razón.

Ese último paso importa: no siempre la tienes tú, y tampoco siempre la tiene la otra. Nosotros estábamos equivocados esta vez, y saberlo valió mucho más que el rato que costó averiguarlo.

Se construye en público

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

← Volver a la bitácora