Scrapy框架的使用之Selector的用法

崔慶才丨靜覓發表於2019-03-04

Scrapy提供了自己的資料提取方法,即Selector(選擇器)。Selector是基於lxml來構建的,支援XPath選擇器、CSS選擇器以及正規表示式,功能全面,解析速度和準確度非常高。

本節將介紹Selector的用法。

1. 直接使用

Selector是一個可以獨立使用的模組。我們可以直接利用Selector這個類來構建一個選擇器物件,然後呼叫它的相關方法如xpath()css()等來提取資料。

例如,針對一段HTML程式碼,我們可以用如下方式構建Selector物件來提取資料:

from scrapy import Selector

body = '<html><head><title>Hello World</title></head><body></body></html>'
selector = Selector(text=body)
title = selector.xpath('//title/text()').extract_first()
print(title)複製程式碼

執行結果如下所示:

Hello World複製程式碼

我們在這裡沒有在Scrapy框架中執行,而是把Scrapy中的Selector單獨拿出來使用了,構建的時候傳入text引數,就生成了一個Selector選擇器物件,然後就可以像前面我們所用的Scrapy中的解析方式一樣,呼叫xpath()css()等方法來提取了。

在這裡我們查詢的是原始碼中的title中的文字,在XPath選擇器最後加text()方法就可以實現文字的提取了。

以上內容就是Selector的直接使用方式。同Beautiful Soup等庫類似,Selector其實也是強大的網頁解析庫。如果方便的話,我們也可以在其他專案中直接使用Selector來提取資料。

接下來,我們用例項來詳細講解Selector的用法。

2. Scrapy Shell

由於Selector主要是與Scrapy結合使用,如Scrapy的回撥函式中的引數response直接呼叫xpath()或者css()方法來提取資料,所以在這裡我們藉助Scrapy Shell來模擬Scrapy請求的過程,來講解相關的提取方法。

我們用官方文件的一個樣例頁面來做演示:http://doc.scrapy.org/en/latest/_static/selectors-sample1.html。

開啟Scrapy Shell,在命令列輸入如下命令:

scrapy shell http://doc.scrapy.org/en/latest/_static/selectors-sample1.html複製程式碼

我們就進入到Scrapy Shell模式。這個過程其實是,Scrapy發起了一次請求,請求的URL就是剛才命令列下輸入的URL,然後把一些可操作的變數傳遞給我們,如requestresponse等,如下圖所示。

Scrapy框架的使用之Selector的用法

我們可以在命令列模式下輸入命令呼叫物件的一些操作方法,回車之後實時顯示結果。這與Python的命令列互動模式是類似的。

接下來,演示的例項都將頁面的原始碼作為分析目標,頁面原始碼如下所示:

<html>
 <head>
  <base href='http://example.com/' />
  <title>Example website</title>
 </head>
 <body>
  <div id='images'>
   <a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
   <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
   <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
   <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
   <a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
  </div>
 </body>
</html>複製程式碼

3. XPath選擇器

進入Scrapy Shell之後,我們將主要操作response這個變數來進行解析。因為我們解析的是HTML程式碼,Selector將自動使用HTML語法來分析。

response有一個屬性selector,我們呼叫response.selector返回的內容就相當於用responsebody構造了一個Selector物件。通過這個Selector物件我們可以呼叫解析方法如xpath()css()等,通過向方法傳入XPath或CSS選擇器引數就可以實現資訊的提取。

我們用一個例項感受一下,如下所示:

>>> result = response.selector.xpath('//a')
>>> result
[<Selector xpath='//a' data='<a href="image1.html">Name: My image 1 <'>,
 <Selector xpath='//a' data='<a href="image2.html">Name: My image 2 <'>,
 <Selector xpath='//a' data='<a href="image3.html">Name: My image 3 <'>,
 <Selector xpath='//a' data='<a href="image4.html">Name: My image 4 <'>,
 <Selector xpath='//a' data='<a href="image5.html">Name: My image 5 <'>]
>>> type(result)
scrapy.selector.unified.SelectorList複製程式碼

列印結果的形式是Selector組成的列表,其實它是SelectorList型別,SelectorList和Selector都可以繼續呼叫xpath()css()等方法來進一步提取資料。

在上面的例子中,我們提取了a節點。接下來,我們嘗試繼續呼叫xpath()方法來提取a節點內包含的img節點,如下所示:

>>> result.xpath('./img')
[<Selector xpath='./img' data='<img src="image1_thumb.jpg">'>,
 <Selector xpath='./img' data='<img src="image2_thumb.jpg">'>,
 <Selector xpath='./img' data='<img src="image3_thumb.jpg">'>,
 <Selector xpath='./img' data='<img src="image4_thumb.jpg">'>,
 <Selector xpath='./img' data='<img src="image5_thumb.jpg">'>]複製程式碼

我們獲得了a節點裡面的所有img節點,結果為5。

值得注意的是,選擇器的最前方加 .(點),這代表提取元素內部的資料,如果沒有加點,則代表從根節點開始提取。此處我們用了./img的提取方式,則代表從a節點裡進行提取。如果此處我們用//img,則還是從html節點裡進行提取。

我們剛才使用了response.selector.xpath()方法對資料進行了提取。Scrapy提供了兩個實用的快捷方法,response.xpath()response.css(),它們二者的功能完全等同於response.selector.xpath()response.selector.css()。方便起見,後面我們統一直接呼叫responsexpath()css()方法進行選擇。

現在我們得到的是SelectorList型別的變數,該變數是由Selector物件組成的列表。我們可以用索引單獨取出其中某個Selector元素,如下所示:

>>> result[0]
<Selector xpath='//a' data='<a href="image1.html">Name: My image 1 <'>複製程式碼

我們可以像操作列表一樣操作這個SelectorList

但是現在獲取的內容是Selector或者SelectorList型別,並不是真正的文字內容。那麼具體的內容怎麼提取呢?

比如我們現在想提取出a節點元素,就可以利用extract()方法,如下所示:

>>> result.extract()
['<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>', '<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg"></a>', '<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg"></a>', '<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg"></a>', '<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg"></a>']複製程式碼

這裡使用了extract()方法,我們就可以把真實需要的內容獲取下來。

我們還可以改寫XPath表示式,來選取節點的內部文字和屬性,如下所示:

>>> response.xpath('//a/text()').extract()
['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']
>>> response.xpath('//a/@href').extract()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']複製程式碼

我們只需要再加一層/text()就可以獲取節點的內部文字,或者加一層/@href就可以獲取節點的href屬性。其中,@符號後面內容就是要獲取的屬性名稱。

現在我們可以用一個規則把所有符合要求的節點都獲取下來,返回的型別是列表型別。

但是這裡有一個問題:如果符合要求的節點只有一個,那麼返回的結果會是什麼呢?我們再用一個例項來感受一下,如下所示:

>>> response.xpath('//a[@href="image1.html"]/text()').extract()
['Name: My image 1 ']複製程式碼

我們用屬性限制了匹配的範圍,使XPath只可以匹配到一個元素。然後用extract()方法提取結果,其結果還是一個列表形式,其文字是列表的第一個元素。但很多情況下,我們其實想要的資料就是第一個元素內容,這裡我們通過加一個索引來獲取,如下所示:

>>> response.xpath('//a[@href="image1.html"]/text()').extract()[0]
'Name: My image 1 '複製程式碼

但是,這個寫法很明顯是有風險的。一旦XPath有問題,那麼extract()後的結果可能是一個空列表。如果我們再用索引來獲取,那不就會可能導致陣列越界嗎?

所以,另外一個方法可以專門提取單個元素,它叫作extract_first()。我們可以改寫上面的例子如下所示:

>>> response.xpath('//a[@href="image1.html"]/text()').extract_first()
'Name: My image 1 '複製程式碼

這樣,我們直接利用extract_first()方法將匹配的第一個結果提取出來,同時我們也不用擔心陣列越界的問題。

另外我們也可以為extract_first()方法設定一個預設值引數,這樣當XPath規則提取不到內容時會直接使用預設值。例如將XPath改成一個不存在的規則,重新執行程式碼,如下所示:

>>> response.xpath('//a[@href="image1"]/text()').extract_first()
>>> response.xpath('//a[@href="image1"]/text()').extract_first('Default Image')
'Default Image'複製程式碼

這裡,如果XPath匹配不到任何元素,呼叫extract_first()會返回空,也不會報錯。

在第二行程式碼中,我們還傳遞了一個引數當作預設值,如Default Image。這樣如果XPath匹配不到結果的話,返回值會使用這個引數來代替,可以看到輸出正是如此。

現在為止,我們瞭解了Scrapy中的XPath的相關用法,包括巢狀查詢、提取內容、提取單個內容、獲取文字和屬性等。

4. CSS選擇器

接下來,我們看看CSS選擇器的用法。

Scrapy的選擇器同時還對接了CSS選擇器,使用response.css()方法可以使用CSS選擇器來選擇對應的元素。

例如在上文我們選取了所有的a節點,那麼CSS選擇器同樣可以做到,如下所示:

>>> response.css('a')
[<Selector xpath='descendant-or-self::a' data='<a href="image1.html">Name: My image 1 <'>, 
<Selector xpath='descendant-or-self::a' data='<a href="image2.html">Name: My image 2 <'>, 
<Selector xpath='descendant-or-self::a' data='<a href="image3.html">Name: My image 3 <'>, 
<Selector xpath='descendant-or-self::a' data='<a href="image4.html">Name: My image 4 <'>, 
<Selector xpath='descendant-or-self::a' data='<a href="image5.html">Name: My image 5 <'>]複製程式碼

同樣,呼叫extract()方法就可以提取出節點,如下所示:

>>> response.css('a').extract()
['<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>', '<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg"></a>', '<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg"></a>', '<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg"></a>', '<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg"></a>']複製程式碼

用法和XPath選擇是完全一樣的。

另外,我們也可以進行屬性選擇和巢狀選擇,如下所示:

>>> response.css('a[href="image1.html"]').extract()
['<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>']
>>> response.css('a[href="image1.html"] img').extract()
['<img src="image1_thumb.jpg">']複製程式碼

這裡用[href="image.html"]限定了href屬性,可以看到匹配結果就只有一個了。另外如果想查詢a節點內的img節點,只需要再加一個空格和img即可。選擇器的寫法和標準CSS選擇器寫法如出一轍。

我們也可以使用extract_first()方法提取列表的第一個元素,如下所示:

>>> response.css('a[href="image1.html"] img').extract_first()
'<img src="image1_thumb.jpg">'複製程式碼

接下來的兩個用法不太一樣。節點的內部文字和屬性的獲取是這樣實現的,如下所示:

>>> response.css('a[href="image1.html"]::text').extract_first()
'Name: My image 1 '
>>> response.css('a[href="image1.html"] img::attr(src)').extract_first()
'image1_thumb.jpg'複製程式碼

獲取文字和屬性需要用::text::attr()的寫法。而其他庫如Beautiful Soup或pyquery都有單獨的方法。

另外,CSS選擇器和XPath選擇器一樣可以巢狀選擇。我們可以先用XPath選擇器選中所有a節點,再利用CSS選擇器選中img節點,再用XPath選擇器獲取屬性。我們用一個例項來感受一下,如下所示:

>>> response.xpath('//a').css('img').xpath('@src').extract()
['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']複製程式碼

我們成功獲取了所有img節點的src屬性。

因此,我們可以隨意使用xpath()css()方法二者自由組合實現巢狀查詢,二者是完全相容的。

5. 正則匹配

Scrapy的選擇器還支援正則匹配。比如,在示例的a節點中的文字類似於Name: My image 1,現在我們只想把Name:後面的內容提取出來,這時就可以藉助re()方法,實現如下:

>>> response.xpath('//a/text()').re('Name:\s(.*)')
['My image 1 ', 'My image 2 ', 'My image 3 ', 'My image 4 ', 'My image 5 ']複製程式碼

我們給re()方法傳了一個正規表示式,其中(.*)就是要匹配的內容,輸出的結果就是正規表示式匹配的分組,結果會依次輸出。

如果同時存在兩個分組,那麼結果依然會被按序輸出,如下所示:

>>> response.xpath('//a/text()').re('(.*?):\s(.*)')
['Name', 'My image 1 ', 'Name', 'My image 2 ', 'Name', 'My image 3 ', 'Name', 'My image 4 ', 'Name', 'My image 5 ']複製程式碼

類似extract_first()方法,re_first()方法可以選取列表的第一個元素,用法如下:

>>> response.xpath('//a/text()').re_first('(.*?):\s(.*)')
'Name'
>>> response.xpath('//a/text()').re_first('Name:\s(.*)')
'My image 1 '複製程式碼

不論正則匹配了幾個分組,結果都會等於列表的第一個元素。

值得注意的是,response物件不能直接呼叫re()re_first()方法。如果想要對全文進行正則匹配,可以先呼叫xpath()方法再正則匹配,如下所示:

>>> response.re('Name:\s(.*)')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'HtmlResponse' object has no attribute 're'
>>> response.xpath('.').re('Name:\s(.*)<br>')
['My image 1 ', 'My image 2 ', 'My image 3 ', 'My image 4 ', 'My image 5 ']
>>> response.xpath('.').re_first('Name:\s(.*)<br>')
'My image 1 '複製程式碼

通過上面的例子,我們可以看到,直接呼叫re()方法會提示沒有re屬性。但是這裡首先呼叫了xpath('.')選中全文,然後呼叫re()re_first()方法,就可以進行正則匹配了。

6. 結語

以上內容便是Scrapy選擇器的用法,它包括兩個常用選擇器和正則匹配功能。熟練掌握XPath語法、CSS選擇器語法、正規表示式語法可以大大提高資料提取效率。


本資源首發於崔慶才的個人部落格靜覓: Python3網路爬蟲開發實戰教程 | 靜覓

如想了解更多爬蟲資訊,請關注我的個人微信公眾號:進擊的Coder

weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)


相關文章