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

jopen 9年前發布 | 34K 次閱讀 Scrappy 網絡爬蟲

 

Scrapy 是Python非常有名的爬蟲框架,框架本身已經為爬蟲性能做了很多優化:多線程、整合xpath和圖片專用管道等等,開發人員只要專注在功能需求上。

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

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

因為這次涉及到圖片的下載,而Scrapy本身為此提供了特殊的圖片管道,所以果斷直接用Scrapy的圖片管道來幫助完成。Scrapy中管道的定義如下:

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

對于管道的典型應用場景如下:

清理HTML數據

驗證爬取的數據(檢查item包含某些字段)

查重(并丟棄)

將爬取結果保存到數據庫中

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

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

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("http://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("http://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就寫到這里。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!