Scrapy 教程
本文翻譯自scrapy的最新官方教程融求,覺得有幫助的朋友可以小小打賞一下,謝謝沮峡。
首先球匕,用戶需要安裝Scrapy,可以參見安裝指導帖烘。在本教程中,我們將爬取網(wǎng)站dmoz橄杨,并包含以下這些任務:
- 創(chuàng)建一個全新的Scrapy項目
- 定義用戶想爬取的數(shù)據(jù)類別
- 編寫一個爬蟲分析一個網(wǎng)頁并提取所需數(shù)據(jù)
- 編寫流程來存儲所提取的數(shù)據(jù)
創(chuàng)建一個項目
首先秘症,我們在目標路徑下輸入并執(zhí)行以下代碼
scrapy startproject tutorial
這會創(chuàng)建一個tutorial
目錄,其中含有以下內(nèi)容
tutorial/
scrapy.cfg # 配置文件
tutorial/ # 項目的Python模組
__init__.py
items.py # items文件式矫,數(shù)據(jù)格式定義
pipelines.py # pipelines文件乡摹,定義流程處理
settings.py # settings文件,項目設置
spiders/ # 爬蟲路徑采转,用戶根據(jù)需要定義自己的爬蟲
__init__.py
...
定義我們的數(shù)據(jù)(Item)
Items是存儲的容器聪廉,隨著爬蟲工作而加載,調(diào)用格式上和Python字典類似故慈。Scrapy中也可以使用單純的Python字典板熊,但是Items提供了額外的對于填充未聲明字段的保護機制,避免用戶輸入錯誤引起的錯誤察绷。
我們通過創(chuàng)建一個scrapy.Item
類干签,并定義其屬性為scrapy.Field
來聲明Scapy中的數(shù)據(jù)類型。這和對應關(guān)系映射(ORM)機制類似拆撼。
我們首先對我們希望從dmoz.org獲取到的數(shù)據(jù)進行建娜堇停刻畫。具體而言闸度,我們希望獲取網(wǎng)頁的名稱竭贩,連接以及描述信息,因此我們對這三個屬性進行字段定義莺禁。因此我們?nèi)缦戮帉?code>tutorial目錄下的items.py
:
import scrapy
class DmozItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
desc = scrapy.Field()
這種定義初看比較復雜留量,但是定義這樣一個類會讓用戶可以進一步使用其他Scrapy中便利的部件和助手。
第一個爬蟲
爬蟲(spiders)是用戶定義的一系列類睁宰,Scrapy根據(jù)這些類在某個定義域內(nèi)抓取用戶感興趣的信息肪获。在爬蟲中,用戶定義初始的需要下載的連接柒傻,怎么進一步擴展爬蟲孝赫,如何解析當前頁面并提取之間定義的Items
為了創(chuàng)建一個爬蟲,用戶首先需要定義一個scrapy.Spider
的子類红符,并且定義某些屬性:
-
name
:是爬蟲的識別符青柄,在當前項目中必須是唯一伐债,不能為不同的爬蟲設置相同的名稱。 -
start_urls
:一組起始鏈接集合致开,爬蟲將從這些鏈接開始峰锁。接下來的鏈接會從這些起始鏈接中提取。 -
parse
:爬蟲的一個方法双戳,會以下載起始鏈接得到的Response
作為參數(shù)調(diào)用虹蒋。其中Response
作為唯一參數(shù)傳遞進去。這個方法負責解析響應數(shù)據(jù)并提取爬取的數(shù)據(jù)以及更多的鏈接飒货。 更進一步魄衅,parse
方法負責處理相應并返回數(shù)據(jù)和接下來的鏈接。
所以我們首先如下定義第一個爬蟲塘辅,存在tutorial/spiders
目錄下晃虫,命名為dmoz_spider.py
:
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):
filename = response.url.split("/")[-2] + '.html'
with open(filename, 'wb') as f:
f.write(response.body)
抓取
Scrapy中我們通過如下命令執(zhí)行爬蟲任務
scrapy crawl dmoz
注意上述命令在項目的最高層目錄執(zhí)行,而dmoz
就是上面定義的爬蟲唯一標識符扣墩。第一次我們會得到類似下面的結(jié)果輸出:
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 [scrapy] INFO: Spider opened
2014-01-23 18:13:08-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2014-01-23 18:13:09-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
2014-01-23 18:13:09-0400 [scrapy] INFO: Closing spider (finished)
由于我們之前的parse
中將響應的body
寫成文件并存儲在最高層目錄哲银。
對當前爬蟲的分析
Scrapy首先針對每一個在start_urls
中的鏈接創(chuàng)建scrapy.Request
對象,并將parse
方法設置為爬蟲的回調(diào)函數(shù)呻惕。這些請求被Scrapy安排時間規(guī)劃并先后執(zhí)行荆责,返回scrapy.http.Response
對象然后通過parse
方法傳輸給爬蟲對象。
提取數(shù)據(jù)
選擇器簡介
這里有不同的方法從網(wǎng)頁中提取數(shù)據(jù)蟆融。Scrapy使用基于XPATH或者CSS的機制實現(xiàn)了自己的選擇器Scrapy Selectors草巡。這里簡單介紹一些XPath表達式以及其含義
-
/html/head/title
: 選擇<head>
元素中的<title>
元素 -
/html/head/title/text()
: 選擇上面<title>
元素中的文本 -
//td
: 選擇所有的<td>
元素 -
//div[@class="mine"]
: 選擇所有包含屬性class="min"
的div
元素
為了解析CSS和XPath表達式,Scrapy提供選擇器Selector
類以及方便的快捷式避免每次都重復實例化選擇器型酥∩胶總體說來,選擇器可以被看作是表示文檔結(jié)構(gòu)節(jié)點的對象弥喉。所以郁竟,第一個實例化的選擇器是和根節(jié)點即整個文檔關(guān)聯(lián)在一塊的。
Scrapy中的Selector
含有四個常用的基本方法
-
xpath()
: 返回一個選擇器列表由境,每一個元素代表根據(jù)xpath表達式選擇的節(jié)點棚亩。 -
css()
: 返回一個選擇器列表,每一個元素代表根據(jù)css表達式選擇的節(jié)點虏杰。 -
extract
: 返回選擇數(shù)據(jù)的unicode字符串 -
re()
: 返回作用上正則表達式的unicode字符串列表
在殼(shell)中嘗試選擇器
在項目的根目錄執(zhí)行命令
scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"
此時IPython環(huán)境看起來如下
[ ... Scrapy log here ... ]
2014-01-23 17:11:42-0400 [scrapy] 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 0x3636b50>
[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] settings <scrapy.settings.Settings object at 0x3fadc50>
[s] spider <Spider 'default' at 0x3cebf50>
[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
In [1]:
在shell環(huán)境加載后讥蟆,我們可以通過局部變量response
獲取響應數(shù)據(jù),所以如果用戶在命令行中輸入response.body
纺阔,就會看到響應的主體瘸彤,類似地,你可以通過輸入response.headers
訪問其頭部數(shù)據(jù)笛钝。
此時质况,response
變量含有選擇器selector
屬性愕宋,為Selector
類的一個實例,隨著這個具體的reponse
實例化结榄。因此用戶可以通過調(diào)用response.selector.xpath()
或者response.selector.css()
來獲取需要的部分中贝,也可以通過快捷表達式response.xpath()
和response.css()
來調(diào)用。測試我們剛剛介紹的選擇器臼朗,有
In [1]: response.xpath('//title')
Out[1]: [<Selector xpath='//title' data=u'<title>Open Directory - Computers: Progr'>]
In [2]: response.xpath('//title').extract()
Out[2]: [u'<title>Open Directory - Computers: Programming: Languages: Python: Books</title>']
In [3]: response.xpath('//title/text()')
Out[3]: [<Selector xpath='//title/text()' data=u'Open Directory - Computers: Programming:'>]
In [4]: response.xpath('//title/text()').extract()
Out[4]: [u'Open Directory - Computers: Programming: Languages: Python: Books']
In [5]: response.xpath('//title/text()').re('(\w+):')
Out[5]: [u'Computers', u'Programming', u'Languages', u'Python']
可以看到邻寿,直接選擇出來為Scrapy中的選擇器對象,需要通過extract()
或者re()
方法提取视哑。
提取數(shù)據(jù)
有了之前的基礎老厌,我們現(xiàn)在從這些頁面中嘗試提取出真實有用的信息。注意到response.body
是網(wǎng)頁的源代碼黎炉,為HTML代碼,通常很難對其進行直接分析醋拧,用戶可以使用一些可視化方法來輔助慷嗜,比如Firebug。通過觀察源代碼可以發(fā)現(xiàn)丹壕,需要的網(wǎng)頁信息其實都存儲在一個<ul>
元素內(nèi)庆械,所以整體流程是通過選擇<li>
元素列表來獲取需要信息:
response.xpath('//ul/li')
以及其中對網(wǎng)頁的描述
response.xpath('//ul/li/text()').extract()
網(wǎng)頁的標題為
response.xpath('//ul/li/a/text()').extract()
以及這些網(wǎng)頁的鏈接地址
response.xpath('//ul/li/a/@href').extract()
正如我們提到的,每一個.xpath()
返回一個選擇器列表菌赖,所以我們可以通過對選擇器調(diào)用.xpath()
方法去進一步獲取需要數(shù)據(jù)缭乘,例如我們將之前的幾種選擇器融合在一塊,有
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
我們將這部分放到之前的parse()
中琉用,有
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
此時我們將不再得到簡單的HTML文件堕绩,而是需要的數(shù)據(jù)格式
使用我們的Item
Item
對象是Scrapy定制的Python字典,所以我們可以簡單使用標準的字典語法來獲取我們想要的值
>>> item = DmozItem()
>>> item['title'] = 'Example title'
>>> item['title']
'Example title'
和普通字典沒有什么區(qū)別邑时。為了返回當前我們爬到的數(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
使用這個爬蟲去爬取dmoz.org可以得到DmozItem
對象
[scrapy] 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']}
[scrapy] 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']}
緊接著的鏈接
假設除開僅僅爬取Books和Resources頁面,我們還想獲取所有Python路徑下的頁面【穑現(xiàn)在用戶知道如何從一個頁面提取數(shù)據(jù)黍氮,所以現(xiàn)在問題是如何從當前頁面抓取用戶感興趣頁面的鏈接,緊接著在這些頁面再次提取感興趣的數(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/",
]
def parse(self, response):
for href in response.css("ul.directory.dir-col > li > a::attr('href')"):
url = response.urljoin(href.extract())
yield scrapy.Request(url, callback=self.parse_dir_contents)
def parse_dir_contents(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
現(xiàn)在parse()
方法僅僅提取了鏈接數(shù)據(jù)沫浆,通過response.urljoin
方法建立絕對路徑并且產(chǎn)生新的請求,并注冊回調(diào)函數(shù)parse_dir_contents()
來爬取需要的數(shù)據(jù)滚秩。這里Scrapy的機制是這樣的专执,當產(chǎn)生新的請求時,Scrapy會調(diào)度進程發(fā)送請求而回調(diào)函數(shù)會在請求完成后執(zhí)行叔遂。在這樣的機制下他炊,用戶可以設計很復雜的爬蟲機制争剿,根據(jù)規(guī)則得到下一步的鏈接,以及根據(jù)當前頁面規(guī)則提取不同的數(shù)據(jù)痊末。一個常用的模式是先使用回調(diào)函數(shù)提取數(shù)據(jù)蚕苇,尋找下一個鏈接然后產(chǎn)生新的請求
def parse_articles_follow_next_page(self, response):
for article in response.xpath("http://article"):
item = ArticleItem()
... extract article data here
yield item
next_page = response.css("ul.navigation > li.next-page > a::attr('href')")
if next_page:
url = response.urljoin(next_page[0].extract())
yield scrapy.Request(url, self.parse_articles_follow_next_page)
存儲爬取的數(shù)據(jù)
最簡單的方法是調(diào)用下面的命令
scrapy crawl dmoz -o items.json
這會產(chǎn)生一個包含所有數(shù)據(jù)的文件items.json
,使用JSON序列化凿叠。在小項目中涩笤,這種方法一般是足夠的,如果想設計更復雜的系統(tǒng)盒件,用戶可以編寫一個Item Pipeline蹬碧,即在tutorial/pipelines.py
中實現(xiàn)自己的存儲方法。