1. 程式人生 > >Python 之 Scrapy筆記(2)- 完整示例

Python 之 Scrapy筆記(2)- 完整示例

這篇文章我們通過一個比較完整的例子來教你使用Scrapy,我選擇爬取虎嗅網首頁的新聞列表。

這裡我們將完成如下幾個步驟:

  • 建立一個新的Scrapy工程
  • 定義你所需要要抽取的Item物件
  • 編寫一個spider來爬取某個網站並提取出所有的Item物件
  • 編寫一個Item Pipline來儲存提取出來的Item物件

Scrapy使用Python語言編寫,如果你對這門語言還不熟,請先去學習下基本知識。

建立Scrapy工程

在任何你喜歡的目錄執行如下命令

1
scrapy startproject coolscrapy

將會建立coolscrapy資料夾,其目錄結構如下:

coolscrapy/
scrapy.cfg # 部署配置檔案 coolscrapy/ # Python模組,你所有的程式碼都放這裡面 __init__.py items.py # Item定義檔案 pipelines.py # pipelines定義檔案 settings.py # 配置檔案 spiders/ # 所有爬蟲spider都放這個資料夾下面 __init__.py ...

定義我們的Item

我們通過建立一個scrapy.Item類,並定義它的型別為scrapy.Field的屬性, 我們準備將虎嗅網新聞列表的名稱、連結地址和摘要爬取下來。我們通過將需要的item模型化,來控制從***獲得的站點資料,比如我們要獲得站點的名字,url和網站描述,我們定義這三種屬性的域。要做到這點,我們編輯在coolscrapy目錄下的items.py檔案,我們的Item類將會是這樣

1
2
3
4
5
6
7
import scrapy

class HuxiuItem(scrapy.Item):
    title = scrapy.Field()    # 標題
link = scrapy.Field() # 連結 desc = scrapy.Field() # 簡述 posttime = scrapy.Field() # 釋出時間

也許你覺得定義這個Item有點麻煩,但是定義完之後你可以得到許多好處,這樣你就可以使用Scrapy中其他有用的元件和幫助類。

第一個Spider

Spider是使用者編寫的類,用於從一個域(或域組)中抓取資訊。他們定義了用於下載的URL的初步列表,如何跟蹤連結,以及如何來解析這些網頁的內容用於提取items。

要建立一個Spider,你必須為scrapy.spider.BaseSpider建立一個子類,並確定三個主要的、強制的屬性:

  • name:爬蟲的識別名,它必須是唯一的,在不同的爬蟲中你必須定義不同的名字.
  • start_urls:爬蟲開始爬的一個URL列表。爬蟲從這裡開始抓取資料,所以,第一次下載的資料將會從這些URLS開始。其他子URL將會從這些起始URL中繼承性生成。
  • parse():爬蟲的方法,呼叫時候傳入從每一個URL傳回的Response物件作為引數,response將會是parse方法的唯一的一個引數,

這個方法負責解析返回的資料、匹配抓取的資料(解析為item)並跟蹤更多的URL。

我們在coolscrapy/spiders資料夾下面新建huxiu_spider.py,內容如下:

huxiu_spider.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
Topic: sample
Desc :
"""
from coolscrapy.items import HuxiuItem
import scrapy

class HuxiuSpider(scrapy.Spider):
    name = "huxiu"
    allowed_domains = ["huxiu.com"]
    start_urls = [
        "http://www.huxiu.com/index.php"
    ]

    def parse(self, response):
        for sel in response.xpath('//div[@class="mod-info-flow"]/div/div[@class="mob-ctt"]'):
            item = HuxiuItem()
            item['title'] = sel.xpath('h3/a/text()')[0].extract()
            item['link'] = sel.xpath('h3/a/@href')[0].extract()
            url = response.urljoin(item['link'])
            item['desc'] = sel.xpath('div[@class="mob-sub"]/text()')[0].extract()
            print(item['title'],item['link'],item['desc'])

執行爬蟲

為了讓我們的爬蟲工作,我們返回專案主目錄執行以下命令,其中huxiu是你定義的spider名字:

scrapy crawl huxiu

如果一切正常,應該可以打印出每一個新聞:

複製程式碼
T:\tutorial>scrapy crawl dmoz
2012-07-13 19:14:45+0800 [scrapy] INFO: Scrapy 0.14.4 started (bot: tutorial)
2012-07-13 19:14:45+0800 [scrapy] DEBUG: Enabled extensions: LogStats, TelnetConsole, CloseSpider, WebService, CoreStats, SpiderState
2012-07-13 19:14:45+0800 [scrapy] DEBUG: Enabled downloader middlewares: HttpAuthMiddleware, DownloadTimeoutMiddleware, UserAgentMiddleware, RetryMiddleware, DefaultHeadersMiddleware, RedirectMiddleware, CookiesMiddleware, HttpCompressionMiddleware, ChunkedTransferMiddleware, DownloaderStats
2012-07-13 19:14:45+0800 [scrapy] DEBUG: Enabled spider middlewares: HttpErrorMiddleware, OffsiteMiddleware, RefererMiddleware, UrlLengthMiddleware, DepthMiddleware
2012-07-13 19:14:45+0800 [scrapy] DEBUG: Enabled item pipelines:
2012-07-13 19:14:45+0800 [dmoz] INFO: Spider opened
2012-07-13 19:14:45+0800 [dmoz] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2012-07-13 19:14:45+0800 [scrapy] DEBUG: Telnet console listening on 0.0.0.0:6023
2012-07-13 19:14:45+0800 [scrapy] DEBUG: Web service listening on 0.0.0.0:6080
2012-07-13 19:14:46+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2012-07-13 19:14:46+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
2012-07-13 19:14:46+0800 [dmoz] INFO: Closing spider (finished)
2012-07-13 19:14:46+0800 [dmoz] INFO: Dumping spider stats:
        {'downloader/request_bytes': 486,
         'downloader/request_count': 2,
         'downloader/request_method_count/GET': 2,
         'downloader/response_bytes': 13063,
         'downloader/response_count': 2,
         'downloader/response_status_count/200': 2,
         'finish_reason': 'finished',
         'finish_time': datetime.datetime(2012, 7, 13, 11, 14, 46, 703000),
         'scheduler/memory_enqueued': 2,
         'start_time': datetime.datetime(2012, 7, 13, 11, 14, 45, 500000)}
2012-07-13 19:14:46+0800 [dmoz] INFO: Spider closed (finished)
2012-07-13 19:14:46+0800 [scrapy] INFO: Dumping global stats:
        {}
複製程式碼

注意包含 [dmoz]的行 ,那對應著我們的爬蟲。你可以看到start_urls中定義的每個URL都有日誌行。因為這些URL是起始頁面,所以他們沒有引用(referrers),所以在每行的末尾你會看到 (referer: <None>). 
有趣的是,在我們的 parse  方法的作用下,兩個檔案被建立:分別是 Books 和 Resources,這兩個檔案中有URL的頁面內容。 

發生了什麼事情?

Scrapy為爬蟲的 start_urls屬性中的每個URL建立了一個 scrapy.http.Request 物件 ,並將爬蟲的parse 方法指定為回撥函式。 
這些 Request首先被排程,然後被執行,之後通過parse()方法,scrapy.http.Response 物件被返回,結果也被反饋給爬蟲。

提取Item

選擇器介紹

我們有很多方法從網站中提取資料。Scrapy 使用一種叫做 XPath selectors的機制,它基於 XPath表示式。如果你想了解更多selectors和其他機制你可以查閱資料http://doc.scrapy.org/topics/selectors.html#topics-selectors 
這是一些XPath表示式的例子和他們的含義

  • /html/head/title: 選擇HTML文件<head>元素下面的<title> 標籤。
  • /html/head/title/text(): 選擇前面提到的<title> 元素下面的文字內容
  • //td: 選擇所有 <td> 元素
  • //div[@class="mine"]: 選擇所有包含 class="mine" 屬性的div 標籤元素

這只是幾個使用XPath的簡單例子,但是實際上XPath非常強大。如果你想了解更多XPATH的內容,我們向你推薦這個XPath教程http://www.w3schools.com/XPath/default.asp

為了方便使用XPaths,Scrapy提供XPathSelector 類, 有兩種口味可以選擇, HtmlXPathSelector (HTML資料解析) 和XmlXPathSelector (XML資料解析)。 為了使用他們你必須通過一個 Response 物件對他們進行例項化操作。你會發現Selector物件展示了文件的節點結構。因此,第一個例項化的selector必與根節點或者是整個目錄有關 。 
Selectors 有三種方法

  • path():返回selectors列表, 每一個select表示一個xpath引數表示式選擇的節點.
  • extract():返回一個unicode字串,該字串為XPath選擇器返回的資料
  • re(): 返回unicode字串列表,字串作為引數由正則表示式提取出來

嘗試在shell中使用Selectors

為了演示Selectors的用法,我們將用到 內建的Scrapy shell,這需要系統已經安裝IPython (一個擴充套件python互動環境) 。

要開始shell,首先進入專案頂層目錄,然後輸入

T:\tutorial>scrapy shell http://www.dmoz.org/Computers/Programming/Languages/Python/Books/

輸出結果類似這樣:

複製程式碼
2012-07-16 10:58:13+0800 [scrapy] INFO: Scrapy 0.14.4 started (bot: tutorial)
2012-07-16 10:58:13+0800 [scrapy] DEBUG: Enabled extensions: TelnetConsole, CloseSpider, WebService, CoreStats, SpiderState
2012-07-16 10:58:13+0800 [scrapy] DEBUG: Enabled downloader middlewares: HttpAuthMiddleware, DownloadTimeoutMiddleware, UserAgentMiddleware, RetryMiddleware, DefaultHeadersMiddleware, RedirectMiddleware, CookiesMiddleware, HttpCompressionMiddleware, ChunkedTransferMiddleware, DownloaderStats
2012-07-16 10:58:13+0800 [scrapy] DEBUG: Enabled spider middlewares: HttpErrorMiddleware, OffsiteMiddleware, RefererMiddleware, UrlLengthMiddleware, DepthMiddleware
2012-07-16 10:58:13+0800 [scrapy] DEBUG: Enabled item pipelines:
2012-07-16 10:58:13+0800 [scrapy] DEBUG: Telnet console listening on 0.0.0.0:6023
2012-07-16 10:58:13+0800 [scrapy] DEBUG: Web service listening on 0.0.0.0:6080
2012-07-16 10:58:13+0800 [dmoz] INFO: Spider opened
2012-07-16 10:58:18+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
[s] Available Scrapy objects:
[s]   hxs        <HtmlXPathSelector xpath=None data=u'<html><head><meta http-equiv="Content-Ty'>
[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   <CrawlerSettings module=<module 'tutorial.settings' from 'T:\tutorial\tutorial\settings.pyc'>>
[s]   spider     <DmozSpider 'dmoz' at 0x1f68230>
[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
WARNING: Readline services not available or not loaded.WARNING: Proper color support under MS Windows requires the pyreadline library.
You can find it at:
http://ipython.org/pyreadline.html
Gary's readline needs the ctypes module, from:
http://starship.python.net/crew/theller/ctypes
(Note that ctypes is already part of Python versions 2.5 and newer).

Defaulting color scheme to 'NoColor'Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.

IPython 0.13 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]:
複製程式碼

Shell載入後,你將獲得迴應,這些內容被儲存在本地變數 response 中,所以如果你輸入response.body 你將會看到response的body部分,或者輸入response.headers 來檢視它的 header部分。 
Shell也例項化了兩種selectors,一個是解析HTML的  hxs 變數,一個是解析 XML 的 xxs 變數。我們來看看裡面有什麼:

複製程式碼
In [1]: hxs.path('//title')
Out[1]: [<HtmlXPathSelector xpath='//title' data=u'<title>Open Directory - Computers: Progr'>]

In [2]: hxs.path('//title').extract()
Out[2]: [u'<title>Open Directory - Computers: Programming: Languages: Python: Books</title>']

In [3]: hxs.path('//title/text()')
Out[3]: [<HtmlXPathSelector xpath='//title/text()' data=u'Open Directory - Computers: Programming:'>]

In [4]: hxs.path('//title/text()').extract()
Out[4]: [u'Open Directory - Computers: Programming: Languages: Python: Books']

In [5]: hxs.path('//title/text()').re('(\w+):')
Out[5]: [u'Computers', u'Programming', u'Languages', u'Python']

In [6]:
複製程式碼

提取資料

現在我們嘗試從網頁中提取資料。 
你可以在控制檯輸入 response.body, 檢查原始碼中的 XPaths 是否與預期相同。然而,檢查HTML原始碼是件很枯燥的事情。為了使事情變得簡單,我們使用Firefox的擴充套件外掛Firebug。更多資訊請檢視
txw1958注:事實上我用的是Google Chrome的Inspect Element功能,而且可以提取元素的XPath。
檢查原始碼後,你會發現我們需要的資料在一個 <ul>元素中,而且是第二個<ul>。 
我們可以通過如下命令選擇每個在網站中的 <li> 元素:

hxs.path('//ul/li') 

然後是網站描述:

hxs.path('//ul/li/text()').extract()

網站標題:

hxs.path('//ul/li/a/text()').extract()

網站連結:

hxs.path('//ul/li/a/@href').extract()

如前所述,每個path()呼叫返回一個selectors列表,所以我們可以結合path()去挖掘更深的節點。我們將會用到這些特性,所以:

複製程式碼
sites = hxs.path('//ul/li')
for site in sites:
    title = site.path('a/text()').extract()
    link = site.path('a/@href').extract()
    desc = site.path('text()').extract()
    print title, link, desc
複製程式碼

將程式碼新增到爬蟲中:

txw1958注:程式碼有修改,綠色註釋掉的程式碼為原教程的,你懂的

複製程式碼
from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector

class DmozSpider(BaseSpider):
    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):
        hxs = HtmlXPathSelector(response)
        sites = hxs.path('//fieldset/ul/li')
        #sites = hxs.path('//ul/li')
        for site in sites:
            title = site.path('a/text()').extract()
            link = site.path('a/@href').extract()
            desc = site.path('text()').extract()
            #print title, link, desc
            print title, link
複製程式碼

現在我們再次抓取dmoz.org,你將看到站點在輸出中被列印 ,執行命令

T:\tutorial>scrapy crawl dmoz

使用條目(Item)

Item 物件是自定義的python字典,使用標準字典類似的語法,你可以獲取某個欄位(即之前定義的類的屬性)的值:

>>> item = DmozItem() 
>>> item['title'] = 'Example title' 
>>> item['title'] 
'Example title' 

Spiders希望將其抓取的資料存放到Item物件中。為了返回我們抓取資料,spider的最終程式碼應當是這樣:

複製程式碼
from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector

from tutorial.items import DmozItem

class DmozSpider(BaseSpider):
   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):
       hxs = HtmlXPathSelector(response)
       sites = hxs.path('//fieldset/ul/li')
       #sites = hxs.path('//ul/li')
       items = []
       for site in sites:
           item = DmozItem()
           item['title'] = site.path('a/text()').extract()
           item['link'] = site.path('a/@href').extract()
           item['desc'] = site.path('text()').extract()
           items.append(item)
       return items
複製程式碼

現在我們再次抓取 : 

複製程式碼
2012-07-16 14:52:36+0800 [dmoz] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
        {'desc': [u'\n\t\t\t\n\t',
                  u' \n\t\t\t\n\t\t\t\t\t\n - Free Python books and tutorials.\n \n'],
         'link': [u'http://www.techbooksforfree.com/perlpython.shtml'],
         'title': [u'Free Python books']}
2012-07-16 14:52:36+0800 [dmoz] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
        {'desc': [u'\n\t\t\t\n\t',
                  u' \n\t\t\t\n\t\t\t\t\t\n - Annotated list of free online books on Python scripting language. Topics range from beginner to advanced.\n \n
          '],
         'link': [u'http://www.freetechbooks.com/python-f6.html'],
         'title': [u'FreeTechBooks: Python Scripting Language']}
2012-07-16 14:52:36+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2012-07-16 14:52:36+0800 [dmoz] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/>
        {'desc': [u'\n\t\t\t\n\t',
                  u' \n\t\t\t\n\t\t\t\t\t\n - A directory of free Python and Zope hosting providers, with reviews and ratings.\n \n'],
         'link': [u'http://www.oinko.net/freepython/'],
         'title': [u'Free Python and Zope Hosting Directory']}
2012-07-16 14:52:36+0800 [dmoz] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/>
        {'desc': [u'\n\t\t\t\n\t',
                  u' \n\t\t\t\n\t\t\t\t\t\n - Features Python books, resources, news and articles.\n \n'],
         'link': [u'http://oreilly.com/python/'],
         'title': [u"O'Reilly Python Center"]}
2012-07-16 14:52:36+0800 [dmoz] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/>
        {'desc': [u'\n\t\t\t\n\t',
                  u' \n\t\t\t\n\t\t\t\t\t\n - Resources for reporting bugs, accessing the Python source tree with CVS and taking part in the development of Python.\n\n'],
         'link': [u'http://www.python.org/dev/'],
         'title': [u"Python Developer's Guide"]}
複製程式碼

儲存抓取的資料

儲存資訊的最簡單的方法是通過Feed exports,命令如下:

T:\tutorial>scrapy crawl dmoz -o items.json -t json

所有抓取的items將以JSON格式被儲存在新生成的items.json 檔案中

在像本教程一樣的小型專案中,這些已經足夠。然而,如果你想用抓取的items做更復雜的事情,你可以寫一個 Item Pipeline(條目管道)。因為在專案建立的時候,一個專門用於條目管道的佔位符檔案已經隨著items一起被建立,目錄在tutorial/pipelines.py。如果你只需要存取這些抓取後的items的話,就不需要去實現任何的條目管道。

匯出抓取資料

最簡單的儲存抓取資料的方式是使用json格式的檔案儲存在本地,像下面這樣執行:

1
scrapy crawl huxiu -o items.json

在演示的小系統裡面這種方式足夠了。不過如果你要構建複雜的爬蟲系統, 最好自己編寫Item Pipeline

儲存資料到資料庫

上面我們介紹了可以將抓取的Item匯出為json格式的檔案,不過最常見的做法還是編寫Pipeline將其儲存到資料庫中。我們在coolscrapy/pipelines.py定義

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18