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… (二維碼自動識別)

相關文章