博客地址:Scrappy入門:百度貼吧圖片爬蟲html
Scrapy是Python很是有名的爬蟲框架,框架自己已經爲爬蟲性能作了不少優化:多線程、整合xpath和圖片專用管道等等,開發人員只要專一在功能需求上。git
基本Scrapy使用教程參考:初窺Scrapy和Scrapy入門教程。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_names
和number = 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就寫到這裏。