クローラー

同様のものを以前につくったが遅すぎて(30分とか)常用できなかった。ファイル保存の機能もなかった。今回のものは速度が50倍ぐらいにはなったのではなかろうか。

#! /usr/bin/env ruby
#-*-coding:utf-8-*-
require 'term/ansicolor'
class String
  include Term::ANSIColor
end

class MyJobAnisoku
  def initialize(args = { })
    require 'rubygems'
    require 'kconv'
    require 'mechanize'
    require 'net/http'
    @a = args
    @a[:url] ||= 'http://youtubeanisoku1.blog106.fc2.com/'
    @a[:url] = URI.parse @a[:url] unless @a[:url].class == URI::HTTP
    @agent = Mechanize.new
    @a[:status] ||= :new
    raise "job have no machine error"  unless @a[:machine]
  end

  #最初のとっかかり更新状況を取ってきて個別の紹介ページのjobにする
  def tokkakari
    print "NEW"
    @agent.get @a[:url]
    links_kousins = @agent.page.links_with(:text => /#{"更新状況".toutf8}/)
    targs = []
    links_kousins.each do |link|
      targs << link.uri
    end
    targs.each_with_index do |link,i|
      break if i > 6
      job = MyJobAnisoku.new(
        :url => link,
        :status => :second,
        :machine => @a[:machine]
        )
      @a[:machine].retry job
    end
  end

  #更新情報のページにアクセスして個別の紹介ページのjobにする
  def second
    print "phase2".yellow
    @agent.get @a[:url]
    links_kousin =  @agent.page/"/html/body/table/tr[2]/td/table/tr/td[2]/div[4]/ul/li/a/@href"
    # links_kobetuには個別のページが入っている
    links_kobetu = []
    links_kousin.each do |link|
      links_kobetu << $1  if link.value =~ /(http:\/\/youtubeanisoku.*)/
    end

    #links_kobetuのについてそれぞれjobをつくって突っ込む
    links_kobetu.each do |link|
      job = MyJobAnisoku.new(
        :url => link,
        :status => :kobetu,
        :machine => @a[:machine]
        )
      @a[:machine].retry job
    end
  end

  #say-moveにアクセスしてvideourlを取得してvideoのjobにする
  def third
    print "say-move".green
    #smにはtitleとurlが入っている
    sm = { :title => @a[:title],:url => @a[:url]}
    @agent.get(sm[:url])
    set =  @agent.page/"/html/body/div/div[2]/div[7]/div[2]/input/@value"
    if set[0]
      sm[:videourl] = set[0].value 
    end

    job = MyJobAnisoku.new(
      :url => sm[:videourl],
      :title => sm[:title],
      :status => :video,
      :machine => @a[:machine]
      )
     @a[:machine].retry job
  end

  #個別のページにアクセスしてsay-moveのjobにする
  def kobetu
    print "phase3".yellow
    @agent.get @a[:url]
    nodeset = @agent.page/"/html/body/table/tr[2]/td/table/tr/td[2]/div[4]/div[2]"
    begin
    titles  = nodeset[0].inner_text.
            gsub(' ','').
            gsub(' ','').
            gsub('「Daum」|','').
            gsub('【Yahoo!】','').
            gsub('veoh','').
            gsub('SM|','').
            gsub('|','').
            split("\r\n").
            select{|e| $1 if e=~/(.*)/ }.
            map{|k| k =~ /((\d{1,2}).*)/; { :episode => $2, :title => $1} }
    titles.reverse!

    _tt = @agent.page.title.gsub(' ★ You Tube アニ速 ★','')
    #取得件数調整ハードコーディング
    title  = _tt + titles.shift[:title].to_s
    title2 = _tt + titles.shift[:title].to_s
    title3 = _tt + titles.shift[:title].to_s
    title4 = _tt + titles.shift[:title].to_s
    title5 = _tt + titles.shift[:title].to_s
          
    rescue => ex
      p ex
      return
    end
    nodeset_vs =  @agent.page/"/html/body/table/tr[2]/td/table/tr/td[2]/div[4]/div[2]/a/@href"
    _dd = []
    nodeset_vs.each do |va|
      _dd << $1  if va.value =~ /(http:\/\/say-move\.org\/comeplay\.php.*)/
    end
    _dd.reverse!

    #取得件数調整ハードコーディング
    url  = _dd.shift
    url2 = _dd.shift
    url3 = _dd.shift
    url4 = _dd.shift
    url5 = _dd.shift

    #取得件数調整ハードコーディング
    unless url.nil?
      job = MyJobAnisoku.new(
        :url => url,
        :title => title,
        :status => :third,
        :machine => @a[:machine]
        )
      @a[:machine].retry job
    end
    
    unless url2.nil?
      job = MyJobAnisoku.new(
        :url => url2,
        :title => title2,
        :status => :third,
        :machine => @a[:machine]
        )
      @a[:machine].retry job
    end

    unless url3.nil?
      job = MyJobAnisoku.new(
        :url => url3,
        :title => title3,
        :status => :third,
        :machine => @a[:machine]
        )
      @a[:machine].retry job
    end

    unless url4.nil?
      job = MyJobAnisoku.new(
        :url => url4,
        :title => title4,
        :status => :third,
        :machine => @a[:machine]
        )
      @a[:machine].retry job
    end

    unless url5.nil?
      job = MyJobAnisoku.new(
        :url => url5,
        :title => title5,
        :status => :third,
        :machine => @a[:machine]
        )
      @a[:machine].retry job
    end
    
  end

  #videoを取得して保存する
  def video
    savedir = @a[:machine].savedir
    Dir.chdir savedir
    filename = "#{@a[:title]}.mp4"
    savepath = "#{savedir}/#{filename}"

    puts "Fetching".green.bold + savepath

    if File.exist?(savepath) && File.size(savepath) > 1024 * 1024 * 3
      puts "File Already Saved".yellow.bold
      return
    end

    #省メモッリ
    command = "curl -# -L -R -o '#{filename}' '#{@a[:url].host}#{@a[:url].path}'"
    system command
=begin
メモリ食い過ぎ。2G超とかwwww
    begin
    @http = EventMachine::Protocols::HttpClient.request(
      :host => @a[:url].host,
      :port => @a[:url].port,
      :request => @a[:url].path
    )
    rescue => ex
      p ex
      return
    end
    
    @http.callback {|response|
      if response[:status] == 200
        puts "#200".green
        open(savepath,"wb") do |io|
          io.write response[:content]
        end
      elsif response[:status] == 302
        puts "302".red.bold
        location = ""
        response[:headers].each do |elem|
          p elem
          location = $1 if elem =~ /Location:\s(.*)/
        end
        job = MyJobAnisoku.new(
          :url => location,
          :title => @a[:title],
          :status => :video,
          :machine => @a[:machine]
          )
        @a[:machine].retry job
      else 
        puts response[:status].to_s.red.bold
        puts response[:headers].to_s.red.bold
#        raise "HTTP Status Error"
      end
    }
=end
  end
  
  def run
    #スレッドをつくってMechanizeを並行実行
    t = Thread.new do
      case @a[:status]
      when :new then
        tokkakari
      when :second then
        second
      when :kobetu then
        kobetu
      when :third then
        third
      when :video then
        video
      end
    end
  end
  
end

# machine
module MyMachine
  attr_accessor :queue

  def initialize
    require 'thread'
    @queue = Queue.new
  end
  
  def setup
    setupjobs
    setupmachine
  end

  def go
    puts "You need write the go method!"
  end

  def retry(job)
    @queue.push job
  end

  private

  def setupjobs
    puts "You need write the setupjobs method!"
  end
  
  def setupmachine
    puts "You need write the setupmachine method!"
  end

end


# for This Work
class MyMachineAnisoku
  include MyMachine
  attr_accessor :savedir

  def initialize(args={ })
    super()
    args[:savedir] ||= "/Users/seijiro/Desktop/video"
    @savedir = args[:savedir]
    begin
      Dir::mkdir(@savedir, 0777)
    rescue => ex
      warn ex
    end
    require 'rubygems'
    require 'eventmachine'
    # @gaman is weight for controll EventMachine finish
    @gaman = 0
  end
  
  def go
    EM.run do
      EM.add_periodic_timer(0) do
        EM.stop if should_stop_machine?
        @queue.pop.run unless @queue.empty?
      end
    end
    puts "End of fetch".green.bold
  end

  private

  # EventMachine用に再定義
  def setupjobs
    ajob = MyJobAnisoku.new(
      :machine => self
      )
    @queue.push ajob
  end

  # Machineは終了すべきか?
  def should_stop_machine?
    @gaman += 1 if @queue.empty?
    print "gaman:#{@gaman}/job:#{@queue.size}"
    return @queue.empty? && @gaman > 500
  end
end

####################################################
machine = MyMachineAnisoku.new(:savedir => "/Users/seijiro/Desktop/video") #<=modify!
machine.setup
machine.go

__END__
これは何:
  ここ数週間の動画を取得するスクリプトです
使い方:
  1.スクリプトを保存してchmod +x
  2.savedir:を変更
  2.スクリプト実行 
コメント:  
XPathつかってみた。
Firebugで右クリック=>Xpathをコピー=>Emacsに貼りつけとかいままでなんでやってこなかったのかと。瞬殺ですね。
cronで毎朝6時とかににキックしとけば良いと思います。
かなりな速度でvideoのurlに到達できます。スレッドの効果です。
100M超えないのでメモリに優しいです。EMの効果です。
50%超えないのでCPUにもやさしいです。EMの効果です。
queueの件数が最大500に満たないので特にコネクション制限とかは掛けません。
Socket数/Fileハンドル数超過とかは起きないと思います。