第43讲:灵活好用的 Spider 的用法
在上一節課我們通過實例了解了 Scrapy 的基本使用方法,在這個過程中,我們用到了 Spider 來編寫爬蟲邏輯,同時用到了一些選擇器來對結果進行選擇。
在這一節課,我們就對 Spider 和 Selector 的基本用法作一個總結。
Spider 的用法
在 Scrapy 中,要抓取網站的鏈接配置、抓取邏輯、解析邏輯等其實都是在 Spider 中配置的。在前一節課的實例中,我們發現抓取邏輯也是在 Spider 中完成的。本節課我們就來專門了解一下 Spider 的基本用法。
Spider 運行流程
在實現 Scrapy 爬蟲項目時,最核心的類便是 Spider 類了,它定義了如何爬取某個網站的流程和解析方式。簡單來講,Spider 要做的事就是如下兩件:
- 定義爬取網站的動作;
- 分析爬取下來的網頁。
對于 Spider 類來說,整個爬取循環如下所述。
- 以初始的 URL 初始化 Request,并設置回調函數。 當該 Request 成功請求并返回時,將生成 Response,并作為參數傳給該回調函數。
- 在回調函數內分析返回的網頁內容。返回結果可以有兩種形式,一種是解析到的有效結果返回字典或 Item 對象。下一步可經過處理后(或直接)保存,另一種是解析到的下一個(如下一頁)鏈接,可以利用此鏈接構造 Request 并設置新的回調函數,返回 Request。
- 如果返回的是字典或 Item 對象,可通過 Feed Exports 等形式存入文件,如果設置了 Pipeline 的話,可以經由 Pipeline 處理(如過濾、修正等)并保存。
- 如果返回的是 Reqeust,那么 Request 執行成功得到 Response 之后會再次傳遞給 Request 中定義的回調函數,可以再次使用選擇器來分析新得到的網頁內容,并根據分析的數據生成 Item。
通過以上幾步循環往復進行,便完成了站點的爬取。
Spider 類分析
在上一節課的例子中我們定義的 Spider 繼承自 scrapy.spiders.Spider,這個類是最簡單最基本的 Spider 類,每個其他的 Spider 必須繼承自這個類,還有后面要說明的一些特殊 Spider 類也都是繼承自它。
這個類里提供了 start_requests 方法的默認實現,讀取并請求 start_urls 屬性,并根據返回的結果調用 parse 方法解析結果。另外它還有一些基礎屬性,下面對其進行講解。
-
name:爬蟲名稱,是定義 Spider 名字的字符串。Spider 的名字定義了 Scrapy 如何定位并初始化 Spider,所以其必須是唯一的。 不過我們可以生成多個相同的 Spider 實例,這沒有任何限制。 name 是 Spider 最重要的屬性,而且是必需的。如果該 Spider 爬取單個網站,一個常見的做法是以該網站的域名名稱來命名 Spider。例如,如果 Spider 爬取 mywebsite.com,該 Spider 通常會被命名為 mywebsite。
-
allowed_domains:允許爬取的域名,是可選配置,不在此范圍的鏈接不會被跟進爬取。
-
start_urls:起始 URL 列表,當我們沒有實現 start_requests 方法時,默認會從這個列表開始抓取。
-
custom_settings:這是一個字典,是專屬于本 Spider 的配置,此設置會覆蓋項目全局的設置,而且此設置必須在初始化前被更新,所以它必須定義成類變量。
-
crawler:此屬性是由 from_crawler 方法設置的,代表的是本 Spider 類對應的 Crawler 對象,Crawler 對象中包含了很多項目組件,利用它我們可以獲取項目的一些配置信息,如最常見的就是獲取項目的設置信息,即 Settings。
-
settings:是一個 Settings 對象,利用它我們可以直接獲取項目的全局設置變量。
除了一些基礎屬性,Spider 還有一些常用的方法,在此介紹如下。
-
start_requests:此方法用于生成初始請求,它必須返回一個可迭代對象,此方法會默認使用 start_urls 里面的 URL 來構造 Request,而且 Request 是 GET 請求方式。如果我們想在啟動時以 POST 方式訪問某個站點,可以直接重寫這個方法,發送 POST 請求時我們使用 FormRequest 即可。
-
parse:當 Response 沒有指定回調函數時,該方法會默認被調用,它負責處理 Response,處理返回結果,并從中提取出想要的數據和下一步的請求,然后返回。該方法需要返回一個包含 Request 或 Item 的可迭代對象。
-
closed:當 Spider 關閉時,該方法會被調用,在這里一般會定義釋放資源的一些操作或其他收尾操作。
Selector 的用法
我們之前介紹了利用 Beautiful Soup、PyQuery,以及正則表達式來提取網頁數據,這確實非常方便。而 Scrapy 還提供了自己的數據提取方法,即 Selector(選擇器)。
Selector 是基于 lxml 構建的,支持 XPath 選擇器、CSS 選擇器,以及正則表達式,功能全面,解析速度和準確度非常高。
接下來我們將介紹 Selector 的用法。
直接使用
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 的用法。
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>XPath 選擇器
進入 Scrapy Shell 之后,我們將主要操作 response 變量來進行解析。因為我們解析的是 HTML 代碼,Selector 將自動使用 HTML 語法來分析。
response 有一個屬性 selector,我們調用 response.selector 返回的內容就相當于用 response 的 text 構造了一個 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 方法,如下所示:
這里使用了 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 方法提取結果,其結果還是一個列表形式,其文本是列表的第一個元素。但很多情況下,我們其實想要的數據就是第一個元素內容,這里我們通過加一個索引來獲取,如下所示:
'Name: My image 1 '但是,這個寫法很明顯是有風險的。一旦 XPath 有問題,那么 extract 后的結果可能是一個空列表。如果我們再用索引來獲取,那不就可能會導致數組越界嗎?
所以,另外一個方法可以專門提取單個元素,它叫作 extract_first。我們可以改寫上面的例子如下所示:
這樣,我們直接利用 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 的相關用法,包括嵌套查詢、提取內容、提取單個內容、獲取文本和屬性等。
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 方法就可以提取出節點,如下所示:
['<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 方法二者自由組合實現嵌套查詢,二者是完全兼容的。
正則匹配
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 方法,就可以進行正則匹配了。
以上內容便是 Scrapy 選擇器的用法,它包括兩個常用選擇器和正則匹配功能。如果你熟練掌握 XPath 語法、CSS 選擇器語法、正則表達式語法可以大大提高數據提取效率。
總結
以上是生活随笔為你收集整理的第43讲:灵活好用的 Spider 的用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第45讲:哪都能存,Item Pipel
- 下一篇: 第42讲:scrapy框架的基本使用