Scrappy入門:百度貼吧圖片爬蟲

博客地址:Scrappy入門:百度貼吧圖片爬蟲html

Scrapy是Python很是有名的爬蟲框架,框架自己已經爲爬蟲性能作了不少優化:多線程、整合xpath和圖片專用管道等等,開發人員只要專一在功能需求上。git

基本Scrapy使用教程參考:初窺ScrapyScrapy入門教程github

學習一種技術或者一個框架最好的方式固然是用它作一些小工程,入門第一步我先選擇了百度貼吧圖片爬蟲,由於既夠簡單又比較實用。數據庫

由於此次涉及到圖片的下載,而Scrapy自己爲此提供了特殊的圖片管道,因此果斷直接用Scrapy的圖片管道來幫助完成。Scrapy中管道的定義以下:數組

當Item在Spider中被收集以後,它將會被傳遞到Item Pipeline,一些組件會按照必定的順序執行對Item的處理。
每一個item pipeline組件(有時稱之爲「Item Pipeline」)是實現了簡單方法的Python類。他們接收到Item並經過它執行一些行爲,同時也決定此Item是否繼續經過pipeline,或是被丟棄而再也不進行處理。多線程

對於管道的典型應用場景以下:app

清理HTML數據
驗證爬取的數據(檢查item包含某些字段)
查重(並丟棄)
將爬取結果保存到數據庫中框架

Scrappy圖片管道的使用教程參考:下載項目圖片異步

使用Scrapy最重要的就是編寫特定的spider類,本文指定的spider類是BaiduTieBaSpider,來看下它的定義:scrapy

import scrapy
import requests
import os
from tutorial.items import TutorialItem
class BaiduTieBaSpider(scrapy.spiders.Spider):
    name = 'baidutieba'
    start_urls = ['http://tieba.baidu.com/p/2235516502?see_lz=1&pn=%d' % i for i in range(1, 38)]
    image_names = {}
    def parse(self, response):
        item = TutorialItem()
        item['image_urls'] = response.xpath("//img[@class='BDE_Image']/@src").extract()
        for index, value in enumerate(item['image_urls']):
            number = self.start_urls.index(response.url) * len(item['image_urls']) + index
            self.image_names[value] = 'full/%04d.jpg' % number
        yield item

這裏要關注Scrappy作的兩件事情:

  • 根據start_urls 中的URL地址訪問頁面並獲得返回

  • parse(self, response)函數就是抓取到頁面以後的解析工做

那麼首先就是start_urls的構造,這裏是觀察了百度貼吧裏的URL規則,其中see_lz=1表示只看樓主,pn=1表示第一頁,根據這些規則得出了一個URL數組。而後再觀察單個頁面的HTML源碼,得出每一個樓層發佈的圖片對應的img標籤的類爲BDE_Image,這樣就能夠得出xpath的表達式:xpath("//img[@class='BDE_Image']/@src"),來提取樓層中全部圖片的src,賦值到item對象的image_urls字段中,當spider返回時,item會進入圖片管道進行處理(即Scrapy會自自動幫你下載圖片)。

對應的item類的編寫和setting.py文件的修改詳見上文的教程。

到這裏下載圖片的基本功能都完成了,可是有個問題:我想要按順序保存圖片怎麼辦?

形成這個問題的關鍵就是Scrapy是多線程抓取頁面的,也就是對於start_urls中地址的抓取都是異步請求,以及item返回以後到圖片管道後對每張圖片的URL也是異步請求,因此是沒法保證每張圖片返回的順序的。

那麼這個問題怎麼解決呢?試了幾種辦法以後,獲得一個相對理想的解決方案就是:製做一個字典,key是圖片地址,value是對應的編號。因此就有了代碼中的image_namesnumber = self.start_urls.index(response.url) * len(item['image_urls']) + index,而後再定製圖片管道,定製的方法詳見上文給出的教程連接,在本文中定製須要作的事情就是重寫file_path函數,代碼以下:

import scrapy
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
from tutorial.spiders.BaiduTieBa_spider import BaiduTieBaSpider

class MyImagesPipeline(ImagesPipeline):

    def file_path(self, request, response=None, info=None):
        image_name = BaiduTieBaSpider.image_names[request.url]
        return image_name

    def get_media_requests(self, item, info):
        for image_url in item['image_urls']:
            yield scrapy.Request(image_url)

    def item_completed(self, results, item, info):
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            raise DropItem("Item contains no images")
        item['image_paths'] = image_paths
        return item

file_path函數就是返回每張圖片保存的路徑,當咱們有一張完整的字典以後,只要根據request的URL去取相應的編號便可。

這個方法顯然是比較消耗內存的,由於若是圖片不少的話,須要維護的字典的條目也會不少,但從已經摺騰過的幾個解決方案(例如不用管道而採用手動阻塞的方式來下載圖片)來看,它的效果是最好的,付出的代價也還算能夠接受。

Scrappy入門第一個小demo就寫到這裏。

相關文章
相關標籤/搜索