Archivio

Posts Tagged ‘xpath’

scrapy: spider

12 Ottobre 2017 Commenti chiusi

Articolo precedente: primi passi con scrapy

Spiders

Gli Spiders sono classi che definiscono come un sito o gruppi di siti, debbano essere analizzati.
In queste classi si decide come sarà il crawling/parsing del sito o dei gruppi di siti e come
saranno strutturati i dati estratti da tali pagine web.

Il progetto è stato già creato precedentemente, si tratta ora di creare lo spider adatto al nostro scopo.
Esiste un direttorio nome-progetto/nome-progetto//spiders all’interno del quale creeremo un file nome-progetto_spider.py nel mio caso, nel direttorio ansa/ansa/spiders, creerò un file ‘ansa_spider.py‘ con all’interno il codice necessario per lo scraping/crawling del sito in oggetto.

Vediamo il ciclo di funzionamento dello spider.

1. Requests iniziali

Generazione delle Requests iniziali per “raschiare” (crawl) le prime URLs e
specifica della funzione di callback da invocare con il Response ottenuto da queste prime Requests.
Le prime requests da eseguire sono ottenute con la chiamata del metodo start_requests()
che di default genera Request per le URLs specificate in una lista, chiamando per ognuna di esse
la corrispondente funzione di callback parse().

import scrapy


class AnsaSpider(scrapy.Spider):
    name = "ansa"
    allowed_domains = ["ansa.it"]

    def start_requests(self):
        urls = ["http://www.ansa.it",]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        pass

“name” è il nome univoco con il quale scrapy troverà ed utilizzerà lo spider.

“allowed_domains” è la lista dei domini autorizzati ad essere analizzati dallo spider.
Se il middleware OffsiteMiddleware sarà abilitato, i domini non presenti in questa lista,
non verranno analizzati dallo spider.

“start_requests” è il metodo che ritorna la lista con le prime Requests da analizzare. Viene invocato da Scrapy
quando apre lo spider per iniziare l’analisi.

Callback parse()

Nella funzione di callback parse(), eseguiamo appunto il parsing del response (web page) e ritorniamo:
– dizionario con i dati estratti
– Item object
– Request objects, o un iterable di tali oggetti

Con sintassi xpath, il primo abbozzo di spider sarebbe:

# ansa_spider.py
import scrapy


class AnsaSpider(scrapy.Spider):
    name = "ansa"
    allowed_domains = ["ansa.it"]

    def start_requests(self):
        start_urls = ["http://www.ansa.it",]
        for url in start_urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        xf = '//article[@class="news"]/descendant::a/@href'
        for url in response.xpath(xf).extract():
             yield {"title": url}

mentre la stessa con il metodo css():

# ansa_spider.py
import scrapy


class AnsaSpider(scrapy.Spider):
    name = "ansa"
    allowed_domains = ["ansa.it"]

    def start_requests(self):
        start_urls = ["http://www.ansa.it",]
        for url in start_urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        for url in response.css('article.news a::attr(href)').extract():
             yield {"title": url}

Lanciamo lo spider e viediamo che succede:

scrapy crawl ansa

come si nota, lo spider estrapola tutti i link presenti nella web page, ritornando un dizionario per ogni
link trovato. Se vogliamo generare un file con questi dati:

scrapy crawl ansa -o ansa.json

Nota:

Al momento scrapy esegue solo l’append dei dati e non sovrascrive il file, se già esistente.
Ricordarsi di eliminarlo prima di procedere con l’estrazione dei dati!

volendo estrarre i dati dal file, entriamo nella shell:

scrapy shell
import json


with open('ansa.json') as f:
    data = json.loads(f.read())

d = {n: v.get('link') for n, v in enumerate(data, 1) if v.get('link') != "javascript:void(0);"}

Tutte queste Requests potranno essere parsate e scaricate a loro volta da scrapy, facendo riferimento ad
una ulteriore callback (anche la stessa parse() volendo).

import scrapy


class AnsaSpider(scrapy.Spider):
    name = "ansa"
    allowed_domains = ["ansa.it"]

    def start_requests(self):
        start_urls = ["http://www.ansa.it",]
        for url in start_urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        xf = '//article[@class="news"]/descendant::a/@href'
        for url in response.xpath(xf).extract():
            if url and url != "javascript:void(0);":
                yield response.follow(url, callback=self.parse_news)

    def parse_news(self, response):
        title = response.xpath('//title/text()').extract_first()
        x_body = '//div[@class="news-txt"]/descendant::p'
        body = ' '.join(([node.xpath('string()').extract()[0]
                          for node in response.xpath(x_body)]))
        yield {'title': title,
               'body': body,
               }

Ora possiamo accedere ad un dizionario che al posto dei singoli link, ha i testi degli articoli.
Ma se volessimo portarci dietro dal primo parse, all’interno delle pagine figlie qualche attributo tipo ad es.
il link alla pagina figlia? Questo ha senso ovviamento per parametri che non sono reperibili nelle pagine figlie,
ma che debbano essere “trasportati” da un parse all’altro.

In questo caso ci vengono in aiuto gli items.

Dentro al file ansa/items.py definiamo il nostro personale Item:

import scrapy


class AnsaItem(scrapy.Item):
    # define the fields for your item here like:
    link = scrapy.Field()

Con l’ausilio di quest’ultimo, possiamo modificare il codice dello spider come segue:

import scrapy
from ansa.items import AnsaItem
from scrapy.exceptions import NotSupported


class AnsaSpider(scrapy.Spider):
    name = "ansa"
    allowed_domains = ["ansa.it"]

    def start_requests(self):
        start_urls = ["http://www.ansa.it",]
        for url in start_urls:
            print '[INFO] Parsing url %s...' % url
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        xf = '//article[@class="news"]/descendant::a[position()=1]/@href'
        for url in response.xpath(xf).extract():
            if url and url != "javascript:void(0);":
                short_url = '/.../%s' % url.split('/')[-1]
                print '[INFO] Parsing urls %s = %s' % (n, short_url)
                ansa_item = AnsaItem()
                # ansa_item assignments
                ansa_item['link'] = url
                yield response.follow(url, callback=self.parse_news,
                                      meta={'ansa_item': ansa_item})

    def parse_news(self, response):
        ansa_item = response.meta.get('ansa_item')
        link = ansa_item.get('link')
        try:
            title = response.xpath('//title/text()').extract_first()
            x_body = '//div[@class="news-txt"]/descendant::p'
            body = ' '.join(([node.xpath('string()').extract()[0]
                              for node in response.xpath(x_body)]))
            yield {'link': link.strip(),
                   'title': title.strip(),
                   'body': body.strip(),
                  }
        except NotSupported:
            print "[WARNING] Response %s not crawled" % response

Nell’Item, gli assegnamenti agli attributi e l’accesso ad essi si comportano
allo stesso modo dei dizionari.
Come si vede, nella prima callback parse(), viene creato l’item e gli si assegna alla chiave ‘link’ il
valore di url. Quando utilizzeremo il metodo follow dell’oggetto response, per analizzare le sottopagine,
richiameremo la callback parse_news() e passeremo al metodo follow(),
l’attributo meta, un dizionario con all’interno il nostro item.
Questo item, viene recuperato all’interno della callback parse_news() ed il valore della sua
chiave ‘link’, ritornato nel dizionario con yield, insieme agli altri valori.

Eliminiamo un eventuale file json preesistente e ricreiamo il tutto:


>>> import json
>>> with open('ansa.json') as f:
...     data = json.loads(f.read())
...
>>> news_dict = {n: value for n, value in enumerate(data, 1)}
>>> news_1 = news_dict.get(1)
>>> news_1.get('title')
u"Ultimatum..."
>>> news_1.get('link')
u'/sito/notizie/mondo/europa/...'

link utili:
scrapy: appunti
scrapy
xpath: appunti

Categorie:python, scrapy, xpath Tag: , ,

scrapy: appunti

12 Ottobre 2017 Commenti chiusi

Installazione

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

Ora per vedere che tutto funzioni, è sufficiente eseguire il comando:

scrapy shell
...
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x04108D90>
[s]   item       {}
[s]   settings   <scrapy.settings.Settings object at 0x04108E90>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser

per uscire:

>>>exit()

Progetto

Creiamo il primo progetto, ad esempio un estrattore di ultime notizie dell’ANSA:

scrapy startproject ansa

prima però di scrivere codice, prendiamo confidenza con i principali comandi.
Lanciamo la shell di scrapy dandole in pasto la pagina web che ci interessa:

scrapy shell "www.ANSA.it"
...
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x04035DB0>
[s]   item       {}
[s]   request    <GET http://www.ANSA.it>
[s]   response   <200 http://www.ANSA.it>
[s]   settings   <scrapy.settings.Settings object at 0x04035EF0>
[s]   spider     <DefaultSpider 'default' at 0x4302fb0>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
...

come si nota ci vengono messi a disposizione alcuni oggetti, tra i quali ‘response’, sul quale è
possibile effettuare query di ogni tipo. Si possono usare sia il metodo css() che il metodo xpath().
Ad es. voglio ottenere il titolo della pagina:

>>> response.css('title')
[<Selector xpath=u'descendant-or-self::title' data=u'<title>ANSA.it</title>'>]
>>> response.xpath('//title')
[<Selector xpath='//title' data=u'<title>ANSA.it</title>'>]

Nel caso di utilizzo del metodo css() è previsto l’utilizzo di una sintassi dedicata.
Il metodo css() comunque ritornerà sempre un oggetto selector che si rifarà alla sintassi
di Xpath:

>>> response.css('title')
[<Selector xpath=u'descendant-or-self::title' data=u'<title>ANSA.it</title>'>]
>>> response.css('title::text')
[<Selector xpath=u'descendant-or-self::title/text()' data=u'ANSA.it'>]
>>> response.css('title::text').extract_first()
u'ANSA.it'

Selector

Un selector altro non è che un’istanza della classe Selector, alla quale viene passata una stringa
o un oggetto TextResponse e, a seconda del dato in ingresso sceglie il tipo di parsing
(XML o HTML) da usare:

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

Xpath è il linguaggio che permette di selezionare i nodi in un documento XML e può essere utilizzato
anche per l’HTML.
come si nota, si passa una stringa html al selector e con il metodo extract(),
si ottiene una lista di testi che rispondono ai nostri criteri, o con extract_first()
si ottiene il primo della lista.
Attenzione ai path:
nel primo caso :’//bar’ troviamo il tag ed il testo contenuto da esso;
nel secondo caso: ‘//bar/text()’ troviamo solo il testo contenuto da quel tag!
Stesso principio per gli oggetti TextResponse:

>>> from scrapy.http import HtmlResponse
>>> resp = HtmlResponse(url='', body=body)
>>> Selector(response=resp).xpath('//bar[text()]').extract_first()
u'<bar>Some text</bar>'

>>> Selector(response=resp).xpath(‘//bar/text()’).extract_first()
u’Some text’

Xpath

Rispetto all’utilizzo dei Selector CSS, le espressioni Xpath sono più potenti
perchè oltre a navigare attraverso la struttura della pagina web, permettono di
osservare i contenuti dei nodi.
Qui è disponibile una serie di appunti su Xpath.

>>> response.xpath('//title[text()]').extract()
[u'<title>ANSA.it</title>']

Andiamo al sodo:
vogliamo ottenere una lista di tutti i selector che contengono articoli?
Per come è strutturata la pagina che stiamo analizzando, gli articoli sono racchiusi all’interno di tag “article”,
quindi:

>>> response.css('article').extract()
...
>>> response.xpath('//article').extract()
...

ovviamente per ottenere il primo della lista, basterebbe chiamare il metodo extract_first():

>>> response.css('article').extract_first()
...

Vediamo come ottenere i sunti delle singole notizie. Queste sono identificabili ad esempio dal tag

.
Quindi vogliamo filtrate tutti i tag

con attributo class=”pp-abs”:

>>> response.css('p.pp-abs').extract()
...

dove ‘p.pp-abs’ sta appunto per

Con la sintassi xpath (qui una breve guida sulla sintassi) sarebbe:

>>> response.xpath('//p[@class="pp-abs"]').extract()
...

Se volessimo solo i testi e non i tag contenitori:

>>> response.css('p.pp-abs::text').extract()
...
>>> response.xpath('//p[@class="pp-abs"]/text()').extract()
...

Oltre al sunto delle notizie, vogliamo ottenere i link delle stesse?
Per come è strutturata la pagina web (‘analizza sorgente pagina’ da browser…) potrei fare qualcosa del genere:

for r in response.css('article.news'):
    p = r.css('p::text').extract_first()
    href = r.css('a::attr(href)').extract_first()
    if p and href != "javascript:void(0);":
        print "%s\nlink: %s" % (p, href)

La condizione if è dovuta al fatto che non sempre all’interno del tag

è disponibile un un tag

e
quindi il metodo extract_first() ritornerebbe ‘None’ e inoltre molti href punterebbero al valore
“javascript:void(0);” che non vogliamo.

Con xpath invece:

for r in response.xpath('//article[@class="news"]'):
    p = r.xpath('descendant::p/text()').extract_first()
    href = r.xpath('descendant::a/@href').extract_first()
    if p and href != "javascript:void(0);":
        print "%s\nlink: %s" % (p, href)

Vediamo adesso come fare per memorizzare tutti i link delle notizie in anteprima ed aprirle tutte,
per memorizzarne il testo completo. La cosa si complica un po’, ma non troppo.
Per questo tipo di cosa utilizzeremo gli spiders.

link utili:
scrapy: spider
scrapy
xpath: appunti

Categorie:python, scrapy, xpath Tag: , ,

Xpath: funzioni

11 Ottobre 2017 Commenti chiusi

Abbiamo parlato della parentele nella parte 1,
e delle abbreviazioni nella parte 2,
vediamo come utilizzare le funzioni messe a disposizione da Xpath.

Funzioni generiche

facciamo sempre riferimento al 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>

1. count()

Ritorna il numero dei nodi in un node-set:

>>> selector = Selector(text=body)
>>> selector.xpath('count(//b-tag)')
[<Selector xpath='count(//b-tag)' data=u'4.0'>]
>>> selector.xpath('count(//f-tag)')
[<Selector xpath='count(//f-tag)' data=u'1.0'>]

per comodità di lettura useremo il metodo extract_first() dell’ oggetto selector:

>>> selector.xpath('count(//b-tag)').extract_first()
u'4.0'
>>> selector.xpath('count(//f-tag)').extract_first()
u'1.0'

2. id()

Seleziona gli elementi in base al loro ID univoco:
id(’id_1’)

>>> selector.xpath('id(1)')
[<Selector xpath='id(1)' data=u'<b-tag id="1">B-text\n\n        <c-tag cla'>]

3. last()

Ritorna gli ultimi elementi di quel tipo:

>>> selector.xpath('//b-tag[last()]')
[<Selector xpath='//b-tag[last()]' data=u'<b-tag attr="b-attr">B-text\n\n        <c-'>]

Come si nota mancano i primi 3 b-tag.

>>> response.xpath('//foo[last()]')
[<Selector xpath='//foo[last()]' data=u'<foo>\n        <bar attr="z">z text</bar>'>]

4. name()

Ritorna il nome di un nodo:

>>> for n, s in enumerate(selector.xpath('//b-tag[name()]')): print n, s
... 
0 <Selector xpath='//b-tag[name()]' data=u'<b-tag class="b-class">B-text</b-tag>'>
1 <Selector xpath='//b-tag[name()]' data=u'<b-tag id="1">B-text\n\n        <c-tag cla'>
2 <Selector xpath='//b-tag[name()]' data=u'<b-tag id="2">B-text\n\n        <c-tag id='>
3 <Selector xpath='//b-tag[name()]' data=u'<b-tag attr="b-attr">B-text\n\n        <c-'>

5. position()

Ritorna la posizione nella lista nodi del nodo che viene processato:

>>> for pos in range(1, 5):
...     print pos, selector.xpath('//b-tag[position() = %s]' % pos)
... 
1 [<Selector xpath='//b-tag[position() = 1]' data=u'<b-tag class="b-class">B-text</b-tag>'>]
2 [<Selector xpath='//b-tag[position() = 2]' data=u'<b-tag id="1">B-text\n\n        <c-tag cla'>]
3 [<Selector xpath='//b-tag[position() = 3]' data=u'<b-tag id="2">B-text\n\n        <c-tag id='>]
4 [<Selector xpath='//b-tag[position() = 4]' data=u'<b-tag attr="b-attr">B-text\n\n        <c-'>]

6. contains()

Ritorna gli elementi per i quali la stringa passata come primo argomento, contiene la stringa
passata come secondo argomento

>>> for n, s in enumerate(selector.xpath('//*[contains(name(), "b-tag")]')): print n, s
... 
0 <Selector xpath='//*[contains(name(), "b-tag")]' data=u'<b-tag class="b-class">B-text</b-tag>'>
1 <Selector xpath='//*[contains(name(), "b-tag")]' data=u'<b-tag id="1">B-text\n\n        <c-tag cla'>
2 <Selector xpath='//*[contains(name(), "b-tag")]' data=u'<b-tag id="2">B-text\n\n        <c-tag id='>
3 <Selector xpath='//*[contains(name(), "b-tag")]' data=u'<b-tag attr="b-attr">B-text\n\n        <c-'>

In questo caso abbiamo filtrato tutti gli elementi i quali nomi (name()) contengono la stringa “b-tag”.
E’ possibile filtrare anche per attributo:

>>> for n, s in enumerate(selector.xpath('//b-tag[contains(@class, "b")]')): print n, s... 
0 <Selector xpath='//b-tag[contains(@class, "b")]' data=u'<b-tag class="b-class">B-text</b-tag>'>

7. starts-with()
Ritorna gli elementi per i quali la stringa passata come primo argomento, comincia con la stringa
passata come secondo argomento

>>> for n, s in enumerate(selector.xpath('//*[starts-with(name(), "f")]')): print n, s
... 
0 <Selector xpath='//*[starts-with(name(), "f")]' data=u'<f-tag>F-text</f-tag>'>

ci sono molte altre funzioni utilizzabili:

8. normalize-space()

Rimuove gli spazi ad inizio e fine stringa e sostituisce tutti gli spazi all’interno della stringa
con uno spazio solo:

>>> selector = scrapy.Selector(text='<bar name="bar1">  A text    spaced  </bar><bar name="bar2"> Another  text</bar>')
>>> selector.xpath('normalize-space(//bar)').extract()
[u'A text spaced']
>>> selector.xpath('normalize-space(//bar[@name="bar2"])').extract()
[u'Another text']

Funziona chiaramente anche sugli attributi:

>>> selector = scrapy.Selector(text='<bar name="  bar  1">  A text    spaced  </bar><bar name="bar2"> Another  text</bar>')
>>> selector.xpath('normalize-space(//bar/attribute::name)').extract()
[u'bar 1']

9. string()

Converte un argomento del nodo in oggetto, in stringa:

>>> selector = scrapy.Selector(text='<bar name="  bar  1">  A text    spaced  </bar><bar name="bar2"> Another  text</bar>')
>>> selector.xpath('string(//bar[1]/@name)').extract()
[u'  bar  1']

10. substring(string, start, n_chars)

Ritorna la parte di stringa specificata dal primo argomento (string),
partendo dall’indice indicato nel secondo argomento (start) e ritornando la
quantità di caratteri indicati dal terzo argomento (n_chars)

>>> selector = scrapy.Selector(text='<bar name="  bar  1">  A text    spaced  </bar><bar name="bar2"> Another  text</bar>')
>>> selector.xpath('//bar[1]/text()').extract_first()
u'  A text    spaced  '
>>> selector.xpath('substring(//bar[1]/text(), 5, 4)').extract_first()
u'text'

11. substring-after(string, prefix)

Ritorna la parte di stringa “string” che viene dopo la parte di stringa passata come
secondo argomento (prefix).

>>> selector = scrapy.Selector(text='<bar>string substring</bar>')
>>> selector.xpath('substring-after(//bar/text(), "string")').extract_first()
u' substring'

12. substring-before(string, siffix)

Ritorna la parte di stringa “string” che viene prima della parte di stringa passata come
secondo argomento (suffix).

>>> selector.xpath('substring-before(//bar/text(), "substring")').extract_first()
u'string '

13. translate(string, string_chars, new_chars)

Esegue una sostituzione carattere per carattere di una stringa (string), sostituendo i caratteri
della stringa stessa (string_chars) con quelli nuovi (new_chars):

>>> selector = scrapy.Selector(text='<bar>abcd</bar>')
>>> selector.xpath('translate(//bar/text(), "bc", "yz")').extract_first()
u'ayzd'

Funzioni numeriche

1. ceiling()

Ritorna il primo intero maggiore del numero dato:

>>> selector.xpath('ceiling(3.141)').extract_first()
u'4.0'

2. floor()

Ritorna il primo intero minore del numero dato:

>>> selector.xpath('floor(3.141)').extract_first()
u'3.0'

3. number()

Converte l’argomento passato in ingresso in numero quando possibile, o,
in caso contrario, in NAN:

>>> selector.xpath('number(100)').extract_first()
u'100.0'
>>> selector.xpath('number(a)').extract_first()
u'nan'

4. round()

Arrotonda il numero dato all’intero subito inferiore:

>>> selector.xpath('floor(1.999)').extract_first()
u'1.0'

5. sum()

Ritorna il totale da un insieme di numeri:

>>> selector = scrapy.Selector(text='<bar>1</bar><bar>2</bar>')
>>> selector.xpath('sum(//bar/text())').extract_first()
u'3.0'

6. not()
Ritorna gli elementi che non soddisfano un certo parametro:

>>> selector = scrapy.Selector(text='<bar name="bar1">1</bar><bar>2</bar>')
>>> selector.xpath('//bar[not(@name)]').extract()
[u'<bar>2</bar>']

In questo caso abbiamo selezionato gli elementi che NON hanno un attributo

Questo è quanto. Un buon punto di partenza per utilizzare xpath.

link utili:
parte 1. xpath: Appunti
parte 2. abbreviazioni
scrapy
xpath syntax

Categorie:scrapy, xpath Tag: ,

Xpath: abbreviazioni

10 Ottobre 2017 Commenti chiusi

Abbiamo parlato della parentele nella parte 1, vediamo come utilizzare
le abbreviazioni disponibili

Il body utilizzato rimane lo stesso:

<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>

Abbreviazioni

1.’child::’

è sottinteso pertanto le sintassi:

selector.xpath('//c-tag')
selector.xpath('//b-tag/c-tag')

equivalgono a

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

2. ‘@’

è la stessa cosa che scrivere ‘attribute:: ‘

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

attenzione ai path!
se al posto delle parentesi quadre, usassi lo /, invece del tag che contiene quel
determinato attributo, selezionerei l’attributo stesso:

>>> selector.xpath('//child::b-tag/@id=1')
[<Selector xpath='//child::b-tag/@id=1' data=u'1'>]

La differenza è evidente se osserviamo il valore di data, o
se utilizziamo il metodo extract() di scrapy:

>>> selector.xpath('//child::b-tag[@id=1]').extract()
[u'<b-tag id="1">B-text\n        <c-tag class="c-class" id="3">C-text</c-tag><c-tag class="c-class"...tag>']
>>> selector.xpath('//child::b-tag/@id=1').extract()
[u'1']

Nel primo caso infatti otteniamo il b-tag con id=1, nel secondo caso otteniamo il testo
dell’attributo id del b-tag con tale id, cioè ‘1’.

3. ‘.’

è la stessa cosa di ‘self::’, ad esempio ‘.//b-tag’ equivale a ‘self::node()//child::b-tag’:

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

4. ‘..’

è la stessa cosa di ‘parent::’, ad esempio ‘//b-tag/..’ equivale a ‘//child::b-tag/parent::node()’:

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

5. ‘//’

è la stessa cosa di ‘descendant-or-self::’, ad esempio ‘//b-tag//c-tag’ equivale
‘//child::b-tag/descendant-or-self::node()/child::c-tag’

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

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

link utili:
parte 1. xpath: Appunti
parte 3. xpath: Funzioni generiche
scrapy
xpath syntax

Categorie:scrapy, xpath Tag: ,

Xpath: appunti

10 Ottobre 2017 Commenti chiusi

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: ,