初窺Scrapy
Scrapy是一個為了爬取網站數據,提取結構性數據而編寫的應用框架。 可以應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。
還是先推薦幾個學習的教程:Scrapy 0.25文檔 Scrapy快速入門教程 這些教程里面有關於Scrapy的安裝,創建項目,爬取實例等等,如果一個全新的東西扔給你首先要看文檔,初看文檔我也是蒙蒙的,后來一層一層的去摸索才大概懂了個皮毛。我們就試着將之前的爬蟲福利改寫成用Scrapy框架的爬蟲,在實踐中學習。 戰斗吧 Scrapy!
安裝Scrapy
如果配置好了pip或者easy_install 可以直接pip install scrapy (從https://pip.pypa.io/en/latest/installing.html 安裝 pip)
還需要從 http://sourceforge.net/projects/pywin32/ 安裝 pywin32 (注:此處要注意了,這里pywin32的版本要跟你python的完全一致,比如你在64位系統安裝的32位的python2.7 那么你也需要安裝2.7 32位的pywin32) 否則遇到:Scrapy [twisted] CRITICAL:Unhandled error in Deferred
新建項目
因為我們要重寫之前的項目,我們新建一個scrapy項目,命名為rosi: scrapy startproject rosi
可以看到目錄里面包含:
1 rosi/ 2 scrapy.cfg #項目的配置文件 3 rosi/ #該項目的Python模塊,代碼全在這里面 4 __init__.py 5 items.py #放多個model的地方 6 pipelines.py #顧名思義 管道,處理items結果的地方 7 settings.py #配置文件 8 spiders/ #爬蟲代碼 9 __init__.py 10 ...
好了。。。說了這么多廢話,接下來讓我們深入基層!新建rosi項目,然后在rosi/rosi/spiders下面新建rosi_spider.py
import scrapy class RosiSpider(scrapy.spiders.Spider): name = "rosi" #爬蟲名字 唯一 allowed_domains = ["baidu.com"] #白名單 start_urls = ["http://www.baidu.com"] #爬取起始頁面 def parse(self,response):#回調函數 print response.url
上面的代碼就是一直簡單的爬蟲,默認爬取了百度首頁。你如果問我,怎么爬取的,什么原理,怎么會爬取了,我只能這么回答你:我表達不出來,因為我也是剛學現在還一團漿糊,我現在只明白怎么用,至於原理,我想等我用的熟了,需要去更深的應用的時候我就會懂了,如果能看的下去可以去看看源碼。。。不過我可以引用官方文檔中的話來回答你:Scrapy為start_urls屬性中的每個url都創建了一個Request對象,並將parse方法最為回調函數(callback)賦值給了Request。Request對象經過調度,執行生成scrapy.http.Response對象並返回給parse方法。
執行該爬蟲:scrapy crawl rosi

我們既然知道了返回的是response,我們可以試着將里面我們需要的東西匹配讀取保存下來,比如文字,比如圖片。在Scrapy中呢他擁有自己的Selectors。使用了一種基於XPath和css的機制。深入的東西還是看官方文檔:Selector文檔 簡單介紹介紹幾個官方文檔的例子:
/html/head/title: 選擇HTML文檔中<head>標簽內的<title>元素/html/head/title/text(): 選擇上面提到的<title>元素的文字//td: 選擇所有的<td>元素//div[@class="mine"]: 選擇所有具有class="mine"屬性的div元素
Selector有4個基本方法:
xpath(): 傳入xpath表達式,返回該表達式所對應的所有節點的selector list列表 。css(): 傳入CSS表達式,返回該表達式所對應的所有節點的selector list列表.extract(): 序列化該節點為unicode字符串並返回list。re(): 根據傳入的正則表達式對數據進行提取,返回unicode字符串list列表。
這里可以自行嘗試一下利用XPath取出百度首頁的title文字等等等等。
好了,重點來了。Scrapy中的BaseSpider爬蟲類只能抓取start_urls中提供的鏈接,而利用Scrapy提供的crawlSpider類可以很方便的自動解析網頁上符合要求的鏈接,從而達到爬蟲自動抓取的功能。要利用crawSpider和BaseSpider的區別在於crawSpider提供了一組Rule對象列表,這些Rule對象規定了爬蟲抓取鏈接的行為,Rule規定的鏈接才會被抓取,交給相應的callback函數去處理。
在rules中通過SmglLinkExtractor提取希望獲取的鏈接。比如:
1 rules = ( 2 Rule(SgmlLinkExtractor(allow = ('detail_\d{4}_\d{5}\.html')),callback = 'parse_image',follow=True), 3 )
這里要解釋下,Rule就是一組對象列表,在這里我們設置要過濾的地址。SmglLinkExtractor的主要參數:
- allow:滿足括號中“正則表達式”的值會被提取,如果為空,則全部匹配。
- deny:與這個正則表達式(或正則表達式列表)不匹配的URL一定不提取。
- allow_domains:會被提取的鏈接的domains。
- deny_domains:一定不會被提取鏈接的domains。
- restrict_xpaths:使用xpath表達式,和allow共同作用過濾鏈接。
- follow 指定這些通過規則匹配出來的鏈接是否需要繼續,如果callback是None,follow默認為False,否則follow是True。通俗點講呢就是如果設置為false 那么就訪問了這個網站為止不再根據Rule判斷該網址,如果設置為True 則繼續從該網址里面選擇符合Rule的網址出來繼續訪問。(舉個例子:網站有25頁,但是首頁上提供的頁面跳轉的標號只有從1-10 后面的隱藏了,平常我們點開10頁 頁面標號是10-20 如果我們follow為false 我們只能得到1-10頁的url 如果設置為True 則每次得到一頁都去取標號,我們能得到所有的頁碼1-25.說的太亂了,一會兒代碼中說。)
我們嘗試着從首頁得到符合規則的rosi跳轉頁面:
1 import scrapy 2 from scrapy.contrib.spiders import CrawlSpider,Rule 3 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor 4 class RosiSpider(CrawlSpider): 5 name = "rosi" 6 allowed_domains = ["5442.com"] 7 start_urls = ["http://www.5442.com/tag/rosi.html"] 8 rules = (Rule(SgmlLinkExtractor(allow=('rosi/[\d]+\.html', )),callback='parse_href',),) 9 def parse_href(self,response):#注意 回調函數不要命名為parse 否則出bug 10 print response.url
得到的結果如下:

天殺的,明明是1-25頁好不好,怎么只有這么幾個,上面說了如果不設置follow的話默認為false,所以訪問了這個就不繼續了,我們設置為True就對了。
我們還是要分析一下這個流程。我們從起始頁面:http://www.5442.com/tag/rosi.html 我們需要得到符合條件為tag/rosi/[0-9]+/.html的所有頁面,然后訪問這些頁面得到所有圖片集的地址如:http://www.5442.com/meinv/20150904/27062.html和http://www.5442.com/meinv/20150904/27062_2.html,分析可得[0-9_]+\.html。這樣我們就得到了所有包含我們需要下載圖片url的地址,我們就可以根據XPath得到圖片url進行下載。所以我們的爬蟲Rule是這樣的:
1 import scrapy,re,urllib2 2 from scrapy.http import Request 3 from scrapy.contrib.spiders import CrawlSpider,Rule 4 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor 5 from scrapy.selector import Selector 6 from scrapydemo.items import * 7 8 class RosiSpider(CrawlSpider): 9 name = "rosi" 10 number = 0 11 allowed_domains = ["5442.com"] 12 start_urls = ["http://www.5442.com/tag/rosi.html"] 13 rules = (Rule(SgmlLinkExtractor(allow=('rosi/[\d]+\.html', )),follow=True), 14 Rule(SgmlLinkExtractor(allow=('[0-9_]+\.html', )),callback='parse_img',follow=True) 15 )
第一條Rule我們得到了rosi的所有頁面的信息,在訪問這些頁面的時候我們並不需要進行處理,所以我們不需要回調函數,然后我們從這些頁面信息中提取出了所有的圖片集頁面,然后我們將圖片集頁面的返回值response給回調函數進行處理:
1 def parse_img(self,response): 2 #print response.url 3 sel = Selector(response) 4 src = sel.xpath("//div[@class='arcBody']//p[@id='contents']//a//img/@src").extract() 5 for item in src: 6 self.saveimg(item) 7 8 def saveimg(self,url): 9 savePath = '%d.jpg'%(self.number) 10 print url 11 self.number += 1 12 try: 13 u = urllib2.urlopen(url) 14 r = u.read() 15 downloadFile = open(savePath,'wb') 16 downloadFile.write(r) 17 u.close() 18 downloadFile.close() 19 except: 20 print savePath,'can not download.'
可能我們要問了,這就完了? items.py 和 pipeline.py咋沒用上呢。那就來談談這兩個:
Items
爬取的主要目標就是從非結構性的數據源提取結構性數據,例如網頁。 Scrapy提供 Item類來滿足這樣的需求。Item 對象是種簡單的容器,保存了爬取到得數據。 其提供了 類似於詞典(dictionary-like) 的API以及用於聲明可用字段的簡單語法。
1 import scrapy 2 3 class Product(scrapy.Item): 4 name = scrapy.Field() 5 price = scrapy.Field() 6 stock = scrapy.Field()
他就是一個model,我們可以在回調函數中通過XPath得到內容 然后新建一個Item對象,賦值給他,
1 def parse_href(self,response): 2 items = [] 3 item = Product() 4 item["name"] = "xxx" 5 item["price"] = "xxx" 6 items.append(item) 7 return items
注意,這里我們返回了一個items!!!當Item在Spider中被收集之后,它將會被傳遞到Item Pipeline,一些組件會按照一定的順序執行對Item的處理。每個item pipeline組件(有時稱之為“Item Pipeline”)是實現了簡單方法的Python類。他們接收到Item並通過它執行一些行為,同時也決定此Item是否繼續通過pipeline,或是被丟棄而不再進行處理。
以下是item pipeline的一些典型應用:
- 清理HTML數據
- 驗證爬取的數據(檢查item包含某些字段)
- 查重(並丟棄)
- 將爬取結果保存到數據庫中
我們可以在pipelines.py中編寫自己的itempipeline方法。你必須實現process_item(self,item,spider)方法。更多內容 看官方文檔。。。
讓我們來看一下以下這個假設的pipeline,它為那些不含稅(price_excludes_vat 屬性)的item調整了price 屬性,同時丟棄了那些沒有價格的item:
1 from scrapy.exceptions import DropItem 2 3 class PricePipeline(object): 4 5 vat_factor = 1.15 6 7 def process_item(self, item, spider): 8 if item['price']: 9 if item['price_excludes_vat']: 10 item['price'] = item['price'] * self.vat_factor 11 return item 12 else: 13 raise DropItem("Missing price in %s" % item)
以下pipeline將所有(從所有spider中)爬取到的item,存儲到一個獨立地 items.jl 文件,每行包含一個序列化為JSON格式的item:
1 import json 2 3 class JsonWriterPipeline(object): 4 5 def __init__(self): 6 self.file = open('items.jl', 'wb') 7 8 def process_item(self, item, spider): 9 line = json.dumps(dict(item)) + "\n" 10 self.file.write(line) 11 return item
好了,今天就到這兒吧。。。其實我現在也蒙蒙的,接下來就是在實際應用中去提升了,畢竟熟能生巧!!戰斗吧 Scrapy!
