《Learning Scrapy》(中文版)第3章 爬蟲基礎(chǔ)


序言
第1章 Scrapy介紹
第2章 理解HTML和XPath
第3章 爬蟲基礎(chǔ)
第4章 從Scrapy到移動(dòng)應(yīng)用
第5章 快速構(gòu)建爬蟲
第6章 Scrapinghub部署
第7章 配置和管理
第8章 Scrapy編程
第9章 使用Pipeline
第10章 理解Scrapy的性能
第11章(完) Scrapyd分布式抓取和實(shí)時(shí)分析


本章非常重要,你可能需要讀幾遍,或是從中查找解決問題的方法。我們會(huì)從如何安裝Scrapy講起,然后在案例中講解如何編寫爬蟲。開始之前,說幾個(gè)注意事項(xiàng)。

因?yàn)槲覀凂R上要進(jìn)入有趣的編程部分,使用本書中的代碼段會(huì)十分重要。當(dāng)你看到:

$ echo hello world
hello world

是要讓你在終端中輸入echo hello world(忽略$),第二行是看到結(jié)果。

當(dāng)你看到:

>>> print 'hi'
hi

是讓你在Python或Scrapy界面進(jìn)行輸入(忽略>>>)。同樣的,第二行是輸出結(jié)果。

你還需要對(duì)文件進(jìn)行編輯。編輯工具取決于你的電腦環(huán)境。如果你使用Vagrant(強(qiáng)烈推薦),你可以是用Notepad、Notepad++、Sublime Text、TextMate,Eclipse、或PyCharm等文本編輯器。如果你更熟悉Linux/Unix,你可以用控制臺(tái)自帶的vim或emacs。這兩個(gè)編輯器功能強(qiáng)大,但是有一定的學(xué)習(xí)曲線。如果你是初學(xué)者,可以選擇適合初學(xué)者的nano編輯器。

安裝Scrapy

Scrapy的安裝相對(duì)簡(jiǎn)單,但這還取決于讀者的電腦環(huán)境。為了支持更多的人,本書安裝和使用Scrapy的方法是用Vagrant,它可以讓你在Linux盒中使用所有的工具,而無關(guān)于操作系統(tǒng)。下面提供了Vagrant和一些常見操作系統(tǒng)的指導(dǎo)。

MacOS

為了輕松跟隨本書學(xué)習(xí),請(qǐng)參照后面的Vagrant說明。如果你想在MacOS中安裝Scrapy,只需控制臺(tái)中輸入:

$ easy_install scrapy

然后,所有事就可以交給電腦了。安裝過程中,可能會(huì)向你詢問密碼或是否安裝Xcode,只需同意即可。

Windows
在Windows中安裝Scrapy要麻煩些。另外,在Windows安裝本書中所有的軟件也很麻煩。我們都為你想到了可能的問題。有Virtualbox的Vagrant可以在所有64位電腦上順利運(yùn)行。翻閱相關(guān)章節(jié),只需幾分鐘就可以安裝好。如果真要在Windows中安裝,請(qǐng)參考本書網(wǎng)站http://scrapybook.com/上面的資料。

Linux
你可能會(huì)在多種Linux服務(wù)器上安裝Scrapy,步驟如下:

提示:確切的安裝依賴變化很快。寫作本書時(shí),Scrapy的版本是1.0.3(翻譯此書時(shí)是1.4)。下面只是對(duì)不同服務(wù)器的建議方法。

Ubuntu或Debian Linux
為了在Ubuntu(測(cè)試機(jī)是Ubuntu 14.04 Trusty Tahr - 64 bit)或是其它使用apt的服務(wù)器上安裝Scrapy,可以使用下面三條命令:

$ sudo apt-get update
$ sudo apt-get install python-pip python-lxml python-crypto python-
cssselect python-openssl python-w3lib python-twisted python-dev libxml2-
dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
$ sudo pip install scrapy

這個(gè)方法需要進(jìn)行編譯,可能隨時(shí)中斷,但可以安裝PyPI上最新版本的Scrapy。如果想避開編譯,安裝不是最新版本的話,可以搜索“install Scrapy Ubuntu packages”,按照官方文檔安裝。

Red Hat或CentOS Linux
在使用yum的Linux上安裝Scrapy也很簡(jiǎn)單(測(cè)試機(jī)是Ubuntu 14.04 Trusty Tahr - 64 bit)。只需三條命令:

sudo yum update
sudo yum -y install libxslt-devel pyOpenSSL python-lxml python-devel gcc
sudo easy_install scrapy

從GitHub安裝
按照前面的指導(dǎo),就可以安裝好Scrapy的依賴了。Scrapy是純Python寫成的,如果你想編輯源代碼或是測(cè)試最新版,可以從https://github.com/scrapy/scrapy克隆最新版,只需命令行輸入:

$ git clonehttps://github.com/scrapy/scrapy.git
$ cd scrapy
$ python setup.py install

我猜如果你是這類用戶,就不需要我提醒安裝virtualenv了。

升級(jí)Scrapy
Scrapy升級(jí)相當(dāng)頻繁。如果你需要升級(jí)Scrapy,可以使用pip、easy_install或aptitude:

$ sudo pip install --upgrade Scrapy

$ sudo easy_install --upgrade scrapy

如果你想降級(jí)或安裝指定版本的Scrapy,可以:

$ sudo pip install Scrapy==1.0.0

$ sudo easy_install scrapy==1.0.0

Vagrant:本書案例的運(yùn)行方法
本書有的例子比較復(fù)雜,有的例子使用了許多東西。無論你是什么水平,都可以嘗試運(yùn)行所有例子。只需一句命令,就可以用Vagrant搭建操作環(huán)境。

本書使用的系統(tǒng)

在Vagrant中,你的電腦被稱作“主機(jī)”。Vagrant在主機(jī)中創(chuàng)建一個(gè)虛擬機(jī)。這樣就可以讓我們忽略主機(jī)的軟硬件,來運(yùn)行案例了。

本書大多數(shù)章節(jié)使用了兩個(gè)服務(wù)——開發(fā)機(jī)和網(wǎng)絡(luò)機(jī)。我們?cè)陂_發(fā)機(jī)中登錄運(yùn)行Scrapy,在網(wǎng)絡(luò)機(jī)中進(jìn)行抓取。后面的章節(jié)會(huì)使用更多的服務(wù),包括數(shù)據(jù)庫和大數(shù)據(jù)處理引擎。

根據(jù)附錄A安裝必備,安裝Vagrant,直到安裝好git和Vagrant。打開命令行,輸入以下命令獲取本書的代碼:

$ git clone https://github.com/scalingexcellence/scrapybook.git
$ cd scrapybook

打開Vagrant:

$ vagrant up --no-parallel

第一次打開Vagrant會(huì)需要些時(shí)間,這取決于你的網(wǎng)絡(luò)。第二次打開就會(huì)比較快。打開之后,登錄你的虛擬機(jī),通過:

$ vagrant ssh

代碼已經(jīng)從主機(jī)中復(fù)制到了開發(fā)機(jī),現(xiàn)在可以在book的目錄中看到:

$ cd book
$ ls
$ ch03 ch04 ch05 ch07 ch08 ch09 ch10 ch11 ...

可以打開幾個(gè)窗口輸入vagrant ssh,這樣就可以打開幾個(gè)終端。輸入vagrant halt可以關(guān)閉系統(tǒng),vagrantstatus可以檢查狀態(tài)。vagrant halt不能關(guān)閉虛擬機(jī)。如果在VirtualBox中碰到問題,可以手動(dòng)關(guān)閉,或是使用vagrant global-status查找id,用vagrant halt <ID> 暫停。大多數(shù)例子可以離線運(yùn)行,這是Vagrant的一大優(yōu)點(diǎn)。

安裝好環(huán)境之后,就可以開始學(xué)習(xí)Scrapy了。

UR2IM——基礎(chǔ)抓取過程
每個(gè)網(wǎng)站都是不同的,對(duì)每個(gè)網(wǎng)站進(jìn)行額外的研究不可避免,碰到特別生僻的問題,也許還要用Scrapy的郵件列表咨詢。尋求解答,去哪里找、怎么找,前提是要熟悉整個(gè)過程和相關(guān)術(shù)語。Scrapy的基本過程,可以寫成字母縮略語UR2IM,見下圖。

The URL
一切都從URL開始。你需要目標(biāo)網(wǎng)站的URL。我的例子是https://www.gumtree.com/,Gumtree分類網(wǎng)站。

例如,訪問倫敦房地產(chǎn)首頁http://www.gumtree.com/flats-houses/london,你就可以找到許多房子的URL。右鍵復(fù)制鏈接地址,就可以復(fù)制URL。其中一個(gè)URL可能是這樣的:https://www.gumtree.com/p/studios-bedsits-rent/split-level。但是,Gumtree的網(wǎng)站變動(dòng)之后,URL的XPath表達(dá)式會(huì)失效。不添加用戶頭的話,Gumtree也不會(huì)響應(yīng)。這個(gè)留給以后再說,現(xiàn)在如果你想加載一個(gè)網(wǎng)頁,你可以使用Scrapy終端,如下所示:

scrapy shell -s USER_AGENT="Mozilla/5.0" <your url here  e.g. http://www.gumtree.com/p/studios-bedsits-rent/...>

要進(jìn)行調(diào)試,可以在Scrapy語句后面添加 –pdb,例如:

scrapy shell --pdb https://gumtree.com

我們不想讓大家如此頻繁的點(diǎn)擊Gumtree網(wǎng)站,并且Gumtree網(wǎng)站上URL失效很快,不適合做例子。我們還希望大家能在離線的情況下,多多練習(xí)書中的例子。這就是為什么Vagrant開發(fā)環(huán)境內(nèi)嵌了一個(gè)網(wǎng)絡(luò)服務(wù)器,可以生成和Gumtree類似的網(wǎng)頁。這些網(wǎng)頁可能并不好看,但是從爬蟲開發(fā)者的角度,是完全合格的。如果想在Vagrant上訪問Gumtree,可以在Vagrant開發(fā)機(jī)上訪問http://web:9312/,或是在瀏覽器中訪問http://localhost:9312/

讓我們?cè)谶@個(gè)網(wǎng)頁上嘗試一下Scrapy,在Vagrant開發(fā)機(jī)上輸入:

$ scrapy shell http://web:9312/properties/property_000000.html
...
[s] Available Scrapy objects:
[s]   crawler    <scrapy.crawler.Crawler object at 0x2d4fb10>
[s]   item       {}
[s]   request    <GET http:// web:9312/.../property_000000.html>
[s]   response   <200 http://web:9312/.../property_000000.html>
[s]   settings   <scrapy.settings.Settings object at 0x2d4fa90>
[s]   spider     <DefaultSpider 'default' at 0x3ea0bd0>
[s] Useful shortcuts:
[s]   shelp()           Shell help (print this help)
[s]   fetch(req_or_url) Fetch request (or URL) and update local...
[s]   view(response)    View response in a browser
>>>

得到一些輸出,加載頁面之后,就進(jìn)入了Python(可以使用Ctrl+D退出)。

請(qǐng)求和響應(yīng)
在前面的輸出日志中,Scrapy自動(dòng)為我們做了一些工作。我們輸入了一條地址,Scrapy做了一個(gè)GET請(qǐng)求,并得到一個(gè)成功響應(yīng)值200。這說明網(wǎng)頁信息已經(jīng)成功加載,并可以使用了。如果要打印reponse.body的前50個(gè)字母,我們可以得到:

>>> response.body[:50]
'<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8"'

這就是這個(gè)Gumtree網(wǎng)頁的HTML文檔。有時(shí)請(qǐng)求和響應(yīng)會(huì)很復(fù)雜,第5章會(huì)對(duì)其進(jìn)行講解,現(xiàn)在只講最簡(jiǎn)單的情況。

抓取對(duì)象
下一步是從響應(yīng)文件中提取信息,輸入到Item。因?yàn)檫@是個(gè)HTML文檔,我們用XPath來做。首先來看一下這個(gè)網(wǎng)頁:

頁面上的信息很多,但大多是關(guān)于版面的:logo、搜索框、按鈕等等。從抓取的角度,它們不重要。我們關(guān)注的是,例如,列表的標(biāo)題、地址、電話。它們都對(duì)應(yīng)著HTML里的元素,我們要在HTML中定位,用上一章所學(xué)的提取出來。先從標(biāo)題開始。

在標(biāo)題上右鍵點(diǎn)擊,選擇檢查元素。在自動(dòng)定位的HTML上再次右鍵點(diǎn)擊,選擇復(fù)制XPath。Chrome給的XPath總是很復(fù)雜,并且容易失效。我們要對(duì)其進(jìn)行簡(jiǎn)化。我們只取最后面的h1。這是因?yàn)閺腟EO的角度,每頁HTML只有一個(gè)h1最好,事實(shí)上大多是網(wǎng)頁只有一個(gè)h1,所以不用擔(dān)心重復(fù)。

提示:SEO是搜索引擎優(yōu)化的意思:通過對(duì)網(wǎng)頁代碼、內(nèi)容、鏈接的優(yōu)化,提升對(duì)搜索引擎的支持。

讓我們看看h1標(biāo)簽行不行:

>>> response.xpath('//h1/text()').extract()
[u'set unique family well']

很好,完全行得通。我在h1后面加上了text(),表示只提取h1標(biāo)簽里的文字。沒有添加text()的話,就會(huì)這樣:

>>> response.xpath('//h1').extract()
[u'<h1 itemprop="name" class="space-mbs">set unique family well</h1>']

我們已經(jīng)成功得到了title,但是再仔細(xì)看看,還能發(fā)現(xiàn)更簡(jiǎn)便的方法。

Gumtree為標(biāo)簽添加了屬性,就是itemprop=name。所以XPath可以簡(jiǎn)化為//*[@itemprop="name"][1]/text()。在XPath中,切記數(shù)組是從1開始的,所以這里[]里面是1。

選擇itemprop="name"這個(gè)屬性,是因?yàn)镚umtree用這個(gè)屬性命名了許多其他的內(nèi)容,比如“You may also like”,用數(shù)組序號(hào)提取會(huì)很方便。

接下來看價(jià)格。價(jià)格在HTML中的位置如下:

<strong class="ad-price txt-xlarge txt-emphasis" itemprop="price">£334.39pw</strong>

我們又看到了itemprop="name"這個(gè)屬性,XPath表達(dá)式為//*[@itemprop="price"][1]/text()。驗(yàn)證一下:

>>> response.xpath('//*[@itemprop="price"][1]/text()').extract()
[u'\xa3334.39pw']

注意Unicode字符(£符號(hào))和價(jià)格350.00pw。這說明要對(duì)數(shù)據(jù)進(jìn)行清理。在這個(gè)例子中,我們用正則表達(dá)式提取數(shù)字和小數(shù)點(diǎn)。使用正則方法如下:

>>> response.xpath('//*[@itemprop="price"][1]/text()').re('[.0-9]+')
[u'334.39']

提取房屋描述的文字、房屋的地址也很類似,如下:

//*[@itemprop="description"][1]/text()
//*[@itemtype="http://schema.org/Place"][1]/text()

相似的,抓取圖片可以用//img[@itemprop="image"][1]/@src。注意這里沒使用text(),因?yàn)槲覀冎幌胍獔D片的URL。

假如這就是我們要提取的所有信息,整理如下:

目標(biāo) XPath表達(dá)式
title //*[@itemprop="name"][1]/text()
Example value: [u'set unique family well']
Price //*[@itemprop="price"][1]/text()
Example value (using re()):[u'334.39']
description //*[@itemprop="description"][1]/text()
Example value: [u'website court warehouse\r\npool...']
Address //*[@itemtype="http://schema.org/Place"][1]/text()
Example value: [u'Angel, London']
Image_URL //*[@itemprop="image"][1]/@src
Example value: [u'../images/i01.jpg']

這張表很重要,因?yàn)橐苍S只要稍加改變表達(dá)式,就可以抓取其他頁面。另外,如果要爬取數(shù)十個(gè)網(wǎng)站時(shí),使用這樣的表可以進(jìn)行區(qū)分。

目前為止,使用的還只是HTML和XPath,接下來用Python來做一個(gè)項(xiàng)目。

一個(gè)Scrapy項(xiàng)目
目前為止,我們只是在Scrapy shell中進(jìn)行操作。學(xué)過前面的知識(shí),現(xiàn)在開始一個(gè)Scrapy項(xiàng)目,Ctrl+D退出Scrapy shell。Scrapy shell只是操作網(wǎng)頁、XPath表達(dá)式和Scrapy對(duì)象的工具,不要在上面浪費(fèi)太多,因?yàn)橹灰煌顺觯瑢戇^的代碼就會(huì)消失。我們創(chuàng)建一個(gè)名字是properties的項(xiàng)目:

$ scrapy startproject properties
$ cd properties
$ tree
.
├── properties
│   ├── __init__.py
│   ├── items.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       └── __init__.py
└── scrapy.cfg
2 directories, 6 files

先看看這個(gè)Scrapy項(xiàng)目的文件目錄。文件夾內(nèi)包含一個(gè)同名的文件夾,里面有三個(gè)文件items.py, pipelines.py, 和settings.py。還有一個(gè)子文件夾spiders,里面現(xiàn)在是空的。后面的章節(jié)會(huì)詳談settings、pipelines和scrapy.cfg文件。

定義items
用編輯器打開items.py。里面已經(jīng)有代碼,我們要對(duì)其修改下。用之前的表里的內(nèi)容重新定義class PropertiesItem。

還要添加些后面會(huì)用到的內(nèi)容。后面會(huì)深入講解。這里要注意的是,聲明一個(gè)字段,并不要求一定要填充。所以放心添加你認(rèn)為需要的字段,后面還可以修改。

字段 Python表達(dá)式
images pipeline根據(jù)image_URL會(huì)自動(dòng)填充這里。后面詳解。
Location 地理編碼會(huì)填充這里。后面詳解。

我們還會(huì)加入一些雜務(wù)字段,也許和現(xiàn)在的項(xiàng)目關(guān)系不大,但是我個(gè)人很感興趣,以后或許能用到。你可以選擇添加或不添加。觀察一下這些項(xiàng)目,你就會(huì)明白,這些項(xiàng)目是怎么幫助我找到何地(server,url),何時(shí)(date),還有(爬蟲)如何進(jìn)行抓取的。它們可以幫助我取消項(xiàng)目,制定新的重復(fù)抓取,或忽略爬蟲的錯(cuò)誤。這里看不明白不要緊,后面會(huì)細(xì)講。

雜務(wù)字段 Python表達(dá)式
url response.url
Example value: ‘http://web.../property_000000.html'
project self.settings.get('BOT_NAME')
Example value: 'properties'
spider self.name
Example value: 'basic'
server server socket.gethostname()
Example value: 'scrapyserver1'
date datetime.datetime.now()
Example value: datetime.datetime(2015, 6, 25...)

利用這個(gè)表修改PropertiesItem這個(gè)類。修改文件properties/items.py如下:

from scrapy.item import Item, Field

class PropertiesItem(Item):
    # Primary fields
    title = Field()
    price = Field()
    description = Field()
    address = Field()
    image_URL = Field()
    
# Calculated fields
    images = Field()
    location = Field()
   
 # Housekeeping fields
    url = Field()
    project = Field()
    spider = Field()
    server = Field()
    date = Field()

這是我們的第一段代碼,要注意Python中是使用空格縮進(jìn)的。每個(gè)字段名之前都有四個(gè)空格或是一個(gè)tab。如果一行有四個(gè)空格,另一行有三個(gè)空格,就會(huì)報(bào)語法錯(cuò)誤。如果一行是四個(gè)空格,另一行是一個(gè)tab,也會(huì)報(bào)錯(cuò)。空格符指定了這些項(xiàng)目是在PropertiesItem下面的。其他語言有的用花括號(hào){},有的用begin – end,Python則使用空格。

編寫爬蟲
已經(jīng)完成了一半。現(xiàn)在來寫爬蟲。一般的,每個(gè)網(wǎng)站,或一個(gè)大網(wǎng)站的一部分,只有一個(gè)爬蟲。爬蟲代碼來成UR2IM流程。

當(dāng)然,你可以用文本編輯器一句一句寫爬蟲,但更便捷的方法是用scrapy genspider命令,如下所示:

$ scrapy genspider basic web

使用模塊中的模板“basic”創(chuàng)建了一個(gè)爬蟲“basic”:

  properties.spiders.basic

一個(gè)爬蟲文件basic.py就出現(xiàn)在目錄properties/spiders中。剛才的命令是,生成一個(gè)名字是basic的默認(rèn)文件,它的限制是在web上爬取URL。我們可以取消這個(gè)限制。這個(gè)爬蟲使用的是basic這個(gè)模板。你可以用scrapy genspider –l查看所有的模板,然后用參數(shù)–t利用模板生成想要的爬蟲,后面會(huì)介紹一個(gè)例子。

查看properties/spiders/basic.py file文件, 它的代碼如下:

import scrapy
class BasicSpider(scrapy.Spider):
    name = "basic"
    allowed_domains = ["web"]
start_URL = (
        'http://www.web/',
    )
    def parse(self, response):
        pass

import命令可以讓我們使用Scrapy框架。然后定義了一個(gè)類BasicSpider,繼承自scrapy.Spider。繼承的意思是,雖然我們沒寫任何代碼,這個(gè)類已經(jīng)繼承了Scrapy框架中的類Spider的許多特性。這允許我們只需寫幾行代碼,就可以有一個(gè)功能完整的爬蟲。然后我們看到了一些爬蟲的參數(shù),比如名字和抓取域字段名。最后,我們定義了一個(gè)空函數(shù)parse(),它有兩個(gè)參數(shù)self和response。通過self,可以使用爬蟲一些有趣的功能。response看起來很熟悉,它就是我們?cè)赟crapy shell中見到的響應(yīng)。

下面來開始編輯這個(gè)爬蟲。start_URL更改為在Scrapy命令行中使用過的URL。然后用爬蟲事先準(zhǔn)備的log()方法輸出內(nèi)容。修改后的properties/spiders/basic.py文件為:

import scrapy
class BasicSpider(scrapy.Spider):
    name = "basic"
    allowed_domains = ["web"]
    start_URL = (
        'http://web:9312/properties/property_000000.html',
    )
    def parse(self, response):
        self.log("title: %s" % response.xpath(
            '//*[@itemprop="name"][1]/text()').extract())
        self.log("price: %s" % response.xpath(
            '//*[@itemprop="price"][1]/text()').re('[.0-9]+'))
        self.log("description: %s" % response.xpath(
        '//*[@itemprop="description"][1]/text()').extract())
        self.log("address: %s" % response.xpath(
            '//*[@itemtype="http://schema.org/'
            'Place"][1]/text()').extract())
        self.log("image_URL: %s" % response.xpath(
            '//*[@itemprop="image"][1]/@src').extract())

總算到了運(yùn)行爬蟲的時(shí)間!讓爬蟲運(yùn)行的命令是scrapy crawl接上爬蟲的名字:

$ scrapy crawl basic
INFO: Scrapy 1.0.3 started (bot: properties)
...
INFO: Spider opened
DEBUG: Crawled (200) <GET http://...000.html>
DEBUG: title: [u'set unique family well']
DEBUG: price: [u'334.39']
DEBUG: description: [u'website...']
DEBUG: address: [u'Angel, London']
DEBUG: image_URL: [u'../images/i01.jpg']
INFO: Closing spider (finished)
...

成功了!不要被這么多行的命令嚇到,后面我們?cè)僮屑?xì)說明。現(xiàn)在,我們可以看到使用這個(gè)簡(jiǎn)單的爬蟲,所有的數(shù)據(jù)都用XPath得到了。

來看另一個(gè)命令,scrapy parse。它可以讓我們選擇最合適的爬蟲來解析URL。用—spider命令可以設(shè)定爬蟲:

$ scrapy parse --spider=basic http://web:9312/properties/property_000001.html

你可以看到輸出的結(jié)果和前面的很像,但卻是關(guān)于另一個(gè)房產(chǎn)的。

填充一個(gè)項(xiàng)目
接下來稍稍修改一下前面的代碼。你會(huì)看到,盡管改動(dòng)很小,卻可以解鎖許多新的功能。

首先,引入類PropertiesItem。它位于properties目錄中的item.py文件,因此在模塊properties.items中。它的導(dǎo)入命令是:

from properties.items import PropertiesItem

然后我們要實(shí)例化,并進(jìn)行返回。這很簡(jiǎn)單。在parse()方法中,我們加入聲明item = PropertiesItem(),它產(chǎn)生了一個(gè)新項(xiàng)目,然后為它分配表達(dá)式:

item['title'] = response.xpath('//*[@itemprop="name"][1]/text()').extract()

最后,我們用return item返回項(xiàng)目。更新后的properties/spiders/basic.py文件如下:

import scrapy
from properties.items import PropertiesItem
class BasicSpider(scrapy.Spider):
    name = "basic"
    allowed_domains = ["web"]
    start_URL = (
        'http://web:9312/properties/property_000000.html',
    )
    def parse(self, response):
        item = PropertiesItem()
        item['title'] = response.xpath(
            '//*[@itemprop="name"][1]/text()').extract()
        item['price'] = response.xpath(
            '//*[@itemprop="price"][1]/text()').re('[.0-9]+')
        item['description'] = response.xpath(
            '//*[@itemprop="description"][1]/text()').extract()
        item['address'] = response.xpath(
            '//*[@itemtype="http://schema.org/'
            'Place"][1]/text()').extract()
        item['image_URL'] = response.xpath(
            '//*[@itemprop="image"][1]/@src').extract()
        return item

現(xiàn)在如果再次運(yùn)行爬蟲,你會(huì)注意到一個(gè)不大但很重要的改動(dòng)。被抓取的值不再打印出來,沒有“DEBUG:被抓取的值”了。你會(huì)看到:

DEBUG: Scraped from <200  
http://...000.html>
  {'address': [u'Angel, London'],
   'description': [u'website ... offered'],
   'image_URL': [u'../images/i01.jpg'],
   'price': [u'334.39'],
   'title': [u'set unique family well']}

這是從這個(gè)頁面抓取的PropertiesItem。這很好,因?yàn)镾crapy就是圍繞Items的概念構(gòu)建的,這意味著我們可以用pipelines填充豐富項(xiàng)目,或是用“Feed export”導(dǎo)出保存到不同的格式和位置。

保存到文件
試運(yùn)行下面:

$ scrapy crawl basic -o items.json
$ cat items.json
[{"price": ["334.39"], "address": ["Angel, London"], "description": 
["website court ... offered"], "image_URL": ["../images/i01.jpg"], 
"title": ["set unique family well"]}]
$ scrapy crawl basic -o items.jl
$ cat items.jl
{"price": ["334.39"], "address": ["Angel, London"], "description": 
["website court ... offered"], "image_URL": ["../images/i01.jpg"], 
"title": ["set unique family well"]}
$ scrapy crawl basic -o items.csv
$ cat items.csv 
description,title,url,price,spider,image_URL...
"...offered",set unique family well,,334.39,,../images/i01.jpg
$ scrapy crawl basic -o items.xml
$ cat items.xml 
<?xml version="1.0" encoding="utf-8"?>
<items><item><price><value>334.39</value></price>...</item></items>

不用我們寫任何代碼,我們就可以用這些格式進(jìn)行存儲(chǔ)。Scrapy可以自動(dòng)識(shí)別輸出文件的后綴名,并進(jìn)行輸出。這段代碼中涵蓋了一些常用的格式。CSV和XML文件很流行,因?yàn)榭梢员籈xcel直接打開。JSON文件很流行是因?yàn)樗拈_放性和與JavaScript的密切關(guān)系。JSON和JSON Line格式的區(qū)別是.json文件是在一個(gè)大數(shù)組中存儲(chǔ)JSON對(duì)象。這意味著如果你有一個(gè)1GB的文件,你可能必須現(xiàn)在內(nèi)存中存儲(chǔ),然后才能傳給解析器。相對(duì)的,.jl文件每行都有一個(gè)JSON對(duì)象,所以讀取效率更高。

不在文件系統(tǒng)中存儲(chǔ)生成的文件也很麻煩。利用下面例子的代碼,你可以讓Scrapy自動(dòng)上傳文件到FTP或亞馬遜的S3 bucket。

$ scrapy crawl basic -o "ftp://user:pass@ftp.scrapybook.com/items.json "
$ scrapy crawl basic -o "s3://aws_key:aws_secret@scrapybook/items.json"

注意,證書和URL必須按照主機(jī)和S3更新,才能順利運(yùn)行。

另一個(gè)要注意的是,如果你現(xiàn)在使用scrapy parse,它會(huì)向你顯示被抓取的項(xiàng)目和抓取中新的請(qǐng)求:

$ scrapy parse --spider=basic http://web:9312/properties/property_000001.html
INFO: Scrapy 1.0.3 started (bot: properties)
...
INFO: Spider closed (finished)
>>> STATUS DEPTH LEVEL 1 <<<
# Scraped Items  ------------------------------------------------
[{'address': [u'Plaistow, London'],
  'description': [u'features'],
  'image_URL': [u'../images/i02.jpg'],
  'price': [u'388.03'],
  'title': [u'belsize marylebone...deal']}]
# Requests  ------------------------------------------------
[]

當(dāng)出現(xiàn)意外結(jié)果時(shí),scrapy parse可以幫你進(jìn)行debug,你會(huì)更感嘆它的強(qiáng)大。

清洗——項(xiàng)目加載器和雜務(wù)字段
恭喜你,你已經(jīng)創(chuàng)建成功一個(gè)簡(jiǎn)單爬蟲了!讓我們讓它看起來更專業(yè)些。

我們使用一個(gè)功能類,ItemLoader,以取代看起來雜亂的extract()和xpath()。我們的parse()進(jìn)行如下變化:

def parse(self, response):
    l = ItemLoader(item=PropertiesItem(), response=response)
    l.add_xpath('title', '//*[@itemprop="name"][1]/text()')
    l.add_xpath('price', './/*[@itemprop="price"]'
           '[1]/text()', re='[,.0-9]+')
    l.add_xpath('description', '//*[@itemprop="description"]'
           '[1]/text()')
    l.add_xpath('address', '//*[@itemtype='
           '"http://schema.org/Place"][1]/text()')
    l.add_xpath('image_URL', '//*[@itemprop="image"][1]/@src')
    return l.load_item()

是不是看起來好多了?事實(shí)上,它可不是看起來漂亮那么簡(jiǎn)單。它指出了我們現(xiàn)在要干什么,并且后面的加載項(xiàng)很清晰。這提高了代碼的可維護(hù)性和自文檔化。(自文檔化,self-documenting,是說代碼的可讀性高,可以像文檔文件一樣閱讀)

ItemLoaders提供了許多有趣的方式整合數(shù)據(jù)、格式化數(shù)據(jù)、清理數(shù)據(jù)。它的更新很快,查閱文檔可以更好的使用它,http://doc.scrapy.org/en/latest/topics/loaders。通過不同的類處理器,ItemLoaders從XPath/CSS表達(dá)式傳參。處理器函數(shù)快速小巧。舉一個(gè)Join()的例子。//p表達(dá)式會(huì)選取所有段落,這個(gè)處理函數(shù)可以在一個(gè)入口中將所有內(nèi)容整合起來。另一個(gè)函數(shù)MapCompose(),可以與Python函數(shù)或Python函數(shù)鏈結(jié)合,實(shí)現(xiàn)復(fù)雜的功能。例如,MapCompose(float)可以將字符串轉(zhuǎn)化為數(shù)字,MapCompose(unicode.strip, unicode.title)可以去除多余的空格,并將單詞首字母大寫。讓我們看幾個(gè)處理函數(shù)的例子:

處理函數(shù) 功能
Join() 合并多個(gè)結(jié)果。
MapCompose(unicode.strip) 除去空格。
MapCompose(unicode.strip, unicode.title) 除去空格,單詞首字母大寫。
MapCompose(float) 將字符串轉(zhuǎn)化為數(shù)字。
MapCompose(lambda i: i.replace(',', ''), float) 將字符串轉(zhuǎn)化為數(shù)字,逗號(hào)替換為空格。
MapCompose(lambda i: urlparse.urljoin(response.url, i)) 使用response.url為開頭,將相對(duì)URL轉(zhuǎn)化為絕對(duì)URL。

你可以使用Python編寫處理函數(shù),或是將它們串聯(lián)起來。unicode.strip()和unicode.title()分別用單一參數(shù)實(shí)現(xiàn)了單一功能。其它函數(shù),如replace()和urljoin()需要多個(gè)參數(shù),我們可以使用Lambda函數(shù)。這是一個(gè)匿名函數(shù),可以不聲明函數(shù)就調(diào)用參數(shù):

myFunction = lambda i: i.replace(',', '')

可以取代下面的函數(shù):

def myFunction(i):
    return i.replace(',', '')

使用Lambda函數(shù),打包replace()和urljoin(),生成一個(gè)結(jié)果,只需一個(gè)參數(shù)即可。為了更清楚前面的表,來看幾個(gè)實(shí)例。在scrapy命令行打開任何URL,并嘗試:


>>> from scrapy.loader.processors import MapCompose, Join
>>> Join()(['hi','John'])
u'hi John'
>>> MapCompose(unicode.strip)([u'  I',u' am\n'])
[u'I', u'am']
>>> MapCompose(unicode.strip, unicode.title)([u'nIce cODe'])
[u'Nice Code']
>>> MapCompose(float)(['3.14'])
[3.14]
>>> MapCompose(lambda i: i.replace(',', ''), float)(['1,400.23'])
[1400.23]
>>> import urlparse
>>> mc = MapCompose(lambda i: urlparse.urljoin('http://my.com/test/abc', i))
>>> mc(['example.html#check'])
['http://my.com/test/example.html#check']
>>> mc(['http://absolute/url#help'])
['http://absolute/url#help']

要記住,處理函數(shù)是對(duì)XPath/CSS結(jié)果進(jìn)行后處理的的小巧函數(shù)。讓我們來看幾個(gè)我們爬蟲中的處理函數(shù)是如何清洗結(jié)果的:

def parse(self, response):
    l.add_xpath('title', '//*[@itemprop="name"][1]/text()',
                MapCompose(unicode.strip, unicode.title))
    l.add_xpath('price', './/*[@itemprop="price"][1]/text()',
                MapCompose(lambda i: i.replace(',', ''), float),
                re='[,.0-9]+')
    l.add_xpath('description', '//*[@itemprop="description"]'
                '[1]/text()', MapCompose(unicode.strip), Join())
    l.add_xpath('address',
               '//*[@itemtype="http://schema.org/Place"][1]/text()',
                MapCompose(unicode.strip))
    l.add_xpath('image_URL', '//*[@itemprop="image"][1]/@src',
                MapCompose(
                lambda i: urlparse.urljoin(response.url, i)))

完整的列表在本章后面給出。如果你用scrapy crawl basic再運(yùn)行的話,你可以得到干凈的結(jié)果如下:

'price': [334.39],
'title': [u'Set Unique Family Well']

最后,我們可以用add_value()方法添加用Python(不用XPath/CSS表達(dá)式)計(jì)算得到的值。我們用它設(shè)置我們的“雜務(wù)字段”,例如URL、爬蟲名、時(shí)間戳等等。我們直接使用前面雜務(wù)字段表里總結(jié)的表達(dá)式,如下:

l.add_value('url', response.url)
l.add_value('project', self.settings.get('BOT_NAME'))
l.add_value('spider', self.name)
l.add_value('server', socket.gethostname())
l.add_value('date', datetime.datetime.now())

記得import datetime和socket,以使用這些功能。

現(xiàn)在,我們的Items看起來就完美了。我知道你的第一感覺是,這可能太復(fù)雜了,值得嗎?回答是肯定的,這是因?yàn)榛蚨嗷蛏伲胱ト【W(wǎng)頁信息并存到items里,這就是你要知道的全部。這段代碼如果用其他語言來寫,會(huì)非常難看,很快就不能維護(hù)了。用Scrapy,只要25行簡(jiǎn)潔的代碼,它明確指明了意圖,你可以看清每行的意義,可以清晰的進(jìn)行修改、再利用和維護(hù)。

你的另一個(gè)感覺可能是處理函數(shù)和ItemLoaders太花費(fèi)精力。如果你是一名經(jīng)驗(yàn)豐富的Python開發(fā)者,你已經(jīng)會(huì)使用字符串操作、lambda表達(dá)構(gòu)造列表,再學(xué)習(xí)新的知識(shí)會(huì)覺得不舒服。然而,這只是對(duì)ItemLoader和其功能的簡(jiǎn)單介紹,如果你再深入學(xué)習(xí)一點(diǎn),你就不會(huì)這么想了。ItemLoaders和處理函數(shù)是專為有抓取需求的爬蟲編寫者、維護(hù)者開發(fā)的工具集。如果你想深入學(xué)習(xí)爬蟲的話,它們是絕對(duì)值得學(xué)習(xí)的。

創(chuàng)建協(xié)議
協(xié)議有點(diǎn)像爬蟲的單元測(cè)試。它們能讓你快速知道錯(cuò)誤。例如,假設(shè)你幾周以前寫了一個(gè)抓取器,它包含幾個(gè)爬蟲。你想快速檢測(cè)今天是否還是正確的。協(xié)議位于評(píng)論中,就在函數(shù)名后面,協(xié)議的開頭是@。看下面這個(gè)協(xié)議:

def parse(self, response):
    """ This function parses a property page.
    @url http://web:9312/properties/property_000000.html
    @returns items 1
    @scrapes title price description address image_URL
    @scrapes url project spider server date
    """

這段代碼是說,檢查這個(gè)URL,你可以在找到一個(gè)項(xiàng)目,它在那些字段有值。現(xiàn)在如果你運(yùn)行scrapy check,它會(huì)檢查協(xié)議是否被滿足:

$ scrapy check basic
----------------------------------------------------------------
Ran 3 contracts in 1.640s
OK
如果url的字段是空的(被注釋掉),你會(huì)得到一個(gè)描述性錯(cuò)誤:
FAIL: [basic] parse (@scrapes post-hook)
------------------------------------------------------------------
ContractFail: 'url' field is missing

當(dāng)爬蟲代碼有錯(cuò),或是XPath表達(dá)式過期,協(xié)議就可能失效。當(dāng)然,協(xié)議不會(huì)特別詳細(xì),但是可以清楚的指出代碼的錯(cuò)誤所在。

綜上所述,我們的第一個(gè)爬蟲如下所示:

from scrapy.loader.processors import MapCompose, Join
from scrapy.loader import ItemLoader
from properties.items import PropertiesItem
import datetime
import urlparse
import socket
import scrapy

class BasicSpider(scrapy.Spider):
    name = "basic"
    allowed_domains = ["web"]
    # Start on a property page
    start_URL = (
        'http://web:9312/properties/property_000000.html',
    )
    def parse(self, response):
        """ This function parses a property page.
        @url http://web:9312/properties/property_000000.html
        @returns items 1
        @scrapes title price description address image_URL
        @scrapes url project spider server date
        """
        # Create the loader using the response
        l = ItemLoader(item=PropertiesItem(), response=response)
        # Load fields using XPath expressions
        l.add_xpath('title', '//*[@itemprop="name"][1]/text()',
                    MapCompose(unicode.strip, unicode.title))
        l.add_xpath('price', './/*[@itemprop="price"][1]/text()',
                    MapCompose(lambda i: i.replace(',', ''),  
                    float),
                    re='[,.0-9]+')
        l.add_xpath('description', '//*[@itemprop="description"]'
                    '[1]/text()',
                    MapCompose(unicode.strip), Join())
        l.add_xpath('address',
                    '//*[@itemtype="http://schema.org/Place"]'
                    '[1]/text()',
                    MapCompose(unicode.strip))
        l.add_xpath('image_URL', '//*[@itemprop="image"]'
                    '[1]/@src', MapCompose(
                    lambda i: urlparse.urljoin(response.url, i)))
        # Housekeeping fields
        l.add_value('url', response.url)
        l.add_value('project', self.settings.get('BOT_NAME'))
        l.add_value('spider', self.name)
        l.add_value('server', socket.gethostname())
        l.add_value('date', datetime.datetime.now())
        return l.load_item()

提取更多的URL
到目前為止,在爬蟲的start_URL中我們還是只加入了一條URL。因?yàn)檫@是一個(gè)元組,我們可以向里面加入多個(gè)URL,例如:

start_URL = (
    'http://web:9312/properties/property_000000.html',
    'http://web:9312/properties/property_000001.html',
    'http://web:9312/properties/property_000002.html',
)

不夠好。我們可以用一個(gè)文件當(dāng)做URL源文件:

start_URL = [i.strip() for i in  
open('todo.URL.txt').readlines()]

還是不夠好,但行得通。更常見的,網(wǎng)站可能既有索引頁也有列表頁。例如,Gumtree有索引頁:http://www.gumtree.com/flats-houses/london

一個(gè)典型的索引頁包含許多列表頁、一個(gè)分頁系統(tǒng),讓你可以跳轉(zhuǎn)到其它頁面。

因此,一個(gè)典型的爬蟲在兩個(gè)方向移動(dòng):

  • 水平——從索引頁到另一個(gè)索引頁
  • 垂直——從索引頁面到列表頁面提取項(xiàng)目

在本書中,我們稱前者為水平抓取,因?yàn)樗谕粚哟危ɡ缢饕┥献ト№撁妫缓笳邽榇怪弊ト。驗(yàn)樗鼜母邔哟危ɡ缢饕┮苿?dòng)到一個(gè)較低的層次(例如列表)。

做起來要容易許多。我們只需要兩個(gè)XPath表達(dá)式。第一個(gè),我們右鍵點(diǎn)擊Next page按鈕,URL位于li中,li的類名含有next。因此XPath表達(dá)式為//*[contains(@class,"next")]//@href。

對(duì)于第二個(gè)表達(dá)式,我們?cè)诹斜淼臉?biāo)題上右鍵點(diǎn)擊,選擇檢查元素:

這個(gè)URL有一個(gè)屬性是itemprop="url。因此,表達(dá)式確定為//*[@itemprop="url"]/@href。打開scrapy命令行進(jìn)行確認(rèn):

$ scrapy shell http://web:9312/properties/index_00000.html
>>> URL = response.xpath('//*[contains(@class,"next")]//@href').extract()
>>> URL
[u'index_00001.html']
>>> import urlparse
>>> [urlparse.urljoin(response.url, i) for i in URL]
[u'http://web:9312/scrapybook/properties/index_00001.html']
>>> URL = response.xpath('//*[@itemprop="url"]/@href').extract()
>>> URL
[u'property_000000.html', ... u'property_000029.html']
>>> len(URL)
30
>>> [urlparse.urljoin(response.url, i) for i in URL]
[u'http://..._000000.html', ... /property_000029.html']

很好,我們看到有了這兩個(gè)表達(dá)式,就可以進(jìn)行水平和垂直抓取URL了。

使用爬蟲進(jìn)行二維抓取
將前一個(gè)爬蟲代碼復(fù)制到新的爬蟲manual.py中:

$ ls
properties  scrapy.cfg
$ cp properties/spiders/basic.py properties/spiders/manual.py

在properties/spiders/manual.py中,我們通過添加from scrapy.http import Request引入Request,將爬蟲的名字改為manual,將start_URL改為索引首頁,將parse()重命名為parse_item()。接下來寫心得parse()方法進(jìn)行水平和垂直的抓取:

def parse(self, response):
    # Get the next index URL and yield Requests
    next_selector = response.xpath('//*[contains(@class,'
                                   '"next")]//@href')
    for url in next_selector.extract():
        yield Request(urlparse.urljoin(response.url, url))

    # Get item URL and yield Requests
    item_selector = response.xpath('//*[@itemprop="url"]/@href')
    for url in item_selector.extract():
        yield Request(urlparse.urljoin(response.url, url),
                      callback=self.parse_item)

提示:你可能注意到了yield聲明。它和return很像,不同之處是return會(huì)退出循環(huán),而yield不會(huì)。從功能上講,前面的例子與下面很像

next_requests = []
for url in...
   next_requests.append(Request(...))
for url in...
   next_requests.append(Request(...))
return next_requests

yield可以大大提高Python編程的效率。

做好爬蟲了。但如果讓它運(yùn)行起來的話,它將抓取5萬張頁面。為了避免時(shí)間太長(zhǎng),我們可以通過命令-s CLOSESPIDER_ITEMCOUNT=90(更多的設(shè)定見第7章),設(shè)定爬蟲在一定數(shù)量(例如,90)之后停止運(yùn)行。開始運(yùn)行:

$ scrapy crawl manual -s CLOSESPIDER_ITEMCOUNT=90
INFO: Scrapy 1.0.3 started (bot: properties)
...
DEBUG: Crawled (200) <...index_00000.html> (referer: None)
DEBUG: Crawled (200) <...property_000029.html> (referer: ...index_00000.html)
DEBUG: Scraped from <200 ...property_000029.html>
  {'address': [u'Clapham, London'],
   'date': [datetime.datetime(2015, 10, 4, 21, 25, 22, 801098)],
   'description': [u'situated camden facilities corner'],
   'image_URL': [u'http://web:9312/images/i10.jpg'],
   'price': [223.88],
   'project': ['properties'],
  'server': ['scrapyserver1'],
   'spider': ['manual'],
   'title': [u'Portered Mile'],
   'url': ['http://.../property_000029.html']}
DEBUG: Crawled (200) <...property_000028.html> (referer: ...index_00000.
html)
...
DEBUG: Crawled (200) <...index_00001.html> (referer: ...)
DEBUG: Crawled (200) <...property_000059.html> (referer: ...)
...
INFO: Dumping Scrapy stats: ...
   'downloader/request_count': 94, ...
   'item_scraped_count': 90,

查看輸出,你可以看到我們得到了水平和垂直兩個(gè)方向的結(jié)果。首先讀取了index_00000.html, 然后產(chǎn)生了許多請(qǐng)求。執(zhí)行請(qǐng)求的過程中,debug信息指明了誰用URL發(fā)起了請(qǐng)求。例如,我們看到,property_000029.html, property_000028.html ... 和 index_00001.html都有相同的referer(即index_00000.html)。然后,property_000059.html和其它網(wǎng)頁的referer是index_00001,過程以此類推。

這個(gè)例子中,Scrapy處理請(qǐng)求的機(jī)制是后進(jìn)先出(LIFO),深度優(yōu)先抓取。最后提交的請(qǐng)求先被執(zhí)行。這個(gè)機(jī)制適用于大多數(shù)情況。例如,我們想先抓取完列表頁再取下一個(gè)索引頁。不然的話,我們必須消耗內(nèi)存存儲(chǔ)列表頁的URL。另外,許多時(shí)候你想用一個(gè)輔助的Requests執(zhí)行一個(gè)請(qǐng)求,下一章有例子。你需要Requests越早完成越好,以便爬蟲繼續(xù)下面的工作。

我們可以通過設(shè)定Request()參數(shù)修改默認(rèn)的順序,大于0時(shí)是高于默認(rèn)的優(yōu)先級(jí),小于0時(shí)是低于默認(rèn)的優(yōu)先級(jí)。通常,Scrapy會(huì)先執(zhí)行高優(yōu)先級(jí)的請(qǐng)求,但不會(huì)花費(fèi)太多時(shí)間思考到底先執(zhí)行哪一個(gè)具體的請(qǐng)求。在你的大多數(shù)爬蟲中,你不會(huì)有超過一個(gè)或兩個(gè)的請(qǐng)求等級(jí)。因?yàn)閁RL會(huì)被多重過濾,如果我們想向一個(gè)URL多次請(qǐng)求,我們可以設(shè)定參數(shù)dont_filter Request()為True。

用CrawlSpider二維抓取
如果你覺得這個(gè)二維抓取單調(diào)的話,說明你入門了。Scrapy試圖簡(jiǎn)化這些瑣事,讓編程更容易。完成之前結(jié)果的更好方法是使用CrawlSpider,一個(gè)簡(jiǎn)化抓取的類。我們用genspider命令,設(shè)定一個(gè)-t參數(shù),用爬蟲模板創(chuàng)建一個(gè)爬蟲:

$ scrapy genspider -t crawl easy web
Created spider 'crawl' using template 'crawl' in module:
  properties.spiders.easy

現(xiàn)在properties/spiders/easy.py文件包含如下所示:

...
class EasySpider(CrawlSpider):
    name = 'easy'
    allowed_domains = ['web']
    start_URL = ['http://www.web/']
    rules = (
        Rule(LinkExtractor(allow=r'Items/'),  
callback='parse_item', follow=True),
    )
    def parse_item(self, response):
        ...

這段自動(dòng)生成的代碼和之前的很像,但是在類的定義中,這個(gè)爬蟲從CrawlSpider定義的,而不是Spider。CrawlSpider提供了一個(gè)包含變量rules的parse()方法,以完成之前我們手寫的內(nèi)容。

現(xiàn)在將start_URL設(shè)定為索引首頁,并將parse_item()方法替換。這次不再使用parse()方法,而是將rules變成兩個(gè)rules,一個(gè)負(fù)責(zé)水平抓取,一個(gè)負(fù)責(zé)垂直抓取:

rules = (
Rule(LinkExtractor(restrict_xpaths='//*[contains(@class,"next")]')),
Rule(LinkExtractor(restrict_xpaths='//*[@itemprop="url"]'),
         callback='parse_item')
)

兩個(gè)XPath表達(dá)式與之前相同,但沒有了a與href的限制。正如它們的名字,LinkExtractor專門抽取鏈接,默認(rèn)就是尋找a、href屬性。你可以設(shè)定tags和attrs自定義LinkExtractor()。對(duì)比前面的請(qǐng)求方法Requests(self.parse_item),回調(diào)的字符串中含有回調(diào)方法的名字(例如,parse_item)。最后,除非設(shè)定callback,一個(gè)Rule就會(huì)沿著抽取的URL掃描外鏈。設(shè)定callback之后,Rule才能返回。如果你想讓Rule跟隨外鏈,你應(yīng)該從callback方法return/yield,或設(shè)定Rule()的follow參數(shù)為True。當(dāng)你的列表頁既有Items又有其它有用的導(dǎo)航鏈接時(shí)非常有用。

你現(xiàn)在可以運(yùn)行這個(gè)爬蟲,它的結(jié)果與之前相同,但簡(jiǎn)潔多了:

$ scrapy crawl easy -s CLOSESPIDER_ITEMCOUNT=90

總結(jié)
對(duì)所有學(xué)習(xí)Scrapy的人,本章也許是最重要的。你學(xué)習(xí)了爬蟲的基本流程UR2IM、如何自定義Items、使用ItemLoaders,XPath表達(dá)式、利用處理函數(shù)加載Items、如何yield請(qǐng)求。我們使用Requests水平抓取多個(gè)索引頁、垂直抓取列表頁。最后,我們學(xué)習(xí)了如何使用CrawlSpider和Rules簡(jiǎn)化代碼。多度幾遍本章以加深理解、創(chuàng)建自己的爬蟲。

我們剛剛從一個(gè)網(wǎng)站提取了信息。它的重要性在哪呢?答案在下一章,我們只用幾頁就能制作一個(gè)移動(dòng)app,并用Scrapy填充數(shù)據(jù)。


序言
第1章 Scrapy介紹
第2章 理解HTML和XPath
第3章 爬蟲基礎(chǔ)
第4章 從Scrapy到移動(dòng)應(yīng)用
第5章 快速構(gòu)建爬蟲
第6章 Scrapinghub部署
第7章 配置和管理
第8章 Scrapy編程
第9章 使用Pipeline
第10章 理解Scrapy的性能
第11章(完) Scrapyd分布式抓取和實(shí)時(shí)分析


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容