自學Python十二 戰斗吧Scrapy!


  初窺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的主要參數:

  1. allow:滿足括號中“正則表達式”的值會被提取,如果為空,則全部匹配。
  2. deny:與這個正則表達式(或正則表達式列表)不匹配的URL一定不提取。
  3. allow_domains:會被提取的鏈接的domains。
  4. deny_domains:一定不會被提取鏈接的domains。
  5. restrict_xpaths:使用xpath表達式,和allow共同作用過濾鏈接。
  6. 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!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM