Katana è il crawler di discovery degli endpoint di ProjectDiscovery: un binario in Go con licenza MIT che prende un target e restituisce URL ed endpoint da passare allo strumento successivo in pipeline. Può fare il crawling in modalità HTTP senza browser oppure con -headless, che usa Chromium. La guida ufficiale presenta la modalità headless come quella con copertura più ampia; questo test mostra che il tipo di endpoint conta tanto quanto il numero totale.
Ho costruito un piccolo sito con tre classi di endpoint volutamente diverse e ho misurato quale modalità individuasse ciascuna di esse su v1.6.1 con -d 4. L’HTML normale ha raggiunto 4/4 link e l’intera catena a tre salti in tutte e quattro le configurazioni. La differenza è emersa negli endpoint esposti tramite sorgente JavaScript rispetto a quelli creati da cambiamenti del DOM a runtime.
Su questo test, la modalità headless ha trovato la classe basata sul DOM a runtime che le modalità senza browser non vedevano, mentre la modalità standard con -jc ha trovato i literal nei file JavaScript che entrambe le esecuzioni headless hanno mancato. Nessuna riga della matrice con quattro comandi copriva entrambe le classi. Ambito, resume e comportamento dei known files hanno fornito gli altri confini pratici.
Cos’è davvero Katana
Il crawler Katana — projectdiscovery/katana su GitHub — è scritto in Go e distribuito con licenza MIT. Ho testato v1.6.1 il 27 luglio 2026; la versione conta perché i risultati su copertura e known files riportati qui sono osservazioni legate a quella build.
Qui conta più del solito anche l’etichetta di categoria. Un crawler di discovery degli endpoint non è uno strumento di estrazione dati. Se ti serve un web crawler in Go che ti restituisca nomi prodotto e prezzi come JSON strutturato, Katana è proprio lo scaffale sbagliato: ti dirà volentieri che esiste /products/1138, ma non saprà dirti nulla di ciò che c’è in quella pagina. È una scelta progettuale, e giudicarlo come estrattore sarebbe come recensire un metal detector in base alla sua capacità di valutare gioielli.
Il suo terreno naturale è il recon per offensive security e l’automazione in pipeline: STDIN in ingresso, URL in uscita, da collegare allo strumento successivo. Da qui la cautela più importante: tutte le misurazioni qui sono state fatte su un fixture in 127.0.0.1 che ho scritto io. Usa Katana solo su host che possiedi o per cui hai autorizzazione esplicita al test, e nient’altro. Non si tratta di eludere difese: si tratta di capire quanta superficie di endpoint un certo comando riesca davvero a enumerare.
Le tre modalità e cosa vede ciascuna
La modalità standard è un client HTTP in Go. Recupera le pagine, analizza l’HTML, segue gli href e non avvia mai un browser. È veloce, economica e cieca verso tutto ciò che esiste solo dopo l’esecuzione di JavaScript.
-jc (-js-crawl) aggiunge un parser JavaScript al percorso senza browser. Scarica i file .js collegati ed estrae dal sorgente le stringhe che sembrano URL. Nessuna esecuzione, solo lettura. Esiste anche -jsl (jsluice), descritto nel README come un parser più pesante e più esigente in memoria — non l’ho testato, quindi non posso dire se cambi il quadro della copertura.

-headless avvia Chromium ed esegue gli script della pagina. In questo fixture, è stata l’unica modalità Katana testata a recuperare il percorso costruito da frammenti e inserito nel DOM a runtime. Questo risultato non dimostra ciò che ogni parser o futura modalità di Katana potrebbe recuperare.
Poi c’è il modello di scope, che è la parte che vorrei fissare bene prima di digitare qualsiasi cosa in produzione.
| Flag | Cosa controlla | Valori / predefinito |
|---|---|---|
-fs (field scope) | quali host sono inclusi | dn, rdn, fqdn oppure una regex personalizzata — predefinito rdn |
-cs e -cos | regex sugli URL che filtrano all’interno di quello scope | — |
-kf | known files: robots.txt e sitemap.xml | il README indica che serve una profondità minima di 3 |
-d | profondità | predefinito 3 |
-resume | riprende un crawl interrotto | — |
L’ordine non è cosmetico: decide se una regex sugli host amplia davvero il crawl oppure lo svuota in silenzio.
Setup: un binario, una sola eccezione
Tre modi per installarlo, ma solo uno richiede una toolchain:
| Modalità di installazione | Prerequisito |
|---|---|
Da sorgente: go install github.com/projectdiscovery/katana/cmd/katana@latest | è richiesto Go 1.25 o successivo |
| Binari precompilati nella pagina release | nessuna toolchain |
| Immagine Docker | nessuna toolchain |
La mia installazione è finita in ~/go/bin/katana e ha riportato Current version: v1.6.1 a ogni esecuzione. Fin qui, la classica buona storia del mondo Go: un file, niente runtime.
L’eccezione riguarda headless, dove il browser è un prerequisito separato dal binario:
Dove gira -headless | Cosa serve |
|---|---|
| Sulla mia macchina | Katana ha rilevato automaticamente un Chromium già installato; non ho registrato la build del browser né indicato un percorso browser |
| Su un server minimale, secondo le istruzioni Ubuntu del progetto | installare prima apt install google-chrome-stable, altrimenti headless non fa nulla |
| Con Docker | headless gira con -system-chrome |
Su un server vuoto, quella comodità sparisce. Metti in conto un browser, non solo un binario, nel momento in cui -headless entra nella riga di comando.
Un dettaglio piccolo ma utile se lo esegui in CI: Katana fa una chiamata di controllo versione a GitHub all’avvio. -duc la disattiva. Su un laptop è rumore; su un runner isolato o con rate limit è un round-trip di rete per esecuzione che non hai chiesto. I miei test di timing usano -duc, così i numeri misurano il crawling e non il “phone home”.
Come l’ho testato
Tre classi di endpoint, scelte apposta perché separano le modalità. Tutto vive in un local fixture server, e la ground truth è stata scritta prima di ogni crawl, così il recall viene misurato contro un insieme fisso e non contro ciò che Katana ha stampato per caso.
- Classe A — HTML semplice.
/page/a,/page/b,/page/c, più una catena a tre salti/depth/1 → /depth/2 → /depth/3. Qualunque crawler dovrebbe prenderli. - Classe B — literal in file JavaScript.
/api/js-endpoint-7e/api/js-endpoint-8esistono solo come stringhe dentro un/static/app.jscollegato. Sono leggibili senza browser, se qualcosa si prende la briga di leggere il JS. - Classe C — solo DOM a runtime. Un percorso costruito al runtime da frammenti (
'endpoint' + (6 * 7)) e inserito nel DOM dallo script. La stringa/runtime-only/endpoint42non compare mai in modo contiguo in nessun byte inviato dal server: non nell’HTML, non nel sorgente JS. Solo l’esecuzione la rivela.
In più c’erano un robots.txt, una sitemap.xml con due endpoint <loc> che non compaiono altrove, una route che restituisce 500, un link morto e un link fuori scope che punta a un secondo server su un hostname diverso.
Anche lo strumento di misura conta quanto il fixture: il server registra ciò che è stato davvero richiesto, quindi le affermazioni su scope e resume si basano sulla verità dei request hit e non sullo stdout di Katana. Le esecuzioni grezze sono nel repo benchmark se vuoi verificare i conti.
Lo split di copertura che quasi nessuno quantifica

La matrice, modalità per classe di endpoint, con -d 4:
| Modalità | Link HTML (A) | Catena depth (A) | Literal JS-file (B) | Runtime-DOM (C) |
|---|---|---|---|---|
| standard | 4/4 | 3/3 | 0/2 | non trovato |
standard -jc | 4/4 | 3/3 | 2/2 | non trovato |
-headless | 4/4 | 3/3 | 0/2 | trovato |
-headless -jc | 4/4 | 3/3 | 0/2 | trovato |
Leggi le ultime due colonne come coppia e il problema salta fuori. La classe B è stata trovata da una sola configurazione: modalità standard con -jc. La classe C è stata trovata da due configurazioni: entrambe le esecuzioni headless. Non esiste una riga con un hit in entrambe le colonne. La matrice completa è in discovery-summary.json, dove il campo calcolato headless_jc_covers_both risulta false.
La conseguenza pratica è che “usa semplicemente headless per avere più copertura” è una formula incompleta per questo test. Headless non ha aggiunto la classe B sopra il risultato standard; ha recuperato la classe C ma ha perso la classe B. Coprire tutte le classi piantate in questo fixture ha richiesto due crawl e una fusione:
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 riga -headless -jc è quella su cui vorrei maggiormente una risposta upstream. Aggiungere il parser JavaScript al run headless non ha recuperato nulla: ancora 0/2 sulla classe B, in ogni esecuzione, inclusa una riproduzione fresca. Sto riportando il comportamento, non sostenendo di averne tracciato il meccanismo; non ho strumentato gli interni di Katana per scoprire perché il percorso browser smette di contribuire con i literal dei file JS. Consideralo un’osservazione riproducibile e un buon issue GitHub, non una diagnosi. (Vale la pena notare anche che la combinazione -hl -jc si è completata correttamente con return code 0 su v1.6.1 su macOS ARM, cosa che storicamente non è sempre stata vera.)
La documentazione ufficiale descrive headless come una modalità che offre una copertura migliore, e in questo fixture è stato vero per la classe renderizzata a runtime. La guida verificata non spiegava questo split tra source literal e DOM runtime, quindi considera la matrice un motivo per testare entrambi i percorsi sulle tue classi di endpoint, non una tassonomia universale.
Quanto costa headless in tempo reale
Tre esecuzioni consecutive per modalità su una macchina altrimenti inattiva:
| Modalità | p50 | min–max | media |
|---|---|---|---|
| standard | 13.08s | 13.07–13.17s | 13.11s |
-headless | 66.82s | 66.78–67.68s | 67.09s |
È un rapporto di 5,1x, con intervalli che non si sovrappongono nemmeno lontanamente: la mia esecuzione standard più lenta (13.17s) ha comunque battuto la mia esecuzione headless più veloce (66.78s) di oltre 53 secondi (cost-summary.json). Non è rumore di misura.
Una nota su quei 13 secondi: il mio fixture include volutamente una route 500 e un link morto, e la modalità standard resta bloccata sul tail di retry del default -timeout 10 su entrambi. Non ho “taroccato” il timeout per far sembrare più veloce la modalità standard, il che significa che un run standard ottimizzato probabilmente amplierebbe il divario invece di ridurlo.
Il rapporto è un segnale di capacità locale, non una previsione di produzione. I target reali variano per latenza, errori, lavoro degli script e scheduling, mentre questo fixture include la coda di timeout predefinita. Usa la differenza misurata di 5,1x per decidere se headless merita un budget e un sottoinsieme di target separati, poi verifica quel piano su host rappresentativi e autorizzati.
Scope rispettato, ma un flag in silenzio non ha fatto nulla
Il test di scope usava due server: il principale su 127.0.0.1 e un secondo raggiungibile come localhost su una porta diversa, che serviva un percorso che esiste solo lì. Quindi un hit su quel percorso è la prova che l’host fuori scope è stato davvero richiesto, non soltanto stampato.
| Configurazione | Host fuori scope richiesto? | Hit sul secondo server |
|---|---|---|
default (-fs rdn) | no | 0 |
-fs fqdn | no | 0 |
-cs localhost | no | 0 |
| `-fs '(127.0.0.1 | localhost)'` | sì |
La disciplina dello scope è una buona notizia: di default Katana è rimasto a casa, e ci è voluto un atto esplicito per allargare. È il comportamento giusto per uno strumento che viene puntato su infrastrutture di altri.
La riga interessante è -cs localhost. Non ha ampliato il crawl al secondo host — e in più ha emesso zero URL in assoluto. Poiché -cs filtra all’interno dello field scope e il field scope era ancora quello dell’host primario, la regex non ha corrisposto a nulla e il crawl ha restituito un insieme vuoto invece di un errore. Se hai mai scritto una regex di scope indicando un host che volevi includere e hai fissato un file di output vuoto, questo è il meccanismo (scope-summary.json). Per aggiungere un host, imposta -fs. Per restringere dentro host che hai già incluso, usa -cs/-cos.
Resume è più grossolano di quanto faccia pensare il flag
Nel README il flag è descritto come -resume string resume scan using resume.cfg, e fa pensare a un file lasciato nella directory di lavoro. Non è così. Sulla mia macchina il checkpoint è stato scritto in ~/.config/katana/resume-<xid>.cfg — misurato, non letto da una pagina di documentazione, perché la documentazione non indica un path.
Il contenuto è la sorpresa più importante. Il file conteneva una mappa InFlightUrls con esattamente una cosa: l’URL seed. Non l’insieme visitato, non il fronte di crawl. Ecco cosa è successo quando ho interrotto un crawl con SIGINT dopo tre secondi e poi l’ho ripreso:
| Esecuzione | Percorsi distinti |
|---|---|
| Crawl baseline completo | 11 |
| Recuperati prima dell’interruzione | 10 |
| Rifetchati dall’esecuzione di resume | tutti gli 11, inclusi i 10 già completati |
Resume ha raggiunto lo stesso set finale di endpoint, quindi nulla è rotto. Ma la granularità del checkpoint è per seed di input, non per URL: il filtro di deduplica in memoria non viene mai persistito, quindi riprendere un crawl a seed singolo fa ripartire da zero quel seed (resume-summary.json). Se dai a Katana una lista di 500 host, resume dovrebbe salvarti gli host completati fino in fondo; questo comportamento multi-seed deriva dal modo in cui lo stato viene salvato, ma io ho misurato solo il caso a seed singolo. Se sei dentro un sito enorme, resume ti compra correttezza, non tempo.
Known files: richiesti, poi abbandonati

-kf all -d 3 ha richiesto entrambi i file — robots.txt e sitemap.xml sono comparsi nel log degli hit del server — e poi ha recuperato 0 su 2 degli endpoint elencati negli elementi <loc> della sitemap. Recall 0.0.
Prima di considerarlo un limite, ho provato a far sì che fosse colpa mia. Ogni variazione ha recuperato la stessa cosa:
| Variazione provata | Endpoint <loc> della sitemap recuperati |
|---|---|
-kf all | 0/2, recall 0.0 |
-kf sitemapxml | 0/2, recall 0.0 |
-kf robotstxt | 0/2, recall 0.0 |
| depth 3 | 0/2, recall 0.0 |
| depth 4 | 0/2, recall 0.0 |
| depth 5 | 0/2, recall 0.0 |
con -jc aggiunto | 0/2, recall 0.0 |
seed diretto su /sitemap.xml | 0/2, recall 0.0 |
Il requisito documentato — usare -kf, andare almeno a profondità tre — è stato rispettato ogni volta. Non è una storia di flag mancanti.
La decisione utile viene prima: su questo fixture con IP literal, non dare per scontato che richiedere i known files significhi che i loro URL <loc> siano entrati nel crawl. Verifica il recall oppure estrai quei URL e passali tu come seed.
Il percorso di codice di v1.6.1 è coerente con questa osservazione, ma non l’ho strumentato durante l’esecuzione. In sitemapxml.go su v1.6.1, NewNavigationRequestURLFromResponse costruisce richieste di navigazione per i <loc> a partire da una risposta senza RootHostname popolato. La richiesta poi passa a ValidateScope; in scope.go su v1.6.1, il ramo per IP literal confronta l’host dell’URL con quel root vuoto e può respingerlo. Il comando custom -fs '(127.0.0.1|localhost)' ha preso un ramo di scope diverso in un test separato, quindi è un rescue previsto dal sorgente, non un workaround misurato per -kf. Un tentativo di conferma è stato bloccato da dialing intermittente nel client known-files su questo host; perciò il risultato riportato resta 0/2.
Quello che farei davvero in produzione, finché qualcuno non conferma il flag: scaricare io stesso la sitemap, estrarre gli URL <loc> e passarli a Katana come lista di seed. Due righe di shell, senza validazione dello scope.
Una cosa però si è comportata esattamente come promesso e merita una frase: la route 500 e il link morto sono stati richiesti, loggati e superati. Ogni run senza browser è terminato con return code 0. Un crawler che si blocca al primo errore è inutile se lasciato incustodito, e Katana non lo fa.
Un controllo di copertura specifico per target prima del deploy
La matrice del fixture è più utile come modello per testare i tuoi target autorizzati. Definisci le classi di endpoint prima di eseguire Katana: link semplici, literal in script collegati, route create solo dopo l’esecuzione e voci known-files sono quattro bucket di partenza ragionevoli. Tieni per ciascuna classe un piccolo campione di ground truth. Senza quella lista preventiva, un output stdout più grande può sembrare una copertura migliore anche quando una classe è sparita.
Esegui prima come misurazioni separate i percorsi senza browser e quelli headless. Salva i comandi esatti, la versione di Katana, la build del browser, i return code e gli output. Normalizza e fai il diff degli insiemi di endpoint invece di confrontare il numero di righe. Se lo standard -jc non aggiunge nulla di unico nel tuo campione, una policy solo headless può bastare; se gli insiemi divergono come qui, mantieni distinte le due passate e fai il merge dopo la raccolta. Aggiungere entrambi i flag in un solo comando non va considerato equivalente all’unione finché un diff specifico sul target non lo dimostra.
Valida lo scope con prove esterne all’output di Katana. Metti una canary URL su un host che dovrebbe restare escluso e controlla il log richieste di quel server. Prova anche un secondo host voluto, se il crawl deve allargarsi. La run -cs localhost qui ha prodotto output vuoto perché il content-scope filtering non ha espanso il field scope; l’invocazione custom -fs '(127.0.0.1|localhost)' invece ha contattato il secondo server. Registrare l’espressione regolare esatta conta, perché una modifica di un solo carattere può cambiare la regex e non solo la sua presentazione.
Testa l’interruzione e i known files separatamente dal recall di discovery. Per resume, interrompi un seed rappresentativo dopo alcune pagine, salva il path del checkpoint generato e conta quanti URL già completati vengono richiesti di nuovo. Per -kf, verifica sia che robots/sitemap siano stati richiesti, sia che gli URL <loc> inseriti siano stati davvero messi in coda. Sono affermazioni diverse. In questo fixture i file sono stati richiesti mentre i due endpoint della sitemap erano assenti, quindi servivano sia i request log sia l’output degli endpoint per vedere il confine.
Infine, stabilisci una baseline locale di costo usando esecuzioni sequenziali su una macchina altrimenti inattiva, poi ripeti sui target rappresentativi. Conserva minimo, massimo e mediana, non solo un moltiplicatore. Il 5,1x qui include il comportamento di timeout e di errore del fixture; ti dice che headless merita un budget a parte, non quanto tempo impiegherà un inventario reale.
Pro e contro
Pro:
- Recall perfetto sull’HTML normale in ogni modalità: 4/4 link e l’intera catena 3/3, senza configurazione.
-jcfunziona davvero senza browser: 2/2 endpoint recuperati da literal in un file JS collegato, senza costo del browser.-headlessè l’unica cosa che ha trovato un endpoint assemblato a runtime — una classe che per costruzione è invisibile al parsing del sorgente.- I default di scope sono conservativi. L’host fuori scope non è mai stato richiesto con default,
-fs fqdno-cs. - Un singolo binario Go, licenza MIT, build precompilate e immagine Docker, I/O pensato per la pipeline.
- Robusto in caso di errore: i 500 e i link morti non fermano il crawl.
Contro:
- Nessuna singola invocazione ha coperto sia gli endpoint da file JS sia quelli del DOM runtime. Per copertura completa servono due run e un merge.
-jcnon ha aggiunto nulla sotto-headless: 0/2 sulla classe B in ogni run headless.- Headless costa 5,1x in tempo reale (66.82s contro 13.08s p50, range non sovrapposti).
-resumericrawla le pagine completate all’interno di un seed. Ripristina il set di endpoint, non il tempo già speso.- I known files hanno richiesto robots.txt e sitemap.xml ma hanno recuperato 0/2 degli endpoint
<loc>della sitemap contro un target IP. - Headless richiede silenziosamente Chromium sulla macchina: la storia del “solo binario” finisce lì.
- Solo discovery. Nessuna estrazione strutturata, nessuna conversione del contenuto, nessuno schema di campi.
Non testato, e quindi fuori da ciò che questi numeri coprono: -jsluice, un test dedicato di cutoff della profondità con -d 1/-d 2, resume multi-seed, compilazione automatica di form e qualunque sito di produzione realmente pesante in JavaScript o protetto. Tutti i numeri arrivano da una sola macchina (macOS arm64) su un fixture locale.
Per chi è e chi dovrebbe evitarlo
Se il tuo lavoro è produrre un inventario di endpoint per infrastrutture su cui hai autorizzazione, Katana ha la forma giusta per la pipeline: plumbing STDIN/STDOUT, un binario distribuibile e modalità sia senza browser sia con browser. Il merge a due passaggi è stato necessario per le classi piantate in questo fixture; se i tuoi target richiedano entrambi i passaggi è qualcosa da stabilire su pagine rappresentative.
Evitalo se vuoi dati e non indirizzi. Katana non ti restituirà mai una tabella di prodotti; ti darà gli URL dove i prodotti potrebbero vivere, e qualcos’altro farà l’estrazione. Evitalo anche se ti serve un unico comando completo: il merge a due passaggi va benissimo in pipeline ed è scomodo da prompt. E se la tua enumerazione si basa sugli endpoint <loc> della sitemap mentre punti a IP, verifica davvero cosa stai ottenendo prima di fidarti dell’output, perché nel mio fixture quel percorso non ha restituito nulla.
Alternative e il confine dell’estrazione
Katana è gratuito, open source con licenza MIT e self-hosted. Mantiene discovery, selezione della modalità, deploy del browser e merge dei risultati dalla tua parte del confine.
Nel mondo open source, i confronti utili sono per compito, non per linguaggio. Colly è l’altra opzione in Go, ma è una libreria che compili con i tuoi callback e non renderizza affatto JavaScript. Crawl4AI usa un browser reale e produce Markdown per pipeline LLM, che è un output completamente diverso. Se stai valutando più strumenti insieme, il nostro panoramica degli scraper open source mette le categorie una accanto all’altra.
Disclosure: Thunderbit è il prodotto dell’editore e non è stato testato in questo fixture Katana. Si colloca a valle nella categoria dell’estrazione gestita, trasformando pagine in testo o record strutturati invece di enumerare la superficie di endpoint di un target autorizzato. Un workflow può usare entrambe le categorie, ma questa recensione fornisce prove solo del comportamento di discovery di Katana.
Prova Thunderbit per l’estrazione di dati web
Verdetto
Usa Katana quando il deliverable è una lista di endpoint per target che sei autorizzato a crawlare e puoi validare la copertura delle modalità su quei target. In questo fixture, lo standard -jc ha recuperato i literal JavaScript piantati, mentre headless ha recuperato l’endpoint nel DOM runtime; le modalità senza browser testate di Katana non hanno recuperato quel percorso runtime. Anche lo scope di default ha lasciato il secondo host non richiesto, e le esecuzioni senza browser hanno proseguito oltre il 500 e il link morto.
Le cautele sono operative: per classi miste di endpoint può servire un merge a due passaggi, headless ha richiesto circa cinque volte il tempo locale, resume su seed singolo ha rifatto i percorsi completati e il recall dei known files è stato 0/2 contro il target IP. Sono risultati del fixture v1.6.1, non garanzie su ogni sito. Ma bastano per definire i controlli che una valutazione in produzione dovrebbe ripetere.
Prova Thunderbit per l’estrazione di dati web Get Started Free
FAQ
Dove salva Katana il file di resume e riprende saltando le pagine già crawlate?
Il checkpoint è finito in ~/.config/katana/resume-<xid>.cfg, non in un resume.cfg nella directory di lavoro come fa pensare l’help del flag. E no, non salta le pagine già completate: il file conserva solo gli URL seed in-flight, quindi un crawl ripreso con seed singolo ha rifetchato tutti gli 11 percorsi baseline inclusi i 10 già finiti. Ottieni lo stesso set finale di endpoint, ma non il tempo risparmiato.
Perché -kf all ha richiesto il mio sitemap.xml ma non ha crawlato gli URL dentro?
Contro un target IP, il risultato è coerente con un confine di validazione dello scope più che con un errore di flag. Nel codice v1.6.1, il parser sitemap di Katana costruisce ogni richiesta <loc> senza portarsi dietro il root hostname, e il controllo dello scope DNS per host IP literal può quindi respingere quell’URL; non ho strumentato l’esecuzione per confermare quel meccanismo. Il recall è rimasto a 0 in tutte le varianti di flag, profondità e seed che ho provato. Una regex host -fs personalizzata prende un ramo di validazione diverso ed è la correzione prevista dal sorgente — ma non sono riuscito a confermarla con -kf sulla mia macchina, quindi trattala come non testata. Estrarre tu stesso gli URL <loc> e passarli a Katana come seed è l’approccio di cui oggi mi fiderei.
Cosa devo conservare quando riporto un test di copertura con Katana?
Registra la versione esatta di Katana e il comando, inclusa l’espressione -fs byte per byte; definisci le classi di endpoint prima dell’esecuzione; conserva i log lato server oltre allo stdout; e separa il comportamento misurato dalle ipotesi tratte dal sorgente. Per i run headless, registra anche la build del browser: questo test non l’ha fatto, quindi la riproducibilità è limitata.
Dovrei usare -jc, -headless o entrambi?
Scegli in base alle classi di endpoint di cui hai bisogno. In questo fixture, lo standard -jc ha trovato i literal memorizzati in un file JavaScript, mentre headless ha trovato l’endpoint inserito nel DOM runtime. Nessuna modalità copriva entrambe le classi da sola, quindi un run a due passaggi seguito da deduplica era la scelta difendibile per target misti.
Un URL che fallisce blocca il crawl? Non in questa esecuzione controllata. Katana ha continuato dopo una risposta 500 e un link morto e ha comunque restituito gli altri percorsi raggiungibili. Questo non sostituisce un vero accounting degli errori in produzione: conserva i log delle richieste fallite e definisci un tasso di errore accettabile, così un crawl parzialmente riuscito non venga scambiato per copertura completa.


