Un rastreador de páginas me dijo que una web con 18 servicios no tenía casi ninguno. La herramienta no se equivocó al leer: lo que leyó fue una pantalla de verificación anti robots, y la marcó como "éxito". Este es el tercer repo de la serie y el que más me enseñó a desconfiar de la palabra éxito.
Crawl4AI es un proyecto abierto que visita una página y te la devuelve como texto limpio, listo para que un modelo la lea. El repo está aquí: github.com/unclecode/crawl4ai. Lo que sigue son mis medidas y mis tropiezos, no su manual.
Qué hace y por qué lo puse
Cuando le pides a un agente que investigue un negocio, lo primero que necesita es leer su sitio. Si le das el HTML crudo, recibe una carga de etiquetas, menús y scripts que no le sirven. Si le das una captura de pantalla, tiene que leer una imagen. Crawl4AI abre la página en un navegador de verdad, espera a que cargue lo que se pinta con JavaScript y te entrega el contenido ya limpio en markdown, que es el formato que un modelo lee mejor.
Lo vi por primera vez en mis registros el 17 de agosto y lo puse a trabajar el 16 de septiembre. Mi caso: investigar prospectos de SVDIGINET antes de escribirles. Quiero saber qué servicios ofrece un negocio local, si tiene formulario de contacto y qué correos publica, y quiero que cada dato venga con la página de donde salió. Eso lo hace un servicio mío, Scout, y Scout le pide las páginas a Crawl4AI.
Fuente: repositorio de Crawl4AI
Cómo lo monté, con números
Lo corro en un servidor privado virtual pequeño: dos procesadores, 7.8 GB de memoria y nada de memoria de intercambio. Esa última cifra es la que decide todo. Sin intercambio, si un programa se come la memoria, el sistema mata al primero que encuentra, y yo tengo otro servicio importante en la misma máquina.
| Decisión | Valor | Por qué |
|---|---|---|
| Imagen | Versión 0.9.3, fijada por su huella digital | Que una actualización no cambie el comportamiento sin que yo lo decida |
| Dirección | 127.0.0.1, puerto 11235 | Solo la propia máquina lo ve. No hay puerto abierto a internet |
| Tope de recursos | 3 GB de memoria, 1.5 procesadores, 512 procesos | Si se desboca, protege al otro servicio |
| Uso en reposo | unos 561 MiB y menos de 1 % de procesador | Medido el día del despliegue |
| Acceso | Red interna aparte; entran los contenedores que yo conecto y quien tenga el token | Quien no tenga el token no puede pedirle páginas |
| Contraseña | Token en cada petición | Sin token, 401 |
Y esto fue lo que comprobé ese día, antes de dejarlo trabajar:
| Prueba | Resultado |
|---|---|
| Pedir /health | 200, sin necesidad de token |
| Pedir /crawl sin token | 401 |
| Pedir /crawl con token | 200 |
| Rastrear una página pública de prueba | 490 caracteres de markdown reales |
| Ver en el servidor qué puertos escuchan | Solo 127.0.0.1:11235, no la dirección pública |
Un detalle que me tomó un rato entender: en esta versión, si no configuras un token, el puerto publicado deja de funcionar sin avisar. No queda abierto sin contraseña, queda mudo. Poner el token hace dos cosas a la vez, activa la autenticación y hace alcanzable el puerto. Por eso "sin autenticación" no es una opción abierta por accidente, y me quitó una preocupación. También me enseñó a no confiar en que un servicio falla de forma ruidosa.
El día que dijo éxito sin leer nada
El 21 de septiembre Scout investigaba el sitio de un negocio local. El resultado decía que tenía poco contenido de servicios. Yo sabía que ofrecían 18. Me puse a mirar qué había leído de verdad.
- La peticiónScout le pide la página a Crawl4AI.
- La respuestaEl sitio devuelve un desafío de verificación anti robots: unos 7 KB de código y apenas 61 caracteres de texto.
- La etiquetaCrawl4AI marca la respuesta como éxito, porque el sitio respondió, aunque no con la página.
- El errorScout toma ese éxito por una página legible y concluye que el negocio tiene pocos servicios.
Dos defectos juntos. Primero, mi detector de páginas rotas pedía que el texto fuera muy poco Y el HTML también muy poco; el código del desafío era lo bastante largo para no cumplir la segunda condición. Segundo, el módulo que arma los hallazgos nunca consultaba si la página se había podido leer. El sistema pasó de "respondió" a "leí" sin que nadie lo verificara.
Lo peor es que la prueba era intermitente. En ejecuciones posteriores, el mismo sitio dejó pasar a Crawl4AI. Un reintento en vivo no demuestra nada, ni a favor ni en contra. Lo que sí sirvió fue darle al código nuevo los bytes exactos que había capturado ese día.
El arreglo y su límite
Agregué un juez de contenido que decide, página por página, si lo recibido se puede leer o no, y clasifica lo que no: desafío, rechazo, página que no se pintó o vacía. Lo que no pasa ese juez nunca entra a la extracción ni genera un hallazgo. Si ninguna página es legible, el resultado es "revisar a mano", no una conclusión inventada.
- Cada página pasa por el juez antes de usarse para nada.
- La que no pasa se guarda en una lista aparte, con el motivo.
- Si no queda ninguna legible, el prospecto se marca para revisión manual.
- Solo entonces, y solo por esos motivos, se prueba un segundo rastreador (más abajo).
El límite real: el arreglo se probó con un caso capturado (7 KB de código, 61 caracteres de texto) y nunca pude volver a ver el bloqueo en vivo, porque era intermitente. Escribí 23 pruebas automáticas para esa versión, con los dos rastreadores simulados para que corran en segundos y sin tocar internet. Una limitación que descubrí al hacerlas: Crawl4AI bloquea los nombres de servidores internos por seguridad, así que no pude montar un sitio de prueba mío en la misma red y apuntarlo ahí. Por eso las pruebas simulan la respuesta en lugar de usar un servidor real.
La regla que saqué de esto sirve para cualquier herramienta que te devuelva un estado: "éxito" dice que la conexión funcionó, no que lo recibido sirve. Yo ahora mido el contenido, no el código de respuesta. Si el texto útil es mínimo comparado con el peso de la página, algo está mal aunque todo diga 200, y prefiero que el sistema diga que no sabe a que invente una conclusión con mucha seguridad.
Esa versión (la 1.3) quedó congelada el 21 de septiembre: sin funciones nuevas ni cambios de arquitectura en lo que ya existía. Una semana después añadí un endpoint de contactos aparte, sin tocar su lógica. Un defecto que cuesta confianza se arregla y se deja quieto.
Y Firecrawl, ¿para qué?
Cuando el juez dice "desafío" o "rechazo", entra Firecrawl, que es un servicio de otra empresa, con créditos. Es un recurso de último momento: con un tope de 3 llamadas por petición, y su respuesta pasa por el mismo juez. Si falla o no hay llave, el resultado vuelve a "revisar a mano". Nunca baja a un hallazgo inventado.
| Crawl4AI | Firecrawl | |
|---|---|---|
| Dónde corre | En mi servidor | En la nube de otra empresa |
| Cuándo lo uso | Siempre, primero | Solo si el juez rechaza la página |
| Qué me preocupó | Que dijera éxito en un desafío | El gasto: el 12 de octubre iba al 50 % de los créditos |
Una herramienta principal, y otra que solo se despierta cuando la primera falla de una forma que reconozco. Es lo más reutilizable de todo el montaje.
Lo que no sé
- Cuántos sitios me devuelven un desafío en un mes normal. Anoto la tasa de cada petición, pero no he hecho el análisis.
- Cómo se comporta con una versión nueva. Mis pruebas son de la 0.9.3 y no he subido de versión.
- La licencia. No la dejé anotada, así que míralo tú en el repo antes de usarlo con clientes.
- Si me conviene otro rastreador de respaldo. Dejé uno como candidato y lo decidiré con los números, no antes.
- Qué tan bien aguanta un sitio muy pesado. Mis pruebas fueron con páginas de negocios locales, pequeñas.
Revisa tus puertos
Si tienes algo corriendo en un servidor, esta es la revisión que más valor me dio de todo este montaje. Pega esto en Claude Code, dentro del servidor. Solo lee:
Revisa, sin cambiar nada, qué servicios de Docker tengo corriendo en este servidor y en qué dirección escucha cada uno. Para cada puerto publicado dime si escucha solo en 127.0.0.1 o en todas las interfaces (0.0.0.0), y si tiene algún tipo de contraseña o token. Marca en una lista corta los que estén abiertos al exterior sin autenticación.
Si te interesa cómo uso a los agentes para no repetir este tipo de errores, lee el post de Wick, el segundo repo de la serie.
