Katana es el crawler de ProjectDiscovery para descubrir endpoints: un binario en Go, con licencia MIT, que toma un objetivo y devuelve URLs y endpoints para que la siguiente herramienta de la cadena siga trabajando. Puede rastrear en modo HTTP sin navegador o con -headless, que usa Chromium. La documentación oficial presenta el modo headless como la opción con mayor cobertura; este caso de prueba muestra por qué el tipo de endpoint importa tanto como la cantidad.
Monté un sitio pequeño con tres clases de endpoints, a propósito distintas entre sí, y medí cuál encontraba cada modo en la v1.6.1 con -d 4. El HTML normal llegó a 4/4 enlaces y a la cadena completa de tres saltos en las cuatro configuraciones. La diferencia apareció en los endpoints expuestos a través del código JavaScript frente a los cambios en el DOM en tiempo de ejecución.
En este fixture, headless detectó la clase basada en DOM en tiempo real que los modos sin navegador probados pasaron por alto, mientras que el modo estándar con -jc encontró literales dentro de archivos JavaScript que ambos runs headless no vieron. Ninguna fila de la matriz de cuatro comandos cubrió las dos clases a la vez. El alcance, la reanudación y el comportamiento de known files marcaron los otros límites prácticos.
Qué es realmente katana
El crawler katana — projectdiscovery/katana en GitHub — está escrito en Go y tiene licencia MIT. Probé la versión v1.6.1 el 27 de julio de 2026; la versión importa porque los hallazgos de cobertura y known files que describo abajo dependen de la compilación.
Aquí la categoría pesa más de lo habitual. Un crawler de descubrimiento de endpoints no es una herramienta de extracción de campos. Si buscas un crawler web en Go que te devuelva nombres de producto y precios como JSON estructurado, katana no va por ese lado: te dirá encantado que /products/1138 existe, pero no te contará nada sobre lo que hay en esa página. Es intencional, y juzgarlo por extracción sería como reseñar un detector de metales por su capacidad para tasar joyas.
Su terreno natural es el reconocimiento para seguridad ofensiva y las cadenas de automatización: entra por STDIN, sale por URLs, y lo conectas con la siguiente herramienta. De ahí la advertencia obvia: todas las mediciones aquí se hicieron contra un fixture en 127.0.0.1 que escribí yo mismo. Usa katana solo en hosts que te pertenecen o para los que tengas autorización escrita de prueba, y nada más. Esto no va de evadir defensas; va de medir cuánto de la superficie de endpoints de un sitio enumera realmente un comando dado.
Los tres modos, y lo que cada uno ve
El modo estándar es un cliente HTTP en Go. Descarga, parsea HTML, sigue hrefs y nunca arranca un navegador. Es rápido, barato y ciego a todo lo que solo existe después de que se ejecuta JavaScript.
-jc (-js-crawl) añade un parser de JavaScript sobre esa ruta sin navegador. Descarga los .js enlazados y extrae literales con forma de URL del código fuente. No ejecuta nada: solo lee. También existe -jsl (jsluice), descrito en el README como un parser más pesado y con mayor consumo de memoria; no lo probé, así que no puedo decir si cambia el panorama de cobertura.

-headless controla Chromium y ejecuta los scripts de la página. En este fixture, fue el único modo de Katana probado que recuperó la ruta ensamblada con fragmentos e inyectada en el DOM en tiempo de ejecución. Ese resultado no demuestra lo que cualquier parser o una futura modalidad de Katana pudiera recuperar.
Luego está el modelo de alcance, que es la parte que yo me grabaría antes de escribir cualquier comando en producción.
| Flag | Qué controla | Valores / valor por defecto |
|---|---|---|
-fs (field scope) | qué hosts entran en juego | dn, rdn, fqdn o una regex personalizada; por defecto rdn |
-cs y -cos | regex de URLs que filtran dentro de ese field scope | — |
-kf | known files: robots.txt y sitemap.xml | el README indica que requiere una profundidad mínima de 3 |
-d | profundidad | por defecto 3 |
-resume | retoma un rastreo interrumpido | — |
El orden no es cosmético: determina si una regex de host amplía el rastreo o lo deja vacío sin avisar.
Configuración: un binario, un asterisco
Tres formas de instalarlo, y solo una necesita toolchain:
| Ruta de instalación | Requisito |
|---|---|
Desde el código fuente: go install github.com/projectdiscovery/katana/cmd/katana@latest | el requisito declarado es Go 1.25 o superior |
| Binarios precompilados en la página de releases | sin toolchain |
| Imagen Docker | sin toolchain |
El mío quedó en ~/go/bin/katana y reportó Current version: v1.6.1 en cada ejecución. Hasta ahí, la historia típica y agradable de Go: un archivo, sin runtime.
El asterisco está en headless, donde el navegador es un requisito aparte del binario:
Dónde se ejecuta -headless | Qué necesita |
|---|---|
| Mi máquina | katana detectó automáticamente un Chromium ya instalado; no registré la build del navegador ni pasé una ruta de browser |
| Un servidor vacío, según las instrucciones de Ubuntu del propio proyecto | apt install google-chrome-stable antes de que headless haga algo |
| La vía Docker | ejecuta headless con -system-chrome |
En un servidor sin nada, esa comodidad desaparece. Reserva presupuesto para un navegador, no solo para un binario, en cuanto -headless entre en tu línea de comandos.
Un detalle menor pero útil si lo usas en CI: katana hace una llamada de comprobación de versión a GitHub al arrancar. -duc la desactiva. En un portátil es ruido; en un runner aislado o con límites de rate es un viaje de ida y vuelta a la red que no pediste. En mis mediciones usé -duc para que los números midieran el rastreo y no el phone-home.
Cómo lo probé
Tres clases de endpoint, elegidas precisamente porque separan los modos. Todo vive en un servidor de fixture local, y la verdad de referencia se escribió antes de ejecutar cualquier rastreo, de modo que el recall se mide contra un conjunto fijo y no contra lo que katana haya impreso por casualidad.
- Clase A — HTML simple.
/page/a,/page/b,/page/c, más una cadena de tres saltos/depth/1 → /depth/2 → /depth/3. Cualquier crawler debería encontrarlos. - Clase B — literales en archivo JavaScript.
/api/js-endpoint-7y/api/js-endpoint-8solo existen como literales de cadena dentro de un/static/app.jsenlazado. Son legibles sin navegador, siempre que alguien se tome la molestia de leer el JS. - Clase C — solo DOM en tiempo de ejecución. Una ruta ensamblada en ejecución a partir de fragmentos (
'endpoint' + (6 * 7)) e inyectada en el DOM por script. La cadena/runtime-only/endpoint42nunca aparece contigua en ningún byte que envíe el servidor: ni en el HTML ni en el código fuente del JS. Solo la ejecución la revela.
Además, un robots.txt, un sitemap.xml con dos endpoints <loc> que no aparecen en ningún otro sitio, una ruta que devuelve 500, un enlace roto y un enlace fuera de alcance que apunta a un segundo servidor en un hostname distinto.
El instrumento importa tanto como el fixture: el servidor cuenta lo que realmente se pidió, así que las afirmaciones sobre alcance y resume se basan en la verdad de los hits, no en el stdout de katana. Los runs en bruto están en el repositorio del benchmark por si quieres revisar mis cuentas.
La división de cobertura que nadie cuantifica

La matriz, modo por clase de endpoint, con -d 4:
| Modo | Enlaces HTML (A) | Cadena de profundidad (A) | Literales en JS (B) | DOM en tiempo de ejecución (C) |
|---|---|---|---|---|
| standard | 4/4 | 3/3 | 0/2 | no encontrado |
standard -jc | 4/4 | 3/3 | 2/2 | no encontrado |
-headless | 4/4 | 3/3 | 0/2 | encontrado |
-headless -jc | 4/4 | 3/3 | 0/2 | encontrado |
Lee las dos últimas columnas como un par y el problema salta a la vista. La clase B solo la encontró una configuración: el modo estándar con -jc. La clase C la encontraron dos: ambos runs headless. No hay ninguna fila con aciertos en ambas columnas. La matriz completa está en discovery-summary.json, donde el campo calculado headless_jc_covers_both da false.
La consecuencia práctica es que “usa headless y ya tendrás más cobertura” era una conclusión incompleta para esta prueba. Headless no añadió la clase B encima del resultado estándar; recuperó la clase C mientras se perdía la clase B. Cubrir todas las clases plantadas en este fixture requirió dos rastreos y una combinación posterior:
katana -u https://target.example -jc -d 4 -silent -o pass-jc.txt
katana -u https://target.example -headless -d 4 -silent -o pass-headless.txt
sort -u pass-jc.txt pass-headless.txt > endpoints.txt
La fila -headless -jc es la que más me gustaría que alguien del upstream explicara. Añadir el parser de JavaScript al run headless no aportó nada nuevo: siguió en 0/2 para la clase B en cada ejecución, incluso en una reproducción limpia. Estoy reportando el comportamiento, no afirmando haber rastreado el mecanismo; no instrumenté el interior de katana para averiguar por qué la ruta con navegador deja de contribuir literales de archivos JS. Tómalo como una observación reproducible y como un buen issue en GitHub, no como un diagnóstico. (Y un apunte: la combinación -hl -jc terminó correctamente con código de retorno 0 en v1.6.1 sobre macOS ARM, algo que históricamente no siempre ha sido así.)
La documentación oficial describe headless como una forma de obtener mejor cobertura, y en este caso sí la dio para la clase renderizada en runtime. La guía revisada no explicaba esta separación entre literales de fuente y DOM en tiempo de ejecución, así que conviene tratar la matriz como una razón para probar ambas rutas con tus propias clases de endpoints, no como una taxonomía universal.
Cuánto cuesta headless en tiempo real
Tres ejecuciones secuenciales por modo en una máquina sin carga relevante:
| Modo | p50 | mínimo–máximo | media |
|---|---|---|---|
| standard | 13.08s | 13.07–13.17s | 13.11s |
-headless | 66.82s | 66.78–67.68s | 67.09s |
Eso da una relación de 5.1x, con rangos que ni siquiera se acercan a solaparse: mi ejecución estándar más lenta (13.17s) seguía superando a mi ejecución headless más rápida (66.78s) por más de 53 segundos (cost-summary.json). Esto no es ruido de medición.
Una salvedad sobre esos 13 segundos: mi fixture incluye a propósito una ruta 500 y un enlace roto, y el modo estándar espera el tramo de reintento por defecto de -timeout 10 en ambos casos. No afiné el timeout para favorecer al modo rápido, lo que significa que un estándar ajustado probablemente ampliaría la diferencia, no la reduciría.
La relación es una señal de capacidad local, no una previsión de producción. Los objetivos reales varían en latencia, fallos, trabajo de scripts y planificación, mientras que este fixture incluye un tramo de timeout por defecto. Usa la diferencia medida de 5.1x para decidir si headless merece un presupuesto y un subconjunto de objetivos separados, y luego valida ese plan en hosts representativos y autorizados.
El alcance se mantuvo, pero un flag no hizo nada en silencio
La prueba de alcance usó dos servidores: el principal en 127.0.0.1 y un segundo accesible como localhost en otro puerto, sirviendo una ruta que existe solo allí. Así que un hit en esa ruta demuestra que el host fuera de alcance sí se pidió de verdad, no que solo se imprimió.
| Configuración | ¿Se pidió el host fuera de alcance? | Hits en el segundo servidor |
|---|---|---|
valor por defecto (-fs rdn) | no | 0 |
-fs fqdn | no | 0 |
-cs localhost | no | 0 |
| `-fs '(127.0.0.1 | localhost)'` | sí |
La disciplina del scope es una buena noticia: por defecto katana se quedó en casa, y hubo que hacer algo explícito para ampliarlo. Ese es el comportamiento correcto para una herramienta que se usa sobre infraestructuras ajenas.
La fila interesante es -cs localhost. No amplió el rastreo al segundo host, y además no emitió ninguna URL. Como -cs filtra dentro del field scope, y el field scope seguía siendo el host principal, la regex no encontró nada y el rastreo devolvió un conjunto vacío en lugar de un error. Si alguna vez escribiste una regex de scope para un host que querías incluir y te quedaste mirando un archivo de salida vacío, ese es el mecanismo (scope-summary.json). Para añadir un host, usa -fs. Para acotar dentro de hosts que ya tienes, usa -cs/-cos.
Resume es más grueso de lo que sugiere el flag
El README describe el flag como -resume string resume scan using resume.cfg, lo que suena a un archivo dejado en el directorio de trabajo. No es así. En mi máquina, el checkpoint se escribió en ~/.config/katana/resume-<xid>.cfg: medido, no leído en una página de docs, porque la documentación no especifica una ruta.
Lo que contiene es la sorpresa más importante. El archivo tenía un mapa InFlightUrls con exactamente una cosa: la URL semilla. No el conjunto ya visitado, ni la frontera. Esto fue lo que pasó cuando interrumpí un rastreo con SIGINT tras tres segundos y luego lo reanudé:
| Ejecución | Rutas distintas |
|---|---|
| Rastreo base completo | 11 |
| Obtenidas antes de la interrupción | 10 |
| Vuelvas a obtener por el run de resume | las 11, incluidas las 10 que ya habían terminado |
Resume llegó al mismo conjunto final de endpoints, así que nada está roto. Pero la granularidad del checkpoint es por semilla de entrada, no por URL: el filtro de deduplicación en memoria nunca se persiste, así que reanudar un rastreo de una sola semilla vuelve a rastrearlo desde cero (resume-summary.json). Si le das a katana una lista de 500 hosts, resume debería ahorrarte los que terminaron por completo; ese comportamiento con múltiples semillas se deduce de cómo se guarda el estado, pero yo solo medí el caso de una sola semilla. Si estás muy metido en un sitio enorme, resume te aporta corrección, no tiempo.
Known files: pidió, pero luego dejó caer

-kf all -d 3 sí pidió ambos archivos — robots.txt y sitemap.xml aparecieron en el registro de hits del servidor — y luego recuperó 0 de 2 de los endpoints listados en los elementos <loc> de ese sitemap. Recall 0.0.
Antes de llamar a eso una limitación, intenté asegurarme de que el fallo no fuera mío. Cada variación devolvió lo mismo:
| Variación probada | Endpoints <loc> del sitemap recuperados |
|---|---|
-kf all | 0/2, recall 0.0 |
-kf sitemapxml | 0/2, recall 0.0 |
-kf robotstxt | 0/2, recall 0.0 |
| profundidad 3 | 0/2, recall 0.0 |
| profundidad 4 | 0/2, recall 0.0 |
| profundidad 5 | 0/2, recall 0.0 |
con -jc añadido | 0/2, recall 0.0 |
semilla directa en /sitemap.xml | 0/2, recall 0.0 |
El requisito documentado — usar -kf y llegar al menos a profundidad 3 — se cumplió siempre. No estamos ante un flag que faltaba.
La decisión útil viene antes: en este fixture con IP literal, no asumas que pedir known files significa que las URLs de sus <loc> se incorporaron al rastreo. Verifica el recall, o extrae y siembra esas URLs tú mismo.
La ruta de código de la v1.6.1 es coherente con lo observado, aunque no la instrumenté durante la ejecución. En sitemapxml.go en v1.6.1, NewNavigationRequestURLFromResponse construye peticiones de navegación desde <loc> sin un RootHostname poblado. La petición pasa luego por ValidateScope; en scope.go en v1.6.1, la rama para IP literal compara el host de la URL con ese root vacío y puede rechazarla. El comando personalizado -fs '(127.0.0.1|localhost)' tomó una rama distinta de scope en otra prueba, así que es un rescate predecible por el código, no un workaround medido para -kf. Un intento de confirmación se vio bloqueado por un dialer intermitente en el cliente de known files en este host; por eso el resultado reportado sigue siendo 0/2.
Lo que yo haría de verdad en producción, hasta que alguien confirme el flag: pedir el sitemap por tu cuenta, extraer las URLs <loc> y pasárselas a katana como lista de semillas. Dos líneas de shell, sin validación de scope de por medio.
Hay algo que sí se comportó exactamente como se anuncia y merece una frase: la ruta 500 y el enlace roto se solicitaron, se registraron y se saltaron. Cada run sin navegador terminó con código de retorno 0. Un crawler que muere ante la primera mala respuesta no sirve desatendido, y katana no hace eso.
Una comprobación de cobertura específica del objetivo antes del despliegue
La matriz del fixture sirve mejor como plantilla para probar tus propios objetivos autorizados. Define las clases de endpoint antes de correr Katana: enlaces simples, literales en scripts enlazados, rutas que solo aparecen tras ejecutar código y entradas de known files son cuatro cubos de partida razonables. Guarda una muestra pequeña de verdad de referencia para cada clase. Sin esa lista previa, un stdout más grande puede parecer mejor cobertura aunque una clase haya desaparecido.
Ejecuta primero las rutas sin navegador y con navegador como mediciones separadas. Guarda los comandos exactos, la versión de Katana, la build del navegador, los códigos de retorno y las salidas. Normaliza y compara los conjuntos de endpoints en lugar de comparar el número de líneas. Si -jc en modo estándar no aporta nada único en tu muestra, quizá baste con una política solo headless; si los conjuntos divergen como aquí, mantén los dos pases separados y combínalos después de recolectar. No des por hecho que añadir ambos flags a un único comando equivale a la unión hasta que un diff específico del objetivo lo demuestre.
Valida el scope con evidencia ajena a la salida de Katana. Pon una URL señuelo en un host que deba quedar excluido e inspecciona el log de peticiones de ese servidor. También prueba un segundo host previsto si el rastreo debe ampliarse. El run -cs localhost aquí produjo una salida vacía porque el filtrado de contenido no amplió el field scope; la invocación personalizada -fs '(127.0.0.1|localhost)' sí contactó con el segundo servidor. Registrar la expresión regular exacta importa porque un cambio de un solo carácter puede alterar la regex y no solo su presentación.
Prueba la interrupción y known files por separado del recall de descubrimiento. Para resume, interrumpe una semilla representativa tras varias páginas, guarda la ruta del checkpoint generado y cuenta cuántas URLs ya completadas se vuelven a pedir. Para -kf, confirma tanto que robots/sitemap se solicitaron como que las URLs <loc> plantadas se programaron de verdad. Son afirmaciones distintas. En este fixture, los archivos se descargaron mientras faltaban los dos endpoints del sitemap, así que tanto los logs del servidor como la salida de endpoints fueron necesarios para ver el límite.
Por último, establece una línea base de coste local con ejecuciones secuenciales en una máquina sin actividad y repítelo en hosts representativos. Conserva mínimo, máximo y mediana, no solo un multiplicador. El 5.1x de aquí incluye el comportamiento de fallos y timeouts del fixture; te dice que headless merece su propio presupuesto, no cuánto tardará un inventario en producción.
Pros y contras
Pros:
- Recall perfecto sobre HTML normal en todos los modos: 4/4 enlaces y la cadena completa de 3/3 saltos, sin configuración especial.
-jcfunciona de verdad sin navegador: recuperó 2/2 endpoints desde literales en un JS enlazado, sin coste de browser.-headlesses lo único que encontró un endpoint ensamblado en tiempo de ejecución, una clase invisible por construcción para el parsing de fuente.- Los valores de scope por defecto son conservadores. El host fuera de alcance nunca se pidió con los valores por defecto, con
-fs fqdnni con-cs. - Un solo binario en Go, licencia MIT, builds precompiladas e imagen Docker, con I/O en forma de pipeline.
- Robusto ante fallos: los 500 y los enlaces rotos no detienen el rastreo.
Contras:
- Ninguna invocación única cubrió a la vez endpoints de archivos JS y de DOM en runtime. La cobertura total necesita dos ejecuciones y un merge.
-jcno aportó nada bajo-headless: 0/2 en la clase B en cada run headless.- Headless cuesta 5.1x en tiempo real (66.82s frente a 13.08s en p50, con rangos no solapados).
-resumevuelve a rastrear páginas ya completadas dentro de una semilla. Restaura el conjunto de endpoints, no el tiempo invertido.- Known files pidió robots.txt y sitemap.xml pero recuperó 0/2 endpoints
<loc>del sitemap contra un objetivo IP. - Headless requiere silenciosamente un Chromium en la máquina; la historia de “un solo binario” termina en el navegador.
- Solo descubrimiento. Sin extracción estructurada, sin conversión de contenido, sin esquema de campos.
No probado, y por tanto fuera de lo que cubren estos números: -jsluice, una prueba dedicada de corte de profundidad en -d 1/-d 2, resume con múltiples semillas, auto-relleno de formularios y cualquier sitio real de producción con mucho JavaScript o protegido. Todos los números provienen de una sola máquina (macOS arm64) sobre un fixture local.
Para quién sirve y quién debería saltárselo
Si tu trabajo consiste en producir un inventario de endpoints de infraestructuras que estás autorizado a tocar, katana tiene la forma de pipeline adecuada: tuberías STDIN/STDOUT, un binario distribuible y modos tanto sin navegador como con navegador. El merge en dos pases fue necesario para las clases plantadas en este fixture; si tus objetivos necesitan ambos pases es algo que debes comprobar con páginas representativas.
Sáltatelo si quieres datos y no direcciones. Katana nunca te dará una tabla de productos; te entrega las URLs donde podrían vivir los productos, y otra herramienta hace la extracción. Sáltatelo también si necesitas que un solo comando lo haga todo: el merge en dos pases va bien en una cadena de herramientas, pero es incómodo en un prompt. Y si tu enumeración se apoya en endpoints <loc> del sitemap mientras apuntas a IPs, verifica lo que realmente estás recibiendo antes de confiar en el resultado, porque en mi fixture esa ruta no devolvió nada.
Alternativas y el límite de la extracción
Katana es gratuito, con licencia MIT y autoalojado. Mantiene el descubrimiento, la elección del modo, el despliegue del navegador y el merge de resultados de tu lado de la frontera.
Dentro del open source, la comparación útil es por tarea, no por lenguaje. Colly es la otra opción en Go, pero es una librería que compilas con tus propios callbacks y no renderiza JavaScript en absoluto. Crawl4AI ejecuta un navegador real y produce Markdown para pipelines de LLM, que es otra salida completamente distinta. Si estás valorando varias a la vez, nuestro resumen de scrapers open source pone las categorías una al lado de la otra.
Divulgación: Thunderbit es el producto del editor y no fue probado en este fixture de Katana. Se sitúa aguas abajo en la categoría de extracción gestionada, convirtiendo páginas en texto o registros estructurados en lugar de enumerar la superficie de endpoints de un objetivo autorizado. Un flujo puede usar ambas categorías, pero esta reseña aporta evidencia solo sobre el comportamiento de descubrimiento de Katana.
Prueba Thunderbit para extraer datos web
Veredicto
Usa katana cuando el entregable sea una lista de endpoints para objetivos que estás autorizado a rastrear y puedas validar la cobertura de modo contra esos objetivos. En este fixture, -jc estándar recuperó los literales de JavaScript plantados, mientras que headless recuperó el endpoint insertado en el DOM en runtime; los modos browserless probados de Katana no recuperaron esa ruta en tiempo de ejecución. El scope por defecto también mantuvo sin pedir el segundo host, y los runs sin navegador siguieron adelante tras el 500 y el enlace roto.
Las advertencias son operativas: puede hacer falta un merge en dos pases para clases mixtas de endpoints, headless tardó alrededor de cinco veces más en tiempo real local, resume de una sola semilla volvió a pedir rutas ya completadas, y el recall de known files fue 0/2 contra el objetivo IP. Son resultados del fixture v1.6.1, no garantías sobre cualquier sitio. Pero sí bastan para definir las comprobaciones que una evaluación de producción debería repetir.
Prueba Thunderbit para extraer datos web Get Started Free
Preguntas frecuentes
¿Dónde guarda katana su archivo de resume, y reanudar salta las páginas que ya rastreé?
El checkpoint quedó en ~/.config/katana/resume-<xid>.cfg, no en un resume.cfg del directorio de trabajo como da a entender la ayuda del flag. Y no, no salta las páginas completadas: el archivo solo guarda URLs semilla en curso, así que al reanudar un rastreo de una sola semilla se volvieron a pedir las 11 rutas base, incluidas las 10 ya terminadas. Obtienes el mismo conjunto final de endpoints, pero no el tiempo ahorrado.
¿Por qué -kf all pidió mi sitemap.xml pero no rastreó las URLs que contiene?
Contra un objetivo IP, eso es un límite de validación de scope, no un error de flag. El parser de sitemap de Katana construye cada petición <loc> sin arrastrar el RootHostname, y luego la comprobación de scope DNS para hosts literales en IP compara el host de la URL con ese root vacío, falla la comparación y descarta la URL. El recall se mantuvo en 0 en todas las variaciones de flag, profundidad y semilla que probé. Una regex de host personalizada con -fs toma otra rama de validación y es la solución que el código sugiere, pero no pude confirmarla con -kf en mi máquina, así que tómala como no probada. Extraer tú mismo las URLs <loc> y sembrar katana con ellas es hoy la ruta en la que confiaría.
¿Qué debería conservar al reportar una prueba de cobertura de Katana?
Registra la versión exacta de Katana y el comando, incluida la expresión -fs byte a byte; define las clases de endpoint antes de correr; conserva los logs de hits del lado del servidor además del stdout; y separa el comportamiento medido de las hipótesis basadas en código fuente. En ejecuciones headless, registra también la build del navegador; esta prueba no lo hizo, lo que limita la reproducibilidad.
¿Debería usar -jc, -headless o ambos?
Elige según las clases de endpoint que necesitas. En este fixture, -jc estándar encontró literales guardados en un archivo JavaScript, mientras que headless encontró el endpoint insertado en el DOM en runtime. Ningún modo cubrió ambas clases por sí solo, así que un run en dos pases seguido de deduplicación fue la opción defendible para objetivos mixtos.
¿Una URL fallida detendrá el rastreo? En esta ejecución controlada, no. Katana siguió después de una respuesta 500 y de un enlace roto, y aun así devolvió las otras rutas alcanzables. Eso no sustituye el control de errores en producción: conserva logs de peticiones fallidas y define una tasa de fallo aceptable para que un rastreo parcialmente exitoso no se confunda con cobertura completa.


