xpath (italian version)


Go back to english version

Questa breve guida spiegherà le basi di XPath e come può servire al fine di processare documenti HTML (o XML).

Molte volte troviamo delle informazioni preziose su dei siti web, in formato HTML o XML, e vorremmo estrapolarle ad esempio per indicizzarle in un nostro database o comunque farne altri usi particolari. Immaginiamo un negozio online con una lista di categorie o di marche, e le vogliamo listare (anche quando sono più di 10 comincia ad essere noioso e meccanico), oppure una lista di IP e di porte, o delle descrizioni di bugs. Quest'articolo ha lo scopo di organizzare un semplice, anzi semplicissimo, script generico per affrontare uno dei tanti scenari che si presentano in queste situazioni. Avrete sicuramente capito che questo script potrebbe essere utilizzato per un web crawler, sia per ottenere i vari links delle pagine da scaricare, sia per processare i dati (e da qui il passo verso XSLT è breve).

cos'è xpath

XPath è l'ennesima diavoleria del consorzio W3, e permette di specificare dei path (simili a quelli del filesystem) al fine di ricercare uno o più specifici tags di un documento XML. La cosa che rendere potente XPath è che non si tratta di un semplice path ma all'interno è possibile specificare una serie di condizioni sugli stessi tags da ricercare.

Qualche esempio:

<catalog>
  <product tested="true">Prodotto 1</product>
  <product tested="false">Prodotto 2</product>
  <product>
    Prodotto 3
    <desc>Descrizione 3</desc>
  </product>
  ...
</catalog>

Ecco qualche esempio di xpath validi:

/catalogrestituisce la lista di tutti i <catalog> che si trovano nella root del documento
/catalog/productrestituisce la lista di tutti i tag <product> che... insomma avete capito
/catalog/product[1]restituisce il primo <product>
/catalog/product[@tested="true"]lista di tutti i tag <product tested="true">
/catalog/product/@testedtutti i valori degli attributi tested degli elementi product
//producttutti gli elementi <product> del documento, anche innestati ecc.
//product/desctutte le <desc> dei <product>
//desctutti gli elementi <desc> anche al di fuori di <product>
//desc/text()il testo contenuto in ciascun elemento <desc>
//product[desc]tutti i prodotti che hanno un elemento <desc>

Con la @ quindi si indicano gli attributi. Attenzione, notate come con / (slash) si definisce la selection di ciò che vogliamo, mentre con [...] (quadre) si specificano delle condizioni sul nodo contestuale. Questo è più chiaro se usereste xpath più volte.

Da notare che product[1] è il primo elemento, non product[0] (non è come con gli array in C), e in questo caso non restituisce una lista ma un singolo elemento perché il path non è ambiguo.

Capite bene che per ottenere la lista di tutti i links all'interno di una pagina XML (o XHTML), l'xpath è immediato:

//a/@href

Ok ma dove sta l'output? Beh questo è un linguaggio, e come tale per essere usato ha bisogno di un qualche interprete, che nella maggior parte dei casi è una libreria di un qualche linguaggio di programmazione.

da html a xml

Prima di andare avanti dobbiamo capire che XPath lavora su documenti XML, quindi tag aperto con relativo tag chiuso, attributi con apici, ecc., tutte cose che non si trovano nei documenti HTML.
Al più sarebbe possibile parsare direttamente un documento XHTML.

Nel caso di HTML e broken HTML, ci serve un qualche strumento di conversione in XML. In questo i linguaggi di alto livello ci aiutano, in particolare useremo Python e la libreria LXML (che ha delle parti scritte in C). Esempio d'utilizzo:

import lxml.html
import sys
tree = lxml.html.parse (sys.argv[1])

Chiamamo il file parser.py, scarichiamo una qualsiasi pagina HTML come pagina.html e ora è possibile invocare genericamente il nostro miniscript nel modo seguente:

$ python parser.py pagina.html

A questo punto nella variabile `tree' avremo il nostro bel documento pronto per elaborare i nostri xpath. Esiste una variante che è lxml.html.fromstring, dove si passa una stringa invece che il nome di un file (utile quando si scarica una pagina HTML con le sockets senza salvarla su disco).

Finalizziamo lo script aggiungendo alla fine:

print tree.xpath (sys.argv[2])

Perfetto, ora come secondo argomento possiamo passare una xpath e come output avremo qualcosa di pythoniano (poi sta a voi trasformarlo in qualcos'altro, oppure usare XSLT).

esempi reali d'utilizzo

Qui abbiamo una lista di mailing lists http://lists.debian.org/completeindex.html, salviamola.
Vogliamo ottenere una lista di tutte le mailing lists.

Analizziamo il codice html, e notiamo che si trovano tutte in corrispondenza di un <a>, che sta dentro un <li>, che sta dentro un <ul>.
Un xpath potrebbe essere:

//ul/li/a/text()

Ma questo potrebbe essere ambiguo, poiché potrebbero esserci altri <ul><li><a> in altre posizioni della pagina. A questo punto l'xpath è molto semplice, tanto vale complicarlo un pò per essere sicuri di prendere solo le mailing lists. Nella pagina vediamo che i <li> sono almeno 10; un buon filtro potrebbe essere:

//ul[li[10]]/li/a/text()

In questo modo garantiamo che esista il 10° <li> all'interno dell'<ul>.

Attenzione! Spesso accade che scriviamo //ul/li[10]/a/text(). In questo caso verrà selezione il 10° elemento <li>. Ci si aspetta che quella sia una condizione, ma non lo è nonostante sia all'interno delle parentesi quadre.

Eseguiamo il nostro script:


$ python parser.py completeindex.html "//ul[li[10]]/li/a//text()"
['cdwrite', 'debian-68k', 'debian-accessibility', 'debian-admin', 'debian-admintool', 'debian-alpha', 'debian-amd64', 'debian-announce', 'debian-apache', 'debian-arm', 'debian-autobuild', 'debian-beowulf', 'debian-blends', 'debian-books', 'debian-boot', 'debian-bsd', 'debian-bugs-closed', ...

Ora fate una ricerca su Yahoo! (per cambiare un pò), e se non è cambiato il sito questo script di seguito dovrebbe darvi i titoli e gli url dei risultati principali:

import sys
import lxml.html
import urllib

baseurl = "http://search.yahoo.com/search?"
xpath = "//li/div/div/h3/a"

query = urllib.urlencode ({'p': sys.argv[1]})
page = urllib.urlopen(baseurl+query).read()
tree = lxml.html.fromstring (page)
elements = tree.xpath(xpath)
for element in elements:
  print element.text_content(), '-', element.get('href')

E ora vi lascio con qualche link interessante su xpath, xslt e lxml :)

$ python search.py "xpath lxml xslt"
XML Path Language (XPath) - http://www.w3.org/TR/xpath
XSL Transformations - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/XSLT
XPath and XSLT with lxml - http://codespeak.net/lxml/xpathxslt.html
Why XSLT 2.0 and XPath 2.0? - http://www.altova.com/XSLT_XPath_2.html
...

conclusione

Nonostante a me personalmente non piace tutto ciò che sta intorno a XML, bisogna ammettere che finalmente è uscito qualcosa di interessante da questo mondo contorto. In particolare, XSLT è un linguaggio a forma di XML (purtroppo si, avete sentito bene) che con l'ausilio di XPath trasforma un qualsiasi documento XML in un qualsiasi altro documento (molto utile per astrarsi dal linguaggio di programmazione).
Nel caso di cui sopra abbiamo usato un `for' e quindi usato il linguaggio ospite; con XSLT questo poteva essere evitato. Tutto il resto ve lo lascio immaginare :)