Construí un escáner de enlaces rotos para sitios masivos con Laravel y Go
El autor describe cómo combinó Laravel como plano de control y Go como motor de escaneo para crear un sistema distribuido capaz de manejar miles de URLs simultáneas.

Un verificador de enlaces roto parece un proyecto de fin de semana: descargar una página, extraer los <a href> y lanzar una petición a cada URL para detectar 404. El método funciona en sitios pequeños, pero al apuntarlo a webs de gran escala aparecen duplicados, redirecciones, dominios externos, límites de velocidad, HTML mal formado y miles de recursos compitiendo por el mismo host. En ese punto el proceso deja de ser un script y se transforma en un sistema de crawling distribuido.
Para enfrentar ese reto, el autor diseñó una arquitectura donde Laravel actúa como plano de control y Go como motor de escaneo. Laravel gestiona la capa de aplicación: usuarios, autenticación, proyectos, configuración de escaneos, límites de uso, programaciones, almacenamiento de resultados y generación de informes. Go, por su parte, se encarga de la parte intensiva en red, realizando peticiones concurrentes mientras mantiene bajo consumo de memoria y controla la concurrencia.
El flujo básico es:
- El usuario inicia un escaneo.
- Laravel crea una tarea en la cola.
- Un worker en Go la toma, la procesa y reporta progreso en lotes.
- Laravel almacena los datos y construye el informe final.
Los workers en Go son procesos independientes que se registran en la aplicación, envían heartbeats y solicitan trabajos vía API. Gracias a esto se pueden añadir más servidores de escaneo sin tocar la web.
Normalización y deduplicación
El crawler comenzó con una cola simple (añadir URL inicial, descargar, extraer enlaces, repetir). El problema fue definir qué URL era “nueva”. Sin normalización, URLs como https://example.com/about, https://example.com/about#team o https://EXAMPLE.com:443/about se tratarían como distintas, provocando peticiones duplicadas o bucles infinitos. Cada URL pasa por una fase de normalización: resolución de URLs relativas, eliminación de fragmentos, normalización de hosts y puertos por defecto. Se conserva el valor original para que el informe indique el enlace exacto que necesita corrección.
Concurrencia limitada
Escanear secuencialmente es demasiado lento; la latencia de red domina el tiempo total. Go permite lanzar una goroutine por URL, pero la concurrencia ilimitada sobrecarga tanto al escáner como al sitio objetivo. Se implementaron límites en varios niveles: número máximo de escaneos simultáneos por worker, pool interno de workers, límite global de peticiones HTTP y límites por host. Opcionalmente, se añaden retrasos entre peticiones al mismo host para respetar sus políticas de tasa.
Gestión de códigos HTTP y errores
Tratar cualquier respuesta fuera del rango 2xx como rota genera falsos positivos. El autor clasifica los resultados de forma más granular: 2xx = disponible, 404/410 = rotos, 3xx = analizar cadena de redirección, 401/403 = posible restricción, 429 = ralentizar, 5xx = fallo temporal. Errores de DNS, TLS, conexión y tiempo de espera reciben su propia categoría. El informe guarda no solo is_broken sino también contexto como código HTTP, tipo de contenido, duración de la petición y estado final de la URL.
Redirecciones con valor
Cada redirección se registra con su código y destino, permitiendo mostrar cadenas completas como /old-page [301] → /new-page [302] → https://www.example.com/new-page [200]. Este detalle ayuda a decidir si actualizar el enlace original, identificar bucles o cadenas excesivamente largas.
El proyecto está disponible en línea en brokenlinks.pro, donde se pueden probar las capacidades del escáner y consultar la documentación.
Aunque la solución está pensada para sitios de gran tamaño, el enfoque modular —Laravel como controlador y Go como motor de red— permite adaptarla a diferentes escalas y requisitos de infraestructura.


