Home > scrapy, xpath > Xpath: appunti

Xpath: appunti

10 Ottobre 2017

Indice:
parte 1. Xpath: Appunti
parte 2. xpath: Abbreviazioni
parte 3. xpath: Funzioni generiche
scrapy
xpath syntax

Da wikipedia:
In informatica XPath è un linguaggio, parte della famiglia XML, che permette di individuare i nodi all’interno di un documento XML.
Le espressioni XPath, a differenza delle espressioni XML, non servono solo ad identificare la struttura di un documento,
bensì a localizzarne con precisione i nodi.

Per poter fare pratica, utilizzeremo la shell di scrapy, quindi passiamo all’installazione.

Installazione Scrapy

Per prima cosa creare un virtual-environment.

virtualenv venv

Abilitare il virtual-environment:

venv/Script/activate

per windows, mentre per linux, nel mio specifico caso Ubuntu:

source venv/bin/activate

Prima di procedere con l’installazione dei pacchetti necessari tramite
“pip”, per ubuntu (>12.04) è necessario installare alcuni pacchetti necessari
alle successive compilazioni, quindi da terminale:

sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev

sempre da venv, se stiamo lavorando da window, installare il pacchetto “pypiwin32”:

pip install pypiwin32

ora, per entrambi i casi, è possibile installare Scrapy:

pip install scrapy

Shell

Per lanciare la shell di scrapy utilizzare il comando:

scrapy shell

Scrapy ci mette a disposizione un oggetto Selector sul quale è possibile
utilizzare xpath.

>>> from scrapy import Selector
>>> s = Selector(text="<foo><bar></bar></foo>")
>>> s.xpath('//bar').extract()
[u'<bar></bar>']

Nota:
Le espressioni xpath, oltre con Selector, possono essere analizzate direttamente
sull’oggetto response, aprendo la shell e passando come argomento il percorso di un
file o l’indirizzo di una pagina web:

(venv) C:\...>scrapy shell "file://path_to_file"
>>> response.xpath('//*').extract()

Qualora volessimo modificare il body dell’oggetto response,
dobbiamo fare una copia di esso utilizzando il metodo replace():

>>> new_body = "<foo><bar></bar></foo>"
>>> response.body = new_body
    ...
    raise AttributeError(msg)
AttributeError: HtmlResponse.body is not modifiable, use HtmlResponse.replace() instead
>>> new_response = response.replace(body=new_body)
>>> new_response.xpath('//bar')
[<Selector xpath='//bar' data=u'<bar></bar>'>]

Diamo ora un’occhiata molto generica a xpath.

Xpath: parentele

Supponiamo di avere una struttura di questo genere:

<A-TAG class="a-class">A-text
    <B-TAG class="b-class">B-text</B-TAG>
    <B-TAG id="1">B-text
        <C-TAG class="c-class" id="3">C-text</C-TAG>
        <C-TAG class="c-class" id="4">C-text
            <D-TAG>D-text</D-TAG> 
        </C-TAG>
    </B-TAG>
    <B-TAG id="2">B-text
        <C-TAG id="5">C-text
            <D-TAG>D-text</D-TAG>
            <D-TAG id="6">D-text
                <E-TAG>E-text</E-TAG>
                <E-TAG>E-text
                    <F-TAG>F-text</F-TAG>
                </E-TAG>
            </D-TAG>
        </C-TAG>
    </B-TAG>
    <B-TAG attr="b-attr">B-text
        <C-TAG>C-text
            <D-TAG>D-text</D-TAG>
        </C-TAG>
    </B-TAG>
</A-TAG>

prepariamo il selector con il testo precedente:

>>> body="""
... <A-TAG class="a-class">A-text
...     <B-TAG class="b-class">B-text</B-TAG>
...     <B-TAG id="1">B-text
...         <C-TAG class="c-class" id="3">C-text</C-TAG>
...         <C-TAG class="c-class" id="4">C-text
...             <D-TAG>D-text</D-TAG>
...         </C-TAG>
...     </B-TAG>
...     <B-TAG id="2">B-text
...         <C-TAG id="5">C-text
...             <D-TAG>D-text</D-TAG>
...             <D-TAG id="6">D-text
...                 <E-TAG>E-text</E-TAG>
...                 <E-TAG>E-text
...                     <F-TAG>F-text</F-TAG>
...                 </E-TAG>
...             </D-TAG>
...         </C-TAG>
...     </B-TAG>
...     <B-TAG attr="b-attr">B-text
...         <C-TAG>C-text
...             <D-TAG>D-text</D-TAG>
...         </C-TAG>
...     </B-TAG>
... </A-TAG>
... """
>>> selector = Selector(text=body)

Nota:
Prima di analizzare le parentele è bene fare una distinzione sui percorsi.
Se l’espressione xpath comincia con ‘/’ si vogliono ricercare percorsi assoluti,
mentre con ‘//’ si cercano TUTTi gli elementi che sposano i criteri successivi.

Iniziamo ad analizzare le parentele ricordandoci di utilizzare il filtri tag in MINUSCOLO!

>>> selector
<Selector xpath=None data=u'<html><body><a-tag class="a-class">A-tex...'
>>>> len(selector.xpath('//B-TAG'))
0
>>> len(selector.xpath('//b-tag'))
4

ancestor::

Ritorna tutti i predecessori (ancestors ovvero genitori, genitori di genitori e così via)
E’ importantissimo ricordarsi di chiamare la funzione position() che decide quale
livello di ancestor si è scelto di conoscere:

>>> selector = Selector(text=body)
>>> for pos in range(1, 4):
        selector.xpath('//c-tag[@id=4]/ancestor::*[position() = %s]' % pos)
...
[<Selector xpath='//c-tag[@id=4]/ancestor::*[position() = 1]' data=u'<b-tag id="1">B-text\n        <c-tag clas'>]
[<Selector xpath='//c-tag[@id=4]/ancestor::*[position() = 2]' data=u'<a-tag class="a-class">A-text\n    <b-tag'>]
[<Selector xpath='//c-tag[@id=4]/ancestor::*[position() = 3]' data=u'<body><a-tag class="a-class">A-text\n    '>]

All’aumentare del numero di posizione, ci allontaniamo con il predecessore: 1=b-tag, 2=a-tag, 3=”body”
‘c-tag[@id=4]’ serve a filtrare tutti i c-tag con attributo id=4, se omettessi questo filtro,
otterrei gli ancestors di tutti i nodi c-tag (o ch iper lui):

>>> for s in selector.xpath('//c-tag/ancestor::*[position() = 1]'): print s
...
<Selector xpath='//c-tag/ancestor::*[position() = 1]' data=u'<b-tag id="1">B-text\n        <c-tag clas'>
<Selector xpath='//c-tag/ancestor::*[position() = 1]' data=u'<b-tag id="2">B-text\n        <c-tag id="'>
<Selector xpath='//c-tag/ancestor::*[position() = 1]' data=u'<b-tag attr="b-attr">B-text\n        <c-t'>

ancestor-or-self::

Ritorna il nodo corrente (self) e come nel caso precedente tutti i predecessori (ancestors).
In questo caso la posizione 1 corrisponde a self (il nodo stesso)

>>> for pos in range(1, 5):
...     selector.xpath('//c-tag[@id=4]/ancestor-or-self::*[position() = %s]' % pos)
...
[<Selector xpath='//c-tag[@id=4]/ancestor-or-self::*[position() = 1]' data=u'<c-tag class="c-class" id="4">C-text\n   '>]
[<Selector xpath='//c-tag[@id=4]/ancestor-or-self::*[position() = 2]' data=u'<b-tag id="1">B-text\n        <c-tag clas'>]
[<Selector xpath='//c-tag[@id=4]/ancestor-or-self::*[position() = 3]' data=u'<a-tag class="a-class">A-text\n    <b-tag'>]
[<Selector xpath='//c-tag[@id=4]/ancestor-or-self::*[position() = 4]' data=u'<body><a-tag class="a-class">A-text\n    '>]

attribute::

Ritorna gli attributi del nodo corrente

>>> for s in selector.xpath('//c-tag[@id=4]/attribute::*'): print s
...
<Selector xpath='//c-tag[@id=4]/attribute::*' data=u'c-class'>
<Selector xpath='//c-tag[@id=4]/attribute::*' data=u'4'>

child::

Ritorna tutti i figli de nodo corrente

>>> for s in selector.xpath('//b-tag[@id=1]/child::*'): print s
...
<Selector xpath='//b-tag[@id=1]/child::*' data=u'<c-tag class="c-class" id="3">C-text</c-'>
<Selector xpath='//b-tag[@id=1]/child::*' data=u'<c-tag class="c-class" id="4">C-text\n   '>

child è sottinteso essere il nodo corrente quindi la sintassi

>>> for s in selector.xpath('//b-tag[@id=1]/child::c-tag'): print s
...
<Selector xpath='//b-tag[@id=1]/child::c-tag' data=u'<c-tag class="c-class" id="3">C-text</c-'>
<Selector xpath='//b-tag[@id=1]/child::c-tag' data=u'<c-tag class="c-class" id="4">C-text\n   '>
>>> for s in selector.xpath('//b-tag[@id=1]/c-tag'): print s
...
<Selector xpath='//b-tag[@id=1]/c-tag' data=u'<c-tag class="c-class" id="3">C-text</c-'>
<Selector xpath='//b-tag[@id=1]/c-tag' data=u'<c-tag class="c-class" id="4">C-text\n   '>

descendant::
Ritorna tutti i nodi discendenti del noto corrente (figli, figli dei figli ecc)

>>> for s in selector.xpath('//b-tag[@id=1]/descendant::*'): print s
...
<Selector xpath='//b-tag[@id=1]/descendant::*' data=u'<c-tag class="c-class" id="3">C-text</c-'>
<Selector xpath='//b-tag[@id=1]/descendant::*' data=u'<c-tag class="c-class" id="4">C-text\n   '>
<Selector xpath='//b-tag[@id=1]/descendant::*' data=u'<d-tag>D-text</d-tag>'>

descendant-or-self::

Ritorna tutti i discendenti del nodo corrente, partendo dal nodo stesso (position())

>>> for pos in range(1, 4):
...     r.xpath('//root/descendant-or-self::*[position()= %s]' % pos)
...
[<Selector xpath='//root/descendant-or-self::*[position()= 1]' data=u'<root><foo><bar attr="x"></bar></foo><fo'>]
[<Selector xpath='//root/descendant-or-self::*[position()= 2]' data=u'<foo><bar attr="x"></bar></foo>'>]
[<Selector xpath='//root/descendant-or-self::*[position()= 3]' data=u'<bar attr="x"></bar>'>]

following::

Ritorna tutto ciò che nel documento viene dopo il tag di chiusura del nodo corrente:

>>> for s in selector.xpath('//b-tag[@id=1]/following::*'): print s
...
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<b-tag id="2">B-text\n        <c-tag id="'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<c-tag id="5">C-text\n            <d-tag>'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<d-tag>D-text</d-tag>'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<d-tag id="6">D-text\n                <e-'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<e-tag>E-text</e-tag>'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<e-tag>E-text\n                    <f-tag'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<f-tag>F-text</f-tag>'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<b-tag attr="b-attr">B-text\n        <c-t'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<c-tag>C-text\n            <d-tag>D-text<'>
<Selector xpath='//b-tag[@id=1]/following::*' data=u'<d-tag>D-text</d-tag>'>

se vogliamo filtrare il risultato ad esempio solo i ‘c-tag’ successivi:

>>> for s in selector.xpath('//b-tag[@id=1]/following::c-tag'): print s
...
<Selector xpath='//b-tag[@id=1]/following::c-tag' data=u'<c-tag id="5">C-text\n            <d-tag>'>
<Selector xpath='//b-tag[@id=1]/following::c-tag' data=u'<c-tag>C-text\n            <d-tag>D-text<'>

following-sibling::

Ritorna tutti i fratelli (siblings, cioè i nodi sullo stesso ASSE) dopo il nodo corrente:

>>> for s in selector.xpath('//b-tag[@id=1]/following-sibling::*'): print s
...
<Selector xpath='//b-tag[@id=1]/following-sibling::*' data=u'<b-tag id="2">B-text\n        <c-tag id="'>
<Selector xpath='//b-tag[@id=1]/following-sibling::*' data=u'<b-tag attr="b-attr">B-text\n        <c-t'>

parent::

Ritorna il genitore del nodo corrente

>>> selector.xpath('//b-tag[@id=1]/parent::*')
[<Selector xpath='//b-tag[@id=1]/parent::*' data=u'<a-tag class="a-class">A-text\n    <b-tag'>]

preceding::

Ritorna tutto ciò che sta prima del tag di apertura del nodo corrente:

>>> for s in selector.xpath('//b-tag[@id=1]/preceding::*'): print s
...
<Selector xpath='//b-tag[@id=1]/preceding::*' data=u'<b-tag class="b-class">B-text</b-tag>'>

preceding-sibling::

Ritorna tutti i fratelli (sibling) prima del nodo corrente

>>> for s in selector.xpath('//b-tag[@id=1]/preceding-sibling::*'): print s
...
<Selector xpath='//b-tag[@id=1]/preceding-sibling::*' data=u'<b-tag class="b-class">B-text</b-tag>'>

self::

Ritorna il nodo corrente

>>> selector.xpath('//b-tag[@id=1]/self::*')
[<Selector xpath='//b-tag[@id=1]/self::*' data=u'<b-tag id="1">B-text\n        <c-tag clas'>]

Riassumendo:

1. I percorsi possono essere ASSOLUTI:

>>> for s in selector.xpath('body/a-tag/b-tag/c-tag'): print s
...
<Selector xpath='body/a-tag/b-tag/c-tag' data=u'<c-tag class="c-class" id="3">C-text</c-'>
<Selector xpath='body/a-tag/b-tag/c-tag' data=u'<c-tag class="c-class" id="4">C-text\n   '>
<Selector xpath='body/a-tag/b-tag/c-tag' data=u'<c-tag id="5">C-text\n            <d-tag>'>
<Selector xpath='body/a-tag/b-tag/c-tag' data=u'<c-tag>C-text\n            <d-tag>D-text<'>

2. I percorsi possono essere RELATIVI:

>>> for s in selector.xpath('//c-tag/../..'): print s
...
<Selector xpath='//c-tag/../..' data=u'<a-tag class="a-class">A-text\n    <b-tag'>

3. La sintassi Xpath formale è la seguente:

axisname::nodetest[predicate]

link utili:
parte 2. xpath: Abbraviazioni
parte 3. xpath: Funzioni generiche
scrapy
xpath syntax

Categorie:scrapy, xpath Tag: ,
I commenti sono chiusi.