Scrapy 教程

Scrapy 教程

本文翻譯自scrapy的最新官方教程融求,覺得有幫助的朋友可以小小打賞一下,謝謝沮峡。

首先球匕,用戶需要安裝Scrapy,可以參見安裝指導帖烘。在本教程中,我們將爬取網(wǎng)站dmoz橄杨,并包含以下這些任務:

  1. 創(chuàng)建一個全新的Scrapy項目
  2. 定義用戶想爬取的數(shù)據(jù)類別
  3. 編寫一個爬蟲分析一個網(wǎng)頁并提取所需數(shù)據(jù)
  4. 編寫流程來存儲所提取的數(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']}

緊接著的鏈接

假設除開僅僅爬取BooksResources頁面,我們還想獲取所有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)自己的存儲方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炒刁,一起剝皮案震驚了整個濱河市恩沽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌翔始,老刑警劉巖罗心,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異城瞎,居然都是意外死亡渤闷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門脖镀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來飒箭,“玉大人,你說我怎么就攤上這事蜒灰∠阴澹” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵强窖,是天一觀的道長盈匾。 經(jīng)常有香客問我,道長毕骡,這世上最難降的妖魔是什么削饵? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮未巫,結(jié)果婚禮上窿撬,老公的妹妹穿的比我還像新娘。我一直安慰自己叙凡,他們只是感情好劈伴,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般跛璧。 火紅的嫁衣襯著肌膚如雪严里。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天追城,我揣著相機與錄音刹碾,去河邊找鬼。 笑死座柱,一個胖子當著我的面吹牛迷帜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播色洞,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼戏锹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了火诸?” 一聲冷哼從身側(cè)響起锦针,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎置蜀,沒想到半個月后伞插,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡盾碗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了舀瓢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廷雅。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖京髓,靈堂內(nèi)的尸體忽然破棺而出航缀,到底是詐尸還是另有隱情,我是刑警寧澤堰怨,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布芥玉,位于F島的核電站,受9級特大地震影響备图,放射性物質(zhì)發(fā)生泄漏灿巧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一揽涮、第九天 我趴在偏房一處隱蔽的房頂上張望抠藕。 院中可真熱鬧,春花似錦蒋困、人聲如沸盾似。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽零院。三九已至溉跃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間告抄,已是汗流浹背撰茎。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玄妈,地道東北人乾吻。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像拟蜻,于是被迫代替她去往敵國和親绎签。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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