第43讲:灵活好用的 Spider 的用法
在上一節(jié)課我們通過實(shí)例了解了 Scrapy 的基本使用方法,在這個(gè)過程中,我們用到了 Spider 來編寫爬蟲邏輯,同時(shí)用到了一些選擇器來對(duì)結(jié)果進(jìn)行選擇。
在這一節(jié)課,我們就對(duì) Spider 和 Selector 的基本用法作一個(gè)總結(jié)。
Spider 的用法
在 Scrapy 中,要抓取網(wǎng)站的鏈接配置、抓取邏輯、解析邏輯等其實(shí)都是在 Spider 中配置的。在前一節(jié)課的實(shí)例中,我們發(fā)現(xiàn)抓取邏輯也是在 Spider 中完成的。本節(jié)課我們就來專門了解一下 Spider 的基本用法。
Spider 運(yùn)行流程
在實(shí)現(xiàn) Scrapy 爬蟲項(xiàng)目時(shí),最核心的類便是 Spider 類了,它定義了如何爬取某個(gè)網(wǎng)站的流程和解析方式。簡(jiǎn)單來講,Spider 要做的事就是如下兩件:
- 定義爬取網(wǎng)站的動(dòng)作;
- 分析爬取下來的網(wǎng)頁。
對(duì)于 Spider 類來說,整個(gè)爬取循環(huán)如下所述。
- 以初始的 URL 初始化 Request,并設(shè)置回調(diào)函數(shù)。 當(dāng)該 Request 成功請(qǐng)求并返回時(shí),將生成 Response,并作為參數(shù)傳給該回調(diào)函數(shù)。
- 在回調(diào)函數(shù)內(nèi)分析返回的網(wǎng)頁內(nèi)容。返回結(jié)果可以有兩種形式,一種是解析到的有效結(jié)果返回字典或 Item 對(duì)象。下一步可經(jīng)過處理后(或直接)保存,另一種是解析到的下一個(gè)(如下一頁)鏈接,可以利用此鏈接構(gòu)造 Request 并設(shè)置新的回調(diào)函數(shù),返回 Request。
- 如果返回的是字典或 Item 對(duì)象,可通過 Feed Exports 等形式存入文件,如果設(shè)置了 Pipeline 的話,可以經(jīng)由 Pipeline 處理(如過濾、修正等)并保存。
- 如果返回的是 Reqeust,那么 Request 執(zhí)行成功得到 Response 之后會(huì)再次傳遞給 Request 中定義的回調(diào)函數(shù),可以再次使用選擇器來分析新得到的網(wǎng)頁內(nèi)容,并根據(jù)分析的數(shù)據(jù)生成 Item。
通過以上幾步循環(huán)往復(fù)進(jìn)行,便完成了站點(diǎn)的爬取。
Spider 類分析
在上一節(jié)課的例子中我們定義的 Spider 繼承自 scrapy.spiders.Spider,這個(gè)類是最簡(jiǎn)單最基本的 Spider 類,每個(gè)其他的 Spider 必須繼承自這個(gè)類,還有后面要說明的一些特殊 Spider 類也都是繼承自它。
這個(gè)類里提供了 start_requests 方法的默認(rèn)實(shí)現(xiàn),讀取并請(qǐng)求 start_urls 屬性,并根據(jù)返回的結(jié)果調(diào)用 parse 方法解析結(jié)果。另外它還有一些基礎(chǔ)屬性,下面對(duì)其進(jìn)行講解。
-
name:爬蟲名稱,是定義 Spider 名字的字符串。Spider 的名字定義了 Scrapy 如何定位并初始化 Spider,所以其必須是唯一的。 不過我們可以生成多個(gè)相同的 Spider 實(shí)例,這沒有任何限制。 name 是 Spider 最重要的屬性,而且是必需的。如果該 Spider 爬取單個(gè)網(wǎng)站,一個(gè)常見的做法是以該網(wǎng)站的域名名稱來命名 Spider。例如,如果 Spider 爬取 mywebsite.com,該 Spider 通常會(huì)被命名為 mywebsite。
-
allowed_domains:允許爬取的域名,是可選配置,不在此范圍的鏈接不會(huì)被跟進(jìn)爬取。
-
start_urls:起始 URL 列表,當(dāng)我們沒有實(shí)現(xiàn) start_requests 方法時(shí),默認(rèn)會(huì)從這個(gè)列表開始抓取。
-
custom_settings:這是一個(gè)字典,是專屬于本 Spider 的配置,此設(shè)置會(huì)覆蓋項(xiàng)目全局的設(shè)置,而且此設(shè)置必須在初始化前被更新,所以它必須定義成類變量。
-
crawler:此屬性是由 from_crawler 方法設(shè)置的,代表的是本 Spider 類對(duì)應(yīng)的 Crawler 對(duì)象,Crawler 對(duì)象中包含了很多項(xiàng)目組件,利用它我們可以獲取項(xiàng)目的一些配置信息,如最常見的就是獲取項(xiàng)目的設(shè)置信息,即 Settings。
-
settings:是一個(gè) Settings 對(duì)象,利用它我們可以直接獲取項(xiàng)目的全局設(shè)置變量。
除了一些基礎(chǔ)屬性,Spider 還有一些常用的方法,在此介紹如下。
-
start_requests:此方法用于生成初始請(qǐng)求,它必須返回一個(gè)可迭代對(duì)象,此方法會(huì)默認(rèn)使用 start_urls 里面的 URL 來構(gòu)造 Request,而且 Request 是 GET 請(qǐng)求方式。如果我們想在啟動(dòng)時(shí)以 POST 方式訪問某個(gè)站點(diǎn),可以直接重寫這個(gè)方法,發(fā)送 POST 請(qǐng)求時(shí)我們使用 FormRequest 即可。
-
parse:當(dāng) Response 沒有指定回調(diào)函數(shù)時(shí),該方法會(huì)默認(rèn)被調(diào)用,它負(fù)責(zé)處理 Response,處理返回結(jié)果,并從中提取出想要的數(shù)據(jù)和下一步的請(qǐng)求,然后返回。該方法需要返回一個(gè)包含 Request 或 Item 的可迭代對(duì)象。
-
closed:當(dāng) Spider 關(guān)閉時(shí),該方法會(huì)被調(diào)用,在這里一般會(huì)定義釋放資源的一些操作或其他收尾操作。
Selector 的用法
我們之前介紹了利用 Beautiful Soup、PyQuery,以及正則表達(dá)式來提取網(wǎng)頁數(shù)據(jù),這確實(shí)非常方便。而 Scrapy 還提供了自己的數(shù)據(jù)提取方法,即 Selector(選擇器)。
Selector 是基于 lxml 構(gòu)建的,支持 XPath 選擇器、CSS 選擇器,以及正則表達(dá)式,功能全面,解析速度和準(zhǔn)確度非常高。
接下來我們將介紹 Selector 的用法。
直接使用
Selector 是一個(gè)可以獨(dú)立使用的模塊。我們可以直接利用 Selector 這個(gè)類來構(gòu)建一個(gè)選擇器對(duì)象,然后調(diào)用它的相關(guān)方法如 xpath、css 等來提取數(shù)據(jù)。
例如,針對(duì)一段 HTML 代碼,我們可以用如下方式構(gòu)建 Selector 對(duì)象來提取數(shù)據(jù):
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)運(yùn)行結(jié)果:
Hello World這里我們沒有在 Scrapy 框架中運(yùn)行,而是把 Scrapy 中的 Selector 單獨(dú)拿出來使用了,構(gòu)建的時(shí)候傳入 text 參數(shù),就生成了一個(gè) Selector 選擇器對(duì)象,然后就可以像前面我們所用的 Scrapy 中的解析方式一樣,調(diào)用 xpath、css 等方法來提取了。
在這里我們查找的是源代碼中的 title 中的文本,在 XPath 選擇器最后加 text 方法就可以實(shí)現(xiàn)文本的提取了。
以上內(nèi)容就是 Selector 的直接使用方式。同 Beautiful Soup 等庫類似,Selector 其實(shí)也是強(qiáng)大的網(wǎng)頁解析庫。如果方便的話,我們也可以在其他項(xiàng)目中直接使用 Selector 來提取數(shù)據(jù)。
接下來,我們用實(shí)例來詳細(xì)講解 Selector 的用法。
Scrapy Shell
由于 Selector 主要是與 Scrapy 結(jié)合使用,如 Scrapy 的回調(diào)函數(shù)中的參數(shù) response 直接調(diào)用 xpath() 或者 css() 方法來提取數(shù)據(jù),所以在這里我們借助 Scrapy Shell 來模擬 Scrapy 請(qǐng)求的過程,來講解相關(guān)的提取方法。
我們用官方文檔的一個(gè)樣例頁面來做演示:http://doc.scrapy.org/en/latest/_static/selectors-sample1.html。
開啟 Scrapy Shell,在命令行中輸入如下命令:
scrapy shell http://doc.scrapy.org/en/latest/_static/selectors-sample1.html這樣我們就進(jìn)入了 Scrapy Shell 模式。這個(gè)過程其實(shí)是 Scrapy 發(fā)起了一次請(qǐng)求,請(qǐng)求的 URL 就是剛才命令行下輸入的 URL,然后把一些可操作的變量傳遞給我們,如 request、response 等,如圖所示。
我們可以在命令行模式下輸入命令調(diào)用對(duì)象的一些操作方法,回車之后實(shí)時(shí)顯示結(jié)果。這與 Python 的命令行交互模式是類似的。
接下來,演示的實(shí)例都將頁面的源碼作為分析目標(biāo),頁面源碼如下所示:
<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 選擇器
進(jìn)入 Scrapy Shell 之后,我們將主要操作 response 變量來進(jìn)行解析。因?yàn)槲覀兘馕龅氖?HTML 代碼,Selector 將自動(dòng)使用 HTML 語法來分析。
response 有一個(gè)屬性 selector,我們調(diào)用 response.selector 返回的內(nèi)容就相當(dāng)于用 response 的 text 構(gòu)造了一個(gè) Selector 對(duì)象。通過這個(gè) Selector 對(duì)象我們可以調(diào)用解析方法如 xpath、css 等,通過向方法傳入 XPath 或 CSS 選擇器參數(shù)就可以實(shí)現(xiàn)信息的提取。
我們用一個(gè)實(shí)例感受一下,如下所示:
>>> 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打印結(jié)果的形式是 Selector 組成的列表,其實(shí)它是 SelectorList 類型,SelectorList 和 Selector 都可以繼續(xù)調(diào)用 xpath 和 css 等方法來進(jìn)一步提取數(shù)據(jù)。
在上面的例子中,我們提取了 a 節(jié)點(diǎn)。接下來,我們嘗試?yán)^續(xù)調(diào)用 xpath 方法來提取 a 節(jié)點(diǎn)內(nèi)包含的 img 節(jié)點(diǎn),如下所示:
>>> 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 節(jié)點(diǎn)里面的所有 img 節(jié)點(diǎn),結(jié)果為 5。
值得注意的是,選擇器的最前方加 .(點(diǎn)),這代表提取元素內(nèi)部的數(shù)據(jù),如果沒有加點(diǎn),則代表從根節(jié)點(diǎn)開始提取。此處我們用了 ./img 的提取方式,則代表從 a 節(jié)點(diǎn)里進(jìn)行提取。如果此處我們用 //img,則還是從 html 節(jié)點(diǎn)里進(jìn)行提取。
我們剛才使用了 response.selector.xpath 方法對(duì)數(shù)據(jù)進(jìn)行了提取。Scrapy 提供了兩個(gè)實(shí)用的快捷方法,response.xpath 和 response.css,它們二者的功能完全等同于 response.selector.xpath 和 response.selector.css。方便起見,后面我們統(tǒng)一直接調(diào)用 response 的 xpath 和 css 方法進(jìn)行選擇。
現(xiàn)在我們得到的是 SelectorList 類型的變量,該變量是由 Selector 對(duì)象組成的列表。我們可以用索引單獨(dú)取出其中某個(gè) Selector 元素,如下所示:
>>> result[0] <Selector xpath='//a' data='<a href="image1.html">Name: My image 1 <'>我們可以像操作列表一樣操作這個(gè) SelectorList。但是現(xiàn)在獲取的內(nèi)容是 Selector 或者 SelectorList 類型,并不是真正的文本內(nèi)容。那么具體的內(nèi)容怎么提取呢?
比如我們現(xiàn)在想提取出 a 節(jié)點(diǎn)元素,就可以利用 extract 方法,如下所示:
這里使用了 extract 方法,我們就可以把真實(shí)需要的內(nèi)容獲取下來。
我們還可以改寫 XPath 表達(dá)式,來選取節(jié)點(diǎn)的內(nèi)部文本和屬性,如下所示:
>>> 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() 就可以獲取節(jié)點(diǎn)的內(nèi)部文本,或者加一層 /@href 就可以獲取節(jié)點(diǎn)的 href 屬性。其中,@ 符號(hào)后面內(nèi)容就是要獲取的屬性名稱。
現(xiàn)在我們可以用一個(gè)規(guī)則把所有符合要求的節(jié)點(diǎn)都獲取下來,返回的類型是列表類型。
但是這里有一個(gè)問題:如果符合要求的節(jié)點(diǎn)只有一個(gè),那么返回的結(jié)果會(huì)是什么呢?我們?cè)儆靡粋€(gè)實(shí)例來感受一下,如下所示:
>>> response.xpath('//a[@href="image1.html"]/text()').extract() ['Name: My image 1 ']我們用屬性限制了匹配的范圍,使 XPath 只可以匹配到一個(gè)元素。然后用 extract 方法提取結(jié)果,其結(jié)果還是一個(gè)列表形式,其文本是列表的第一個(gè)元素。但很多情況下,我們其實(shí)想要的數(shù)據(jù)就是第一個(gè)元素內(nèi)容,這里我們通過加一個(gè)索引來獲取,如下所示:
'Name: My image 1 '但是,這個(gè)寫法很明顯是有風(fēng)險(xiǎn)的。一旦 XPath 有問題,那么 extract 后的結(jié)果可能是一個(gè)空列表。如果我們?cè)儆盟饕齺慝@取,那不就可能會(huì)導(dǎo)致數(shù)組越界嗎?
所以,另外一個(gè)方法可以專門提取單個(gè)元素,它叫作 extract_first。我們可以改寫上面的例子如下所示:
這樣,我們直接利用 extract_first 方法將匹配的第一個(gè)結(jié)果提取出來,同時(shí)我們也不用擔(dān)心數(shù)組越界的問題。
另外我們也可以為 extract_first 方法設(shè)置一個(gè)默認(rèn)值參數(shù),這樣當(dāng) XPath 規(guī)則提取不到內(nèi)容時(shí)會(huì)直接使用默認(rèn)值。例如將 XPath 改成一個(gè)不存在的規(guī)則,重新執(zhí)行代碼,如下所示:
>>> response.xpath('//a[@href="image1"]/text()').extract_first()>>> response.xpath('//a[@href="image1"]/text()').extract_first('Default Image') 'Default Image'這里,如果 XPath 匹配不到任何元素,調(diào)用 extract_first 會(huì)返回空,也不會(huì)報(bào)錯(cuò)。在第二行代碼中,我們還傳遞了一個(gè)參數(shù)當(dāng)作默認(rèn)值,如 Default Image。這樣如果 XPath 匹配不到結(jié)果的話,返回值會(huì)使用這個(gè)參數(shù)來代替,可以看到輸出正是如此。
到現(xiàn)在為止,我們了解了 Scrapy 中的 XPath 的相關(guān)用法,包括嵌套查詢、提取內(nèi)容、提取單個(gè)內(nèi)容、獲取文本和屬性等。
CSS 選擇器
接下來,我們看看 CSS 選擇器的用法。Scrapy 的選擇器同時(shí)還對(duì)接了 CSS 選擇器,使用 response.css() 方法可以使用 CSS 選擇器來選擇對(duì)應(yīng)的元素。
例如在上文我們選取了所有的 a 節(jié)點(diǎn),那么 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 <'>]同樣,調(diào)用 extract 方法就可以提取出節(jié)點(diǎn),如下所示:
['<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 選擇是完全一樣的。另外,我們也可以進(jìn)行屬性選擇和嵌套選擇,如下所示:
>>> 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 屬性,可以看到匹配結(jié)果就只有一個(gè)了。另外如果想查找 a 節(jié)點(diǎn)內(nèi)的 img 節(jié)點(diǎn),只需要再加一個(gè)空格和 img 即可。選擇器的寫法和標(biāo)準(zhǔn) CSS 選擇器寫法如出一轍。
我們也可以使用 extract_first() 方法提取列表的第一個(gè)元素,如下所示:
>>> response.css('a[href="image1.html"] img').extract_first() '<img src="image1_thumb.jpg">'接下來的兩個(gè)用法不太一樣。節(jié)點(diǎn)的內(nèi)部文本和屬性的獲取是這樣實(shí)現(xiàn)的,如下所示:
>>> 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 都有單獨(dú)的方法。
另外,CSS 選擇器和 XPath 選擇器一樣可以嵌套選擇。我們可以先用 XPath 選擇器選中所有 a 節(jié)點(diǎn),再利用 CSS 選擇器選中 img 節(jié)點(diǎn),再用 XPath 選擇器獲取屬性。我們用一個(gè)實(shí)例來感受一下,如下所示:
>>> response.xpath('//a').css('img').xpath('@src').extract() ['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']我們成功獲取了所有 img 節(jié)點(diǎn)的 src 屬性。
因此,我們可以隨意使用 xpath 和 css 方法二者自由組合實(shí)現(xiàn)嵌套查詢,二者是完全兼容的。
正則匹配
Scrapy 的選擇器還支持正則匹配。比如,在示例的 a 節(jié)點(diǎn)中的文本類似于 Name: My image 1,現(xiàn)在我們只想把 Name: 后面的內(nèi)容提取出來,這時(shí)就可以借助 re 方法,實(shí)現(xiàn)如下:
>>> response.xpath('//a/text()').re('Name:\s(.*)') ['My image 1 ', 'My image 2 ', 'My image 3 ', 'My image 4 ', 'My image 5 ']我們給 re 方法傳入一個(gè)正則表達(dá)式,其中 (.*) 就是要匹配的內(nèi)容,輸出的結(jié)果就是正則表達(dá)式匹配的分組,結(jié)果會(huì)依次輸出。
如果同時(shí)存在兩個(gè)分組,那么結(jié)果依然會(huì)被按序輸出,如下所示:
>>> 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 方法可以選取列表的第一個(gè)元素,用法如下:
>>> response.xpath('//a/text()').re_first('(.*?):\s(.*)') 'Name' >>> response.xpath('//a/text()').re_first('Name:\s(.*)') 'My image 1 '不論正則匹配了幾個(gè)分組,結(jié)果都會(huì)等于列表的第一個(gè)元素。
值得注意的是,response 對(duì)象不能直接調(diào)用 re 和 re_first 方法。如果想要對(duì)全文進(jìn)行正則匹配,可以先調(diào)用 xpath 方法然后再進(jìn)行正則匹配,如下所示:
>>> 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 '通過上面的例子,我們可以看到,直接調(diào)用 re 方法會(huì)提示沒有 re 屬性。但是這里首先調(diào)用了 xpath(’.’)選中全文,然后調(diào)用 re 和 re_first 方法,就可以進(jìn)行正則匹配了。
以上內(nèi)容便是 Scrapy 選擇器的用法,它包括兩個(gè)常用選擇器和正則匹配功能。如果你熟練掌握 XPath 語法、CSS 選擇器語法、正則表達(dá)式語法可以大大提高數(shù)據(jù)提取效率。
總結(jié)
以上是生活随笔為你收集整理的第43讲:灵活好用的 Spider 的用法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第45讲:哪都能存,Item Pipel
- 下一篇: 第42讲:scrapy框架的基本使用