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,然後把一些可操作的變數傳遞給我們,如request
、response
等,如下圖所示。
我們可以在命令列模式下輸入命令呼叫物件的一些操作方法,回車之後實時顯示結果。這與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
返回的內容就相當於用response
的body
構造了一個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()
。方便起見,後面我們統一直接呼叫response
的xpath()
和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… (二維碼自動識別)