一個事件驅動的圖片爬蟲

weixin_34357887發表於2017-12-14

起因

  1. 無聊的時候會翻出去看看國外的漫畫,然而一頁一頁載入總是會很慢,偶爾還需要多重新整理幾次才能顯示出來,非常影響體驗。於是就寫了個指令碼去抓某一個漫畫下所有的圖片,這樣跑一遍指令碼,就能在本地看圖片了。

  2. 為了偷懶,第一個版本用的單執行緒模型,幾百張圖片序列請求,真的慢。

  3. 實際工作中一直沒什麼機會用到非同步IO,正好拿來練練手。

分析

併發的下載圖片,有多執行緒和事件驅動兩套方案。

多執行緒的實現方式,例如一部漫畫有300張圖,我不可能開300個Thread,系統受不了。比較實際的做法是使用一個容量為N的ThreadPool,那麼,同時就只能發出N個請求,然後所有執行緒Block等待,其實效率也不高

然而事件驅動的方式就不一樣了,我可以一口氣把所有請求發出去,當有請求完成時,就呼叫事先定義的回撥Handle,實現了300張圖片的並行下載。

先上圖看看效果

從圖中就可以看出,所有的請求都發出去之後,才陸續有響應結果亂序到達。這就是典型的非同步IO的情景。

基於EventMachine的非同步圖片爬蟲

EventMachine是ruby社群知名的事件驅動庫,類似於Netty、NodeJS

通過 EM.run{}就可以開始一個事件迴圈

以下是關鍵程式碼

  #img_info = [{file_name: '1.jpg', url:'xxx'}...]

  def getImg(img_info)
    EM.run{ #開啟事件迴圈
      multi = EventMachine::MultiRequest.new #request容器
      @img_info_copy = img_info.dup
      img_info.each do |info|
        file_name = File.join(@dir, info[:file_name])
        if FileTest::exist?(file_name)
          @img_info_copy.delete(info)
          puts "#{file_name} skip".blue
          next
        end
        puts "#{file_name} start".green
        req = EventMachine::HttpRequest.new(info[:url]).get #建立request
        multi.add "#{file_name}",req
        req.callback { #成功回撥
          File.open(file_name, 'w') { |file| file.write(req.response) }
          @img_info_copy.delete(info)
          puts "#{file_name} done".green
        }
        req.errback { #失敗回撥
          puts "#{file_name} fail".red
        }
      end
      multi.callback do #所有request都完成後的回撥
        if @img_info_copy.size == 0 #如果沒有圖片下載失敗
          EM.stop
        else #遞迴呼叫,重新下載的圖片
          puts "Total fails: #{@img_info_copy.size}, solving...".red
          getImg @img_info_copy.dup
        end
      end
    }
  end
複製程式碼

遇到的小坑

EM.run {}之後,主執行緒就block了,所有寫在它後面的程式碼都不執行
複製程式碼

效果

通過這次的優化,下載一部兩三百頁漫畫的時間從之前單執行緒版本的二十多分鐘,變成了現在的兩分鐘左右!

相關文章