Sauron Software Projects Repository
grab4j
Presentazione Documentazione Download

Manuale di grab4j

Prima di cominciare

Per utilizzare il grabber di grab4j nella tua applicazione Java, devi aggiungere il file grab4j.jar al CLASSPATH. Poiché grab4j dipende da alcuni jar di terze parti (tutti posizionati nella directory lib) devi aggiungere anche questi al CLASSPATH.

grab4j richiede un ambiente di esecuzione Java J2SE versione 1.4 o successiva.

Web-grabbing con grab4j

Estrarre informazioni da un documento HTML online è un'operazione da svolgere in tre passi:

  1. Scaricare il documento dalla rete.
  2. Interpretare il documento e costruirne una rappresentazione ad oggetti.
  3. Estrarre informazioni dalla rappresentazione del documento, eseguendo la routine con la logica di grabbing.

Il problema principale riguarda proprio la logica di grabbing. Una volta che un documento è stato scaricato ed interpretato, qualsiasi suo elemento può essere raggiunto mediante la rappresentazione ad oggetti. Tuttavia un documento contiene informazioni sia utili sia inutili. Normalmente una pagina web, oltre al suo contenuto principale, contiene anche elementi di navigazione, banner, loghi e strutture di layout. Anche considerando il solo contenuto principale del documento, non è detto che si debba recuperarlo nella sua interezza. Pertanto la logica di grabbing serve per specificare quali elementi debbano essere recuperati e quali, invece, scartati.

Gli strumenti messi a disposizione da grab4j sono generici e permettono di scaricare ed interpretare una qualsiasi pagina web. Fatto ciò grab4j non può sapere quale sia la logica di grabbing di volta in volta necessaria, poiché questa dipende dal particolare documento esaminato e dagli scopi dell'applicazione. Ad ogni modo grab4j fornisce una serie di strumenti utili per programmare ed "iniettare" nella libreria la peculiare logica di grabbing richiesta.

Normalmente la logica di grabbing si realizza selezionando alcuni elementi del codice HTML e scartandone altri. Naturalmente quali elementi debbano essere considerati dipende dal contesto. Ad esempio, per eseguire il grabbing di una pagina web con delle previsioni meteo, si dovrà esaminarne il codice HTML e poi spiegare a grab4j cosa fare. Una cosa del tipo: "estrai il testo che è nel terzo elemento <p> dall'alto, poi prendi le immagini che sono nel <div> immediatamente successivo, ma solo se hanno un attributo width con valore 50, e restituisci il risultato aggregandolo in questa struttura Java it.mieiclassi.Meteo che ho preparato due minuti fa"...

Per far questo è possibile sfruttare due differenti approcci, uno basato su Java e l'altro su JavaScript.

Il primo è molto semplice e non costituisce nulla di nuovo per il programmatore Java. Sostanzialmente si chiede a grab4j di scaricare un documento e di costruirne una rappresentazione. Una volta completata l'operazione, la rappresentazione ad oggetti del documento viene presa in carico dal programmatore che, aiutandosi con i metodi e gli strumenti messi a disposizione dal pacchetto, esegue la sua logica di grabbing e fa quel che deve fare. Questa tecnica, benché semplice, soffre di un noioso problema. Le pagine Web sono mutevoli sia nei contenuti sia nella struttura. Finché si tratta dei soli contenuti, non c'è problema, visto che lo scopo di un grabber è proprio prelevare i dati più aggiornati. Anche quella che è la struttura della pagina web al momento della scrittura della logica di grabbing, però, potrebbe cambiare nel tempo, e questo costituisce un problema. Un cambio sulla struttura della pagina, anche di piccola entità, potrebbe rendere inservibile la routine scritta in precedenza. Non resta che correggere la routine affinché rifletta i cambi apportati alla struttura della pagina. Se la routine è scritta in Java bisogna modificare le classi che la implementano, ricompilare, impacchettare di nuovo l'applicazione, distribuire la nuova versione del software, oppure fermare le istanze in esecuzione e sostituirle, quindi riavviare il tutto. Non è proprio il massimo.

Se la logica di grabbing viene invece espressa mediante uno script interpretato, possibilmente conservato in un file separato dall'applicazione e non integrato al suo interno, il cambio della struttura della pagina web può essere gestito più velocemente. Quello che occorre è correggere lo script e sostituire il file richiamato dall'applicazione. Se il software è ben fatto non c'è neanche bisogno di riavviare l'applicazione, figuriamoci quindi di ricompilarla.

L'approccio Java

Mettere in pratica l'approccio basato su Java è semplicissimo. Basta usare la classe it.sauronsoftware.grab4j.html.HTMLDocumentFactory per ottenere la rappresentazione ad oggetti di un documento web:

HTMLDocument doc = HTMLDocumentFactory.buildDocument("http://www.host.com/page.html");

L'oggetto restituito è di tipo it.sauronsoftware.grab4j.html.HTMLDocument. Gli elementi nella rappresentazione possono essere esplorati con i metodi getElements(), getElementCount(), getElement(), getElementById(), getElementsByAttribute() e getElementsByTag(). Strumenti di ricerca avanzata sono realizzati dai metodi searchElements() e searchElement(), e dalla classe the it.sauronsoftware.grab4j.html.search.Criteria.

Ciascun elemento del documento è rappresentato con un'istanza della classe it.sauronsoftware.grab4j.html.HTMLElement. I riferimenti di tipo HTMLElement portano sempre ad oggetti più specifici, come HTMLText, HTMLTag, HTMLImage e HTMLLink. E' quindi possibile eseguire il casting ed accedere ai metodi aggiuntivi di queste classi.

C'è poco altro da dire, se non che una guida metodica alle funzionalità offerte è nei javadoc di grab4j.

L'approccio JavaScript

La classe it.sauronsoftware.grab4j.WebGrabber permette il grabbing di un documento web con una sola chiamata Java:

URL pageUrl = new URL("http://www.host.com/page.html");
File scriptFile = new File("grabbing-logic.js");
Object result = WebGrabber.grab(pageUrl, scriptFile);

Due sono le informazioni richieste: la pagina web da analizzare e la logica di grabbing, che viene "iniettata" nella libreria attraverso uno script JavaScript. Lo script ha il compito di prendere la rappresentazione del documento ed estrarne i dati di interesse, possibilmente assemblandoli in una struttura. Quanto elaborato dallo script e da esso indicato come il risultato dell'operazione viene restituito dal metodo grab() sotto forma di oggetto Java. L'applicazione può a questo punto esaminare ed utilizzare il risultato ottenuto.

Per approfondire le diverse forme del metodo grab() si faccia riferimento al javadoc della classe WebGrabber.

Lo script di grabbing

Lo script di grabbing deve essere realizzato con linguaggio JavaScript/ECMAScript. Questo significa che si può fare uso di tutte le funzioni, le classi e le costanti della libreria ECMAScript, come le funzioni parseInt() e isNaN(), la classe Math e la costante Infinity.

Riferimenti globali

Oltre ai riferimenti globali messi a disposizione da ECMAScript, gli script di grabbing ricevono altre due variabili.

document

Questa è una sorta di variabile di input per lo script. Contiene il riferimento alla rappresentazione del documento scaricato ed analizzato. Chiamando i metodi di questo oggetto si accede alle informazioni da estrarre.

titleElement = document.getElementById("titolo");

result

Questa, invece, è una sorta di variabile di output. Quando il lavoro di estrazione delle informazioni è completato, queste vanno aggregate in un unico oggetto (tipicamente, una semplice struttura dati) e restituite. Impostando il valore della variabile result si specifica cosa restituire al codice chiamante.

result = myResult; 

Funzioni globali

Oltre alle funzioni messe a disposizione da ECMAScript gli script di grabbing possono richiamare anche alcune altre funzioni.

print(<string>)

Stampa una stringa nel canale di standard output. E' utile per fare il debug dello script.

print("ciao mondo!");
print(document.getElementCount());

openDocument(<string>)

Scarica ed analizza la pagina HTML all'indirizzo passato come parametro, restituendo un riferimento alla sua rappresentazione ad oggetti. Questa funzione permette allo script di aprire ed analizzare altri documenti oltre a quello ricevuto nel riferimento document, che rappresenta il punto di partenza dell'operazione di grabbing. Ci sono dei casi in cui ciò torna molto utile. Si immagini, ad esempio, che la pagina consegnata al metodo grab() contenga una lista di collegamenti alle informazioni di interesse, e non le informazioni stesse. Lo script può prendere in gestione la lista e, per ogni collegamento riscontrato, andare ad aprire il documento riferito ed estrarre da esso le informazioni volute. Quando capita una situazione simile si è veramente contenti del fatto che esista una funzione openDocument().

var doc2 = openDocument("http://www.sito.com/pagina.html");

encodeEntities(<string>)

Questa funzione prende una stringa e la codifica come HTML. Ciò significa che ogni carattere riservato o problematico contenuto nella stringa sarà sostituito da un'entità di HTML (per intendersi, quelle del tipo &nome; oppure &#xx;). Una nuova stringa contenente il testo originale ricodificato in HTML viene generata e restituita.

var str = encodeEntities("<test>");

decodeEntities(<string>)

Il contrario di encodeEntities(). Questa funzione prende in gestione una stringa che si suppone codificata in HTML, interpretandone le entità e generando una nuova stringa con le medesime decodificate.

var str = decodeEntities("&lt;test&gt;");

Metodi degli oggetti document

Naturalmente, come in Java così in JavaScript, è possibile esplorare gli elementi presenti nella rappresentazione di un documento HTML. Nello script si possono avere diversi oggetti di questo tipo. Il primo è quello riferito dalla variabile globale document, che rappresenta il documento principale, mentre altri possono essere costruiti chiamando la funzione openDocument(). I metodi disponibili sono riportati di seguito.

getElementCount()

Restituisce il numero degli elementi di privo livello del documento. Tipicamente ce ne sono un paio, il primo è il doctype, il secondo è il tag <html>, ma a seconda di come è scritta la pagina può andare diversamente.

var n = document.getElementCount();

getElement(<integer>)

Restituisce l'elemento di primo livello all'indice specificato.

for (var i = 0; i < document.getElementCount(); i++) {
  var el = document.getElement(i);
  // ...
}

getElementById(<string>)

Cerca fra tutti gli elementi del documento, ricorsivamente (quindi non solo su quelli di primo livello, ma anche nei loro figli, nei figli dei loro figli e così via), finché non ne trova uno che abbia il valore specificato come argomento nel suo attributo id. Se nessun elemento viene individuato, allora restituisce null.

var el = document.getElementById("titolo");

getElementsByTag(<string>)

Cerca ricorsivamente fra tutti gli elementi del documento, collezionando quelli corrispondenti al tag specificato, che restituisce in un array. Se nessun tag del tipo specificato viene individuato, restituisce un array vuoto.

var elements = document.getElementsByTag("h1");
for (var i = 0; i < elements.length; i++) {
  // ...
}

getElementsByAttribute(<string>, <string>)

Cerca ricorsivamente fra tutti gli elementi del documento, collezionando quelli che hanno l'attributo specificato nel primo parametro con un valore uguale a quello specificato nel secondo. Restituisce un array con gli elementi in grado di soddisfare il requisito. Se nessun elemento viene individuato, restituisce un array vuoto.

var elements = document.getElementsByAttribute("align", "center");
for (var i = 0; i < elements.length; i++) {
  // ...
}

searchElements(<string>)

Cerca ricorsivamente fra tutti gli elementi del documento, restituendo un array con gli elementi in grado di soddisfare il particolare criterio fornito in argomento. Se nessun elemento viene individuato, restituisce un array vuoto.

var elements = document.searchElements("html/body/.../img(src=*.jpg)");
for (var i = 0; i < elements.length; i++) {
  // ...
}

I criteri di ricerca per questo tipo di metodi saranno illustrati in seguito.

searchElement(<string>)

Cerca ricorsivamente fra tutti gli elementi del documento, finché non ne individua uno in grado di soddisfare il particolare criterio fornito in argomento. Se nessun elemento soddisfa il criterio restituisce null.

var el = document.searchElement("html/body/ul/li");

I criteri di ricerca per questo tipo di metodi saranno illustrati in seguito.

Metodi degli oggetti element

Si è visto come estrarre gli elementi dal documento, ora si vedrà come estrarre informazioni e sotto-elementi da un elemento qualsiasi.

getElementCount()

Restituisce il numero dei sotto-elementi dell'elemento.

var n = el.getElementCount();

getElement(<integer>)

Restituisce il sotto-elemento all'indice specificato.

for (var i = 0; i < el.getElementCount(); i++) {
  var el2 = el.getElement(i);
  // ...
}

getElementById(<string>)

Cerca fra tutti i sotto-elementi, ricorsivamente (quindi cercherà anche nei sotto-sotto-elementi, nei sotto-sotto-sotto-elementi e così via), finché non ne trova uno che abbia il valore specificato come argomento nel suo attributo id. Se nessun elemento viene individuato, allora restituisce null.

var el2 = el.getElementById("titolo");

getElementsByTag(<string>)

Cerca ricorsivamente fra tutti i sotto-elementi, collezionando quelli corrispondenti al tag specificato, che restituisce in un array. Se nessun tag del tipo specificato viene individuato, restituisce un array vuoto.

var elements = el.getElementsByTag("h1");
for (var i = 0; i < elements.length; i++) {
  // ...
}

getElementsByAttribute(<string>, <string>)

Cerca ricorsivamente fra tutti i sotto-elementi, collezionando quelli che hanno l'attributo specificato nel primo parametro con un valore uguale a quello specificato nel secondo. Restituisce un array con gli elementi in grado di soddisfare il requisito. Se nessun elemento viene individuato, restituisce un array vuoto.

var elements = document.getElementsByAttribute("align", "center");
for (var i = 0; i < elements.length; i++) {
  // ...
}

searchElements(<string>)

Cerca ricorsivamente fra tutti i sotto-elementi, restituendo un array con gli elementi in grado di soddisfare il particolare criterio fornito in argomento. Se nessun elemento viene individuato, restituisce un array vuoto.

var elements = el.searchElements("table/tr/td/img(src=*.jpg)");
for (var i = 0; i < elements.length; i++) {
  // ...
}

I criteri di ricerca per questo tipo di metodi saranno illustrati in seguito.

searchElement(<string>)

Cerca ricorsivamente fra tutti i sotto-elementi, finché non ne individua uno in grado di soddisfare il particolare criterio fornito in argomento. Se nessun elemento soddisfa il criterio restituisce null.

var el2 = el.searchElement("ul/li");

I criteri di ricerca per questo tipo di metodi saranno illustrati in seguito.

getPreviousElement()

Restituisce l'elemento che viene prima di quello corrente. Se l'elemento corrente è il primo del suo gruppo restituisce null.

var p = el.getPreviousElement();

getNextElement()

Restituisce l'elemento che viene dopo quello corrente. Se l'elemento corrente è l'ultimo del suo gruppo restituisce null.

var n = el.getNextElement();

getParentElement()

Restituisce l'elemento di cui quello corrente è figlio, cioè sotto-elemento. Se l'elemento corrente non ha un elemento genitore (accade quando l'elemento corrente è un elemento di primo livello nel documento) allora il metodo restituisce null.

var p = el.getParentElement();

Metodi degli oggetti tag

Se l'elemento che si sta manipolando rappresenta un tag HTML, oltre ai metodi descritti nel paragrafo precedente, sono disponibili anche alcune altre funzionalità.

getTagName()

Restituisce il nome del tag.

var tagname = el.getTagName();

getAttribute(<string>)

Restituisce il valore dell'attributo specificato, o null se il tag non ha l'attributo desiderato.

var attrValue = el.getAttribute("align"); 

isEmpty()

Restituisce true se il tag è vuoto e non ha contenuto.

var empty = el.isEmpty();

getInnerText()

Restituisce il contenuto del tag convertendolo il testo semplice.

var text = el.getInnerText();

getInnerHTML()

Restituisce il contenuto del tag sotto forma di codice HTML.

var html = el.getInnerHTML();

getOuterHTML()

Restituisce il codice HTML che forma il tag stesso ed il suo contenuto.

var html = el.getOuterHTML();

getLinkURL()

Se il tag è <a> con un attributo href valido, questo metodo restituisce l'indirizzo collegato. Mentre il metodo getAttribute("href") restituisce il valore dell'attributo nudo e crudo, getLinkURL() fa un'analisi del valore e lo raffronta con altri parametri, come l'indirizzo del documento stesso ed eventuali impostazioni di base poste al suo interno, in modo da convertire i collegamenti relativi in indirizzo assoluti. Che poi è la stessa cosa che fa un browser quando si clicca su un collegamento relativo. E' possibile usare il valore restituito dal metodo con la funzione openDocument(), che accetta solo indirizzi assoluti, per realizzare la navigazione tra i documenti remoti descritta in precedenza.

var elements = document.getElementsByTag("a");
for (var i = 0; i < elements.length; i++) {
  print(elements[i].getLinkURL());
}

getImageURL()

Se il tag è un <img> con un attributo src valido, questo metodo restituisce l'indirizzo dell'immagine richiamata. Come avviene per getLinkURL(), questo metodo non è un semplice alias di getAttribute("src"). Anche in questo caso l'indirizzo estratto viene esaminato e raffrontato con le informazioni sul documento, in modo da trasformare quando possibile i riferimenti relativi in indirizzi assoluti.

var elements = document.getElementsByTag("img");
for (var i = 0; i < elements.length; i++) {
  print(elements[i].getImageURL());
}

Criteri di ricerca degli elementi

I criteri di ricerca possono essere utilizzati con i metodi searchElement() e searchElements() delle rappresentazioni di documenti ed elementi.

Un criterio di ricerca è anzitutto suddiviso da più parti (token), separate da un carattere "slash".

token1/token2/token3

La sequenza serve ad individuare un percorso tra i tag del documento. Per comprendere meglio si può svolgere un paragone con i percorsi usati da un file system per localizzare una risorsa. Un percorso come

C:/dir1/dir2/file.ext

significa che si sta richiedendo il file file.ext, che è nella directory dir2, che a sua volta è in dir1, e che a sua volta è contenuta nella radice del disco logico C.

I percorsi espressi in un criterio di ricerca di grab4j funzionano allo stesso modo, solo che invece che trattare directory e file fanno riferimento ai tag HTML del documento. Un percorso come

html/body/p

identifica qualsiasi elemento <p> che è contenuto dentro <body>, che a sua volta ha l'elemento <html> come padre.

Questi percorsi sono da intendersi come relativi rispetto all'elemento dal quale si lancia la ricerca. Solo se la si lancia direttamente con i metodi searchElements() o searchElement() di un documento assumono allora un significato assoluto.

Ogni parte di un percorso di ricerca deve seguire il modello:

tagNamePattern[index](attribute1=valuePattern1)(attribute2=valuePattern2)(...)

Il primo elemento del token è il pattern per il riconoscimento di un tag attraverso il suo nome, ed è l'unica parte sempre richiesta. Questo pattern supporta il wildcard asterisco, cioè un asterisco può essere utilizzato per indicare una sequenza di caratteri qualsiasi.

html/body/*

Questo criterio rintraccia tutti gli elementi che hanno per padre il tag <body>, che a sua volta deve essere dentro il blocco <html> ... </html>.

html/body/h*

Questo, invece, cerca dentro <body> i tag il cui nome inizia per h, ad esempio <h1>, <h2>, <h3> e così via.

Se ci sono più elementi che soddisfano il criterio di ricerca è possibile richiedere che ne venga considerato soltanto uno, attraverso un indice:

html/body/div[1]

Questo criterio restituisce il secondo <div> tra tutti quelli che si trovano nel <body>. Si osservi che il primo elemento di un gruppo ha indice 0, e quindi l'indice 1 identifica il secondo elemento, 2 il terzo e così via.

html/body/h*[2]

Questo criterio individua il terzo elemento nella lista di quelli il cui nome inizia con h e sono figli di <body>.

Ricerche più dettagliate prendono in considerazione anche gli attributi dei tag.

html/body/div(id=d1)

Questo criterio ricerca, sempre all'interno dell struttura <html> <body> ... </body> </html>, tutti quei <div> aventi un attributo id con valore d1.

Il wildcard asterisco può essere usato nei valori degli attributi:

html/body/div(id=*)

Questa ricerca, rispetto alla precedente, seleziona tutti i <div> che hanno un attributo id, indipendentemente dal suo valore.

html/body/div(id=d*)

Questo criterio richiede invece che il valore dell'attributo cominci con la lettera d.

Siccome un tag può avere più attributi, un criterio di ricerca può specificare più restrizioni di attributo insieme.

html/body/div(id=d*)(align=left)

Questo criterio cerca i <div> che hanno un attributo id il cui valore inizia per d, ed un attributo align il cui valore deve necessariamente essere left.

L'uso dei selettori sugli attributi non esclude quello del selettore d'indice.

html/body/div[1](id=d*)(align=left)

Questo criterio di ricerca è identico alla precedente, ma restituisce soltando il secondo risultato (indice 1) fra tutti quelli individuati.

Talvolta non si sa a quale profondità si trova un elemento desiderato, oppure non è importante saperlo. Per questo è possibile usare un token di "ricerca ricorsiva", costituito da una sequenza di tre punti.

html/body/.../table

Questo criterio restituisce tutti gli elementi <table> che ci sono nel corpo del documento, sia se sono direttamente dentro <body> sia se sono dentro gli elementi figli di <body>, o i figli dei figli di <body> e così via. Il token di ricerca ricorsiva tra i sotto-elementi, quindi, sta ad indicare una profondità qualsiasi.

L'esempio appena visto restituisce sia le tabelle realizzate con struttura del tipo

<html><body><table>...

sia quelle annidate, come

<html><body><div><div>table>...

Durante la composizione di un criterio di ricerca può capitare che, per il valore di un attributo o per qualche altro campo, si debba fare un uso di un carattere particolare o riservato. E' pertanto possibile indicare il carattere problematico con una sequenza del tipo <xx>, dove xx è il codice esadecimale del carattere nel charset in uso.

Si immagini di voler cercare un elemento <img> con l'attributo alt uguale alla stringa Foto di Carlo (2007). Il criterio

.../img(alt=Foto di Carlo (2007))

non è valido, poiché le parentesi tonde intorno all'anno vanno a collidere con quelle utilizzate sintatticamente come contenitori dei selettori sugli attributi. In questo caso è sufficiente verificare quale sia il codice esadecimale delle parentesi tonde nel charset utilizzato (le parentesi tonde in ASCII hanno codice esadecimale rispettivamente uguale a 28 e a 29) e riscrivere il criterio come segue:

.../img(alt=Foto di Carlo <28>2007<29>)

Uso di classi Java nello script di grabbing

Oltre alla libreria ECMAScript ed oltre alle funzionalità messe a disposizione da grab4j, gli script di grabbing possono utilizzare qualsiasi classe Java dell'ambiente che li esegue. A conti fatti, quindi, il linguaggio di scripting usato per gli script di grabbing è molto più potente del JavaScript tradizionale.

Proprio come in Java, è conveniente fare l'import delle classi e dei pacchetti che si intende utilizzare.

Per importare una classe che fa parte di un pacchetti nella gerarchia java.* si usa l'istruzione:

importClass(<class>);

Ad esempio:

importClass(java.util.ArrayList);

Le classi che vengono da pacchetti non java.* possono essere importate così:

importClass(Packages.<class>);

Ad esempio:

importClass(Packages.it.sauronsoftware.grab4j.examples2.Item);

Il discorso è simile per importare interi pacchetti. Se il pacchetto da importare è nella gerarchia java.*:

importPackage(<package>);

Ad esempio:

importPackage(java.util);

Se il pacchetto è di atra gerarchia:

importPackage(Packages.<package>);

Ad esempio:

importPackage(Packages.it.sauronsoftware.grab4j.examples2);

Una volta che le classi sono state importate, possono essere utilizzate in maniera molto intuitiva, più o meno come si farebbe in Java:

importClass(java.util.ArrayList);
var list = new ArrayList();
// ...

Esempi

Alcuni esempi possono essere trovati nella directory examples.

© Sauron Software 2007 - 2012 | This page in English