Java Crawler

Un crawler (anche detto spider, boot o web robot) è un programma che naviga il web al fine di ricercare pagine nuove (o aggiornate) a scopo di indicizzazione o più in generale per estrarre informazioni di diverso tipo. La particolarità di tali software è che sono in grado di allargare autonomamente il proprio raggio di azione seguendo i link presenti nelle pagine visitate. Per tale motivo i tempi di esecuzione divengono rapidamente esponenziali al punto che i crawler dei motori di ricerca commerciali (google, yahoo, etc.) sono praticamente sempre attivi (continuous crawling) ed in esecuzione su server diversi.

L’algoritmo

L’algoritmo di crawling in pseudocodice è in realtà molto semplice ma prima di introdurlo diamo alcune definizioni:

  • La frontiera F è un insieme da cui vengono prelevati (e quindi rimossi) i link da seguire ed in cui vengono inseriti i nuovi link trovati;
  • semi sono gli indirizzi (URL) di partenza del processo di crawling;
  • Un set L di link visitati identifica tutti i documenti già analizzati.

L’algoritmo avrà quindi la seguente struttura:

  1. si recupera un link dalla frontiera;
    1. Il link viene eliminato da F ed inserito in L;
  2. si scarica il codice HTML della pagina al fine di;
    1. analizza la pagina secondo gli scopi del crawler;
    2. estrarre tutti i link della pagina;
  3. ogni link estratto che non è presente né in L né il F viene aggiunto alla frontiera.
  4. si ritorna al punto 1 se la frontiera contiene ancora link, altrimenti il processo termina.

Si noti che al punto 3 è possibile aggiungere ulteriori condizioni per l’inserimento dei link nella frontiera, ad esempio per limitare il crawling all’interno di uno specifico dominio. In questo caso più propriamente di parla di web scraping.

L’Implementazione

Esistono diversi tool e framework che implementano un crawler. Ma prima di parlarne presentiamo un esempio di codice java che ci sarà comunque utile per comprendere il comportamento di un crawler e la sua complessità.

Innanzitutto abbiamo bisogno di due insiemi che implementano rispettivamente la frontiera ed i link visitati e dei metodi per popolarli:

Quindi abbiamo bisogno di un metodo che scarica il file HTML referenziato da un URL dato:

In ultima analisi abbiamo la necessità di un metodo che estragga gli URL dalle pagine HTML. Questo è sicuramente un compito molto complesso e di non semplice implementazione. Nella soluzione proposta ci siamo affidati ad una regular expression trovata in internet ma sicuramente nei crawler “industriali” sono adottate tecniche più avanzate che tengono conto anche di aspetti legati alla posizione del link nel testo etc.

Quindi lo pseudocodice descritto sopra si traduce nel seguente codice java, in cui abbiamo inserito come ulteriore condizione di terminazione la visita di 100 link al fine di evitare un loop infinito:

Avviando il processo anche solo partendo dal link di google http://www.google.it l’output finale del programma mostra 1355 link ancora da visitare presenti nella frontiera dopo 100 pagine estratte. Questo ci da quindi una idea di quanto sia virtualmente impossibile eseguire il crawling di tutto il web con programmi così semplici come quello presentato.

JSoup

Nell’esempio descritto sopra abbiamo sorvolato sul problema dell’analisi del contenuto della pagina estratta dall’url considerando i fini per cui il crawler è realizzato. Genericamente, infatti, ci siamo limitati a stampare l’url e ad estrarre i link per mezzo della regular expression.

Per fare analisi più approfondite abbiamo bisogno di un parser HTML che abbiamo individuato in JSoap. Si tratta di un framework java le cui API consentono di estrarre dati e manipolare in maniera estremamente semplice documenti HTML5 sfruttando le potenzialità di DOM, CSS e metodi di accesso simili a quelli offerti da JQuery.

Per utilizzare JSoup inseriamo innanzitutto il riferimento Maven nel nostro progetto:

Quindi ridisegniamo il metodo start() del nostro crawler eliminando i metodo extractUtls() e read() che invece sono efficientemente implementati da JSoup:

JSoup esegue il parsing dei file HTML indipendentemente dal fatto che il documento sia ben formato o meno, gestendo quindi situazioni, tra l’altro molto comuni, di tag non chiusi o tag impliciti. Il risultato del parsing è una struttura dati costruita in memoria e detta DOM (Document Object Model), che può essere navigata in modo molto efficente con l’implementazione del CSS selector che offre il framework.

Tornando al codice del metodo start() le funzionalità utilizzate del parser sono:

  1. estrazione e parsing del documento: Jsoup.connect( url ).get();
  2. selezione nel documento di tutti i link presenti (il tag <a>): document.select("a[href]");
  3. estrazione dell’attributo href da tutti i link: link.attr("abs:href");

Si noti l’utilizzo del prefisso abs: che risolve l’url, che in genere è relativo, rispetto al contesto dell’applicazione. In altri termini trasforma un link relativo del tipo  /post/id=23 in http://www.myapp.com/post/id=23.

Eseguendo il nuovo metodo questa volta i link in frontiera dopo aver visitato 100 pagine sono 4636 a dimostrazione della maggiore efficacia del parser rispetto alla semplice regular expression. Relativamente all’efficienza, invece, si noterà che l’esecuzione è più lenta poiché il parsing è una operazione sicuramente più onerosa.

Crawler4J

Il crawler da noi implementato, pur assolvendo bene il proprio compito, è carente rispetto a determinati aspetti sia tecnici che implementativi. Innanzitutto un crawler dovrebbe essere sufficientemente “gentile” da seguire le direttive specificate nel file robot.txt presente nei siti web visitati. Tale file è utilizzato dai gestori del sito per definire restrizioni per i crawler in termini di frequenza di visita, modalità di visita, etc.

Dal punto di vista implementativo, invece, il nostro codice non tiene conto di diversi aspetti come, ad esempio, i tempi do crawling che, come abbiamo visto, possono essere esponenziali, oppure l’interruzione accidentale dell’esecuzione, che determinerebbe il riavvio del processo daccapo.

Introduciamo quindi una soluzione completa interamente scritta in java, molto efficiente e di semplice utilizzo: crawler4j. Disponibile su Maven Central è possibile includerlo nel progetto semplicemente inserendone la dipendenza nel pom.xml:

L’implementazione del crawler richiede l’estensione della classe WebCrawler che implementa l’interfaccia Runnable. Questo perché tale classe può essere eseguita su più thread al fine di parallelizzare il processo di crawling. La classe non presenta metodi astratti ma i due metodi shouldVisit()visit() hanno implementazioni di default assolutamente inutili. Il primo metodo, infatti, è utilizzabile per filtrare i link da visitare e per default restituisce sempre TRUE. Il secondo, invece, è invocato ad ogni url visitato e per default non fa assolutamente nulla.

Implementiamo quindi il primo metodo per la selezione dei link da visitare facendo in modo da escludere tutti i link che escono dal dominio e che non rimandano a pagine HTML:

Nella realizzazione del metodo visit() volevamo mantenere la logica già mostrata negli esempi precedenti al fine di visualizzare il numero di elementi visitati e da visitare. Sfortunatamente il framework è scarsamente documentato quindi l’implementazione che presentiamo è il risultato di una ipotesi che abbiamo fatto ispezionando il codice sorgente:

Esempi più complessi è significativi possono essere trovati direttamente sulla pagina Github del progetto.

Prima di procedere con l’implementazione del controller del nostro crawler realizzato con Crawler4J, accenniamo brevemente alla sua architettura funzionale, mostrata nella seguente immagine (sorgente).

crawler4j

Molto semplicemente il controller gestisce N crawler che ricevono gli URL da visitare da una frontiera comune. Il recupero delle pagine è delegato ad un page fetcher (che a discapito dell’immagine è comune ai crawler) mentre l’analisi è demandata al metodo visit() al quel è inviata la pagina web dopo averla parsata.  Un ulteriore componente gestisce infine i file robots.txt incontranti durante il crawling.

Per altre opzioni di configurazione, tipo user-agent, proxy, politiche di fairness etc., vi rimandiamo alla pagina ufficiale su Github.

Codice Sorgente

Il codice sorgente con gli esempi presentati è disponibile qui crawler.

How useful was this post?

Click on a star to rate it!

Average rating 4.3 / 5. Vote count: 3

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!