在本篇教程中,我們假定您已經(jīng)安裝好 Scrapy。 如若不然,請(qǐng)參考安裝指南
。
接下來以 Open Directory Project(dmoz) (dmoz) 為例來講述爬取。
本篇教程中將帶您完成下列任務(wù):
spider
并提取 Item
Item Pipeline
來存儲(chǔ)提取到的 Item(即數(shù)據(jù))Scrapy 由 Python 編寫。如果您剛接觸并且好奇這門語(yǔ)言的特性以及 Scrapy 的詳情,對(duì)于已經(jīng)熟悉其他語(yǔ)言并且想快速學(xué)習(xí) Python 的編程老手,我們推薦 Learn Python The Hard Way,對(duì)于想從 Python 開始學(xué)習(xí)的編程新手,非程序員的 Python 學(xué)習(xí)資料列表將是您的選擇。
在開始爬取之前,您必須創(chuàng)建一個(gè)新的 Scrapy 項(xiàng)目。 進(jìn)入您打算存儲(chǔ)代碼的目錄中,運(yùn)行下列命令:
scrapy startproject tutorial
該命令將會(huì)創(chuàng)建包含下列內(nèi)容的 tutorial 目錄:
tutorial/
scrapy.cfg
tutorial/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...
這些文件分別是:
Item 是保存爬取到的數(shù)據(jù)的容器;其使用方法和 python 字典類似, 并且提供了額外保護(hù)機(jī)制來避免拼寫錯(cuò)誤導(dǎo)致的未定義字段錯(cuò)誤。
類似在 ORM 中做的一樣,您可以通過創(chuàng)建一個(gè) scrapy.Item
類, 并且定義類型為 scrapy.Field
的類屬性來定義一個(gè) Item。 (如果不了解 ORM, 不用擔(dān)心,您會(huì)發(fā)現(xiàn)這個(gè)步驟非常簡(jiǎn)單)
首先根據(jù)需要從 dmoz.org 獲取到的數(shù)據(jù)對(duì) item 進(jìn)行建模。 我們需要從 dmoz 中獲取名字,url,以及網(wǎng)站的描述。 對(duì)此,在 item 中定義相應(yīng)的字段。編輯 tutorial
目錄中的 items.py
文件:
import scrapy
class DmozItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
desc = scrapy.Field()
一開始這看起來可能有點(diǎn)復(fù)雜,但是通過定義 item, 您可以很方便的使用 Scrapy 的其他方法。而這些方法需要知道您的 item 的定義。
Spider 是用戶編寫用于從單個(gè)網(wǎng)站(或者一些網(wǎng)站)爬取數(shù)據(jù)的類。
其包含了一個(gè)用于下載的初始 URL,如何跟進(jìn)網(wǎng)頁(yè)中的鏈接以及如何分析頁(yè)面中的內(nèi)容, 提取生成 item
的方法。
為了創(chuàng)建一個(gè) Spider,您必須繼承 scrapy.Spider
類, 且定義以下三個(gè)屬性:
name
: 用于區(qū)別 Spider。 該名字必須是唯一的,您不可以為不同的 Spider 設(shè)定相同的名字。start_urls
: 包含了 Spider 在啟動(dòng)時(shí)進(jìn)行爬取的 url 列表。 因此,第一個(gè)被獲取到的頁(yè)面將是其中之一。 后續(xù)的 URL 則從初始的 URL 獲取到的數(shù)據(jù)中提取。parse()
是 spider 的一個(gè)方法。 被調(diào)用時(shí),每個(gè)初始 URL 完成下載后生成的 Response
對(duì)象將會(huì)作為唯一的參數(shù)傳遞給該函數(shù)。 該方法負(fù)責(zé)解析返回的數(shù)據(jù)(response data),提取數(shù)據(jù)(生成 item)以及生成需要進(jìn)一步處理的 URL 的 `Request 對(duì)象。以下為我們的第一個(gè) Spider 代碼,保存在 tutorial/spiders 目錄下的 dmoz_spider.py 文件中:
import scrapy
class DmozSpider(scrapy.spiders.Spider):
name = "dmoz"
allowed_domains = ["dmoz.org"]
start_urls = [
"http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
]
def parse(self, response):
filename = response.url.split("/")[-2]
with open(filename, 'wb') as f:
f.write(response.body)
進(jìn)入項(xiàng)目的根目錄,執(zhí)行下列命令啟動(dòng) spider:
scrapy crawl dmoz
crawl dmoz
啟動(dòng)用于爬取 dmoz.org
的 spider,您將得到類似的輸出:
2014-01-23 18:13:07-0400 [scrapy] INFO: Scrapy started (bot: tutorial)
2014-01-23 18:13:07-0400 [scrapy] INFO: Optional features available: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Overridden settings: {}
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled extensions: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled downloader middlewares: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled spider middlewares: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled item pipelines: ...
2014-01-23 18:13:07-0400 [dmoz] INFO: Spider opened
2014-01-23 18:13:08-0400 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2014-01-23 18:13:09-0400 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
2014-01-23 18:13:09-0400 [dmoz] INFO: Closing spider (finished)
查看包含 [dmoz]
的輸出,可以看到輸出的 log 中包含定義在 start_urls
的初始 URL,并且與 spider 中是一一對(duì)應(yīng)的。在 log 中可以看到其沒有指向其他頁(yè)面( (referer:None
) )。
除此之外,更有趣的事情發(fā)生了。就像我們 parse 方法指定的那樣,有兩個(gè)包含 url 所對(duì)應(yīng)的內(nèi)容的文件被創(chuàng)建了: Book,Resources 。
Scrapy 為 Spider 的 start_urls 屬性中的每個(gè) URL 創(chuàng)建了 scrapy.Request 對(duì)象,并將 parse 方法作為回調(diào)函數(shù)(callback)賦值給了 Request。
Request 對(duì)象經(jīng)過調(diào)度,執(zhí)行生成 scrapy.http.Response
對(duì)象并送回給 spider parse()
方法。
從網(wǎng)頁(yè)中提取數(shù)據(jù)有很多方法。Scrapy 使用了一種基于 XPath 和 CSS 表達(dá)式機(jī)制: Scrapy Selectors
。 關(guān)于 selector 和其他提取機(jī)制的信息請(qǐng)參考 Selector
文檔。
這里給出 XPath 表達(dá)式的例子及對(duì)應(yīng)的含義:
/html/head/title
: 選擇 HTML 文檔中<head>
標(biāo)簽內(nèi)的<title>
元素/html/head/title/text()
: 選擇上面提到的<title>
元素的文字//td
: 選擇所有的<td>
元素//div[@class="mine"]
: 選擇所有具有 class="mine"屬性的 div 元素上邊僅僅是幾個(gè)簡(jiǎn)單的 XPath 例子,XPath 實(shí)際上要比這遠(yuǎn)遠(yuǎn)強(qiáng)大的多。 如果您想了解的更多,我們推薦這篇 XPath 教程。
為了配合 XPath,Scrapy 除了提供了 Selector
之外,還提供了方法來避免每次從 response 中提取數(shù)據(jù)時(shí)生成 selector 的麻煩。
Selector 有四個(gè)基本的方法(點(diǎn)擊相應(yīng)的方法可以看到詳細(xì)的 API 文檔):
xpath()
: 傳入 xpath 表達(dá)式,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的 selector list 列表 。css()
: 傳入 CSS 表達(dá)式,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的 selector list 列表.extract()
: 序列化該節(jié)點(diǎn)為 unicode 字符串并返回 list。re()
: 根據(jù)傳入的正則表達(dá)式對(duì)數(shù)據(jù)進(jìn)行提取,返回 unicode 字符串 list 列表。為了介紹 Selector 的使用方法,接下來我們將要使用內(nèi)置的 Scrapy shell。Scrapy Shell 需要您預(yù)裝好 IPython(一個(gè)擴(kuò)展的 Python 終端)。
您需要進(jìn)入項(xiàng)目的根目錄,執(zhí)行下列命令來啟動(dòng) shell:
scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"
注解
當(dāng)您在終端運(yùn)行 Scrapy 時(shí),請(qǐng)一定記得給 url 地址加上引號(hào),否則包含參數(shù)的 url(例如 & 字符)會(huì)導(dǎo)致 Scrapy 運(yùn)行失敗。
shell 的輸出類似:
[ ... Scrapy log here ... ]
2015-01-07 22:01:53+0800 [domz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
[s] Available Scrapy objects:
[s] crawler <scrapy.crawler.Crawler object at 0x02CE2530>
[s] item {}
[s] request <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s] response <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s] sel <Selector xpath=None data=u'<html lang="en">\r\n<head>\r\n<meta http-equ'>
[s] settings <CrawlerSettings module=<module 'tutorial.settings' from 'tutorial\settings.pyc'>>
[s] spider <DomzSpider 'domz' at 0x302e350>
[s] Useful shortcuts:
[s] shelp() Shell help (print this help)
[s] fetch(req_or_url) Fetch request (or URL) and update local objects
[s] view(response) View response in a browser
>>>
當(dāng) shell 載入后,您將得到一個(gè)包含 response 數(shù)據(jù)的本地 response 變量。輸入 response.body 將輸出 response 的包體,輸出 response.headers 可以看到 response 的包頭。
更為重要的是,當(dāng)輸入 response.selector 時(shí), 您將獲取到一個(gè)可以用于查詢返回?cái)?shù)據(jù)的 selector(選擇器), 以及映射到 response.selector.xpath() 、response.selector.css() 的 快捷方法(shortcut): response.xpath() 和 response.css() 。
同時(shí),shell 根據(jù) response 提前初始化了變量 sel 。該 selector 根據(jù) response 的類型自動(dòng)選擇最合適的分析規(guī)則(XML vs HTML)。
讓我們來試試:
In [1]: sel.xpath('//title')
Out[1]: [<Selector xpath='//title' data=u'<title>Open Directory - Computers: Progr'>]
In [2]: sel.xpath('//title').extract()
Out[2]: [u'<title>Open Directory - Computers: Programming: Languages: Python: Books</title>']
In [3]: sel.xpath('//title/text()')
Out[3]: [<Selector xpath='//title/text()' data=u'Open Directory - Computers: Programming:'>]
In [4]: sel.xpath('//title/text()').extract()
Out[4]: [u'Open Directory - Computers: Programming: Languages: Python: Books']
In [5]: sel.xpath('//title/text()').re('(\w+):')
Out[5]: [u'Computers', u'Programming', u'Languages', u'Python']
現(xiàn)在,我們來嘗試從這些頁(yè)面中提取些有用的數(shù)據(jù)。
您可以在終端中輸入 response.body
來觀察 HTML 源碼并確定合適的 XPath 表達(dá)式。不過,這任務(wù)非常無聊且不易。您可以考慮使用 Firefox 的 Firebug 擴(kuò)展來使得工作更為輕松。詳情請(qǐng)參考 使用 Firebug 進(jìn)行爬取和借助 Firefox 來爬取。
在查看了網(wǎng)頁(yè)的源碼后,您會(huì)發(fā)現(xiàn)網(wǎng)站的信息是被包含在 第二個(gè) <ul> 元素中。
我們可以通過這段代碼選擇該頁(yè)面中網(wǎng)站列表里所有 <li> 元素:
sel.xpath('//ul/li')
網(wǎng)站的描述:
sel.xpath('//ul/li/text()').extract()
網(wǎng)站的標(biāo)題:
sel.xpath('//ul/li/a/text()').extract()
以及網(wǎng)站的鏈接:
sel.xpath('//ul/li/a/@href').extract()
之前提到過,每個(gè).xpath()
調(diào)用返回 selector 組成的 list,因此我們可以拼接更多的.xpath()
來進(jìn)一步獲取某個(gè)節(jié)點(diǎn)。我們將在下邊使用這樣的特性:
for sel in response.xpath('//ul/li'):
title = sel.xpath('a/text()').extract()
link = sel.xpath('a/@href').extract()
desc = sel.xpath('text()').extract()
print title, link, desc
注解
關(guān)于嵌套 selctor 的更多詳細(xì)信息,請(qǐng)參考 嵌套選擇器(selectors) 以及 選擇器(Selectors) 文檔中的 使用相對(duì) XPaths 部分。
在我們的 spider 中加入這段代碼:
import scrapy
class DmozSpider(scrapy.Spider):
name = "dmoz"
allowed_domains = ["dmoz.org"]
start_urls = [
"http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
]
def parse(self, response):
for sel in response.xpath('//ul/li'):
title = sel.xpath('a/text()').extract()
link = sel.xpath('a/@href').extract()
desc = sel.xpath('text()').extract()
print title, link, desc
現(xiàn)在嘗試再次爬取 dmoz.org,您將看到爬取到的網(wǎng)站信息被成功輸出:
scrapy crawl dmoz
Item 對(duì)象是自定義的 python 字典。您可以使用標(biāo)準(zhǔn)的字典語(yǔ)法來獲取到其每個(gè)字段的值。(字段即是我們之前用 Field 賦值的屬性):
>>> item = DmozItem()
>>> item['title'] = 'Example title'
>>> item['title']
'Example title'
一般來說,Spider 將會(huì)將爬取到的數(shù)據(jù)以 Item 對(duì)象返回。所以為了將爬取的數(shù)據(jù)返回,我們最終的代碼將是:
import scrapy
from tutorial.items import DmozItem
class DmozSpider(scrapy.Spider):
name = "dmoz"
allowed_domains = ["dmoz.org"]
start_urls = [
"http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
]
def parse(self, response):
for sel in response.xpath('//ul/li'):
item = DmozItem()
item['title'] = sel.xpath('a/text()').extract()
item['link'] = sel.xpath('a/@href').extract()
item['desc'] = sel.xpath('text()').extract()
yield item
注解
您可以在 dirbot 項(xiàng)目中找到一個(gè)具有完整功能的 spider。該項(xiàng)目可以通過 https://github.com/scrapy/dirbot 找到。
現(xiàn)在對(duì) dmoz.org 進(jìn)行爬取將會(huì)產(chǎn)生 DmozItem
對(duì)象:
[dmoz] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
{'desc': [u' - By David Mertz; Addison Wesley. Book in progress, full text, ASCII format. Asks for feedback. [author website, Gnosis Software, Inc.\n],
'link': [u'http://gnosis.cx/TPiP/'],
'title': [u'Text Processing in Python']}
[dmoz] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
{'desc': [u' - By Sean McGrath; Prentice Hall PTR, 2000, ISBN 0130211192, has CD-ROM. Methods to build XML applications fast, Python tutorial, DOM and SAX, new Pyxie open source XML processing library. [Prentice Hall PTR]\n'],
'link': [u'http://www.informit.com/store/product.aspx?isbn=0130211192'],
'title': [u'XML Processing with Python']}
最簡(jiǎn)單存儲(chǔ)爬取的數(shù)據(jù)的方式是使用 Feed exports
:
scrapy crawl dmoz -o items.json
該命令將采用 JSON 格式對(duì)爬取的數(shù)據(jù)進(jìn)行序列化,生成 items.json
文件。
在類似本篇教程里這樣小規(guī)模的項(xiàng)目中,這種存儲(chǔ)方式已經(jīng)足夠。 如果需要對(duì)爬取到的 item 做更多更為復(fù)雜的操作,您可以編寫 Item Pipeline
。 類似于我們?cè)趧?chuàng)建項(xiàng)目時(shí)對(duì) Item 做的,用于您編寫自己的 tutorial/pipelines.py
也被創(chuàng)建。 不過如果您僅僅想要保存 item,您不需要實(shí)現(xiàn)任何的 pipeline。
本篇教程僅介紹了 Scrapy 的基礎(chǔ),還有很多特性沒有涉及。請(qǐng)查看 初窺 Scrapy 章節(jié)中的 還有什么? 部分,大致瀏覽大部分重要的特性。
接著,我們推薦您把玩一個(gè)例子(查看例子
),而后繼續(xù)閱讀基本概念
。