BitArts Blog

ロードバイク通勤のRubyプログラマで伊豆ダイバー。の個人的なブログ。

Rubyでクローラー

リンクだけじゃなく、フォーム、イメージ、フレームまでがっつり収集してくれるクローラーが欲しかったんだけどwgetではできないようなので自作することにした。

フォームのフィールドを集めたりするの、ちょっと大変そうだな。。と思ったんだけど、WWW::Mechanizeというライブラリを使ったら超簡単だった。ビバMechanize!

require "rubygems"
require "mechanize"

class CrawlerListener
  def notify_begin
  end
  def pre_request
  end
  def notify_response(result)
    puts %Q{#{result[:method]} #{result[:uri]} #{result[:query] ? result[:query].inspect : ""}}
  end
  def post_request
  end
  def notify_end
  end
end

class WebCrawler

  PRODUCT = "BitArts Crawler 1.0"

  attr_accessor :listener, :results, :excludes, :username, :password

  def initialize(listener=CrawlerListener.new)
    @listener = listener
    @excludes = /\.(jpg|png|gif|js|css|ico)$/i
  end

private

  def create_agent
    agent = WWW::Mechanize.new
    @user_agent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; #{PRODUCT})"
    agent.user_agent = @user_agent
    agent.auth(username, password)
    agent
  end

  def http_request(method, uri, query={})
    agent = create_agent
    begin
      if method == "GET"
        page = agent.get(uri, query)
      elsif method == "POST"
        page = agent.post(uri, query)
      end
      agent.page.encoding = "UTF-8"
      page
    rescue
      nil
    end
  end

  def get_links(page)
    links = []
    (page.links + page.meta + page.frames + page.iframes).each {|link|
      begin
        if link.uri
          links << page.uri + link.uri
        end
      rescue URI::InvalidURIError
      end
    }
    imgsrcs = page.root.search("img").map{|e| e["src"]}
    imgsrcs.each {|uri|
      links << page.uri + uri
    }
    links
  end

  def child_uri?(uri)
    uri.to_s.index(@root_uri.gsub(/\/[^\/]*?$/, "/")) == 0
  end

  def crawl_r(uri, referer=nil, query=nil, method="GET")
    return false unless child_uri?(uri)
    result = {}
    return false if uri.to_s =~ @excludes
    @listener.pre_request
    page = http_request(method, uri, query)
    @listener.post_request
    result = {
      :method => method,
      :uri => uri,
      :query => query,
      :referer => referer,
      :user_agent => @user_agent
    }
    if page
      result.update({
        :code => page.code,
        :body => page.body,
        :header => page.response
      })
    end
    @listener.notify_response(result)
    @results << result
    if page.is_a?(WWW::Mechanize::Page)
      links = get_links(page)
      links.each {|u|
        unless @uris.has_key?(u.to_s)
          @uris[u.to_s] = true
          crawl_r(u, uri)
        end
      }
      page.forms.each {|f|
        u = page.uri + f.action
        k = u.to_s + "?" + f.request_data
        unless @uris.has_key?(k)
          @uris[k] = true
          crawl_r(u, uri, f.build_query, f.method)
        end
      }
    end
    true
  end

public

  def crawl(uri)
    @uris = {}
    @results = []
    @root_uri = uri
    @listener.notify_begin
    crawl_r(uri.is_a?(URI) ? uri : URI.parse(uri))
    @listener.notify_end
    true
  end

end

crawler = WebCrawler.new
crawler.crawl("https://bitarts.jp/")