basyura's blog

あしたになったらほんきだす。

mechanize でスクレイピングして json 形式にする

0.9.0 から nokogiri に変わってたらしく、以前書いていたものが動かなくなってた。
書き直してみたんだけどリファレンス引くのが大変・・・。メソッド呼び出しした結果でどのクラスのインスタンスが返ってくるかとか、それはどのメソッドがあるのかとか。Ruby が大クラス主義である一因かもなぁと思ったり。これが Java みたいにうじゃうじゃクラスがあったら大変だ。
リファレンスを引くのは当たり前としても、もうちょっと便利にならないかなぁと。eclipse 中毒乙か。

require 'rubygems'
require 'mechanize'
require 'json/lexer'

class TwitterJSON
  # error
  class LoginError < StandardError ; end
  class OverLimitError < StandardError ; end
  #
  RETRY_MAX = 5
  # constructor
  def initialize(user_name , password , option = {})
    option[:max_history] ||= 1
    @user_name = user_name
    @password  = password
    @logined   = false
    @agent = WWW::Mechanize.new
    @agent.max_history = option[:max_history].to_i
  end
  # get json
  def json
    login unless @logined
    parse
  end
  #
  # private methods
  #
  private
  def login
    start = Time.now
    puts "login ... start" if $DEBUG
    page = @agent.get('http://twitter.com')
    form = page.forms[0]
    form["session[username_or_email]"] = @user_name
    form["session[password]"] = @password
    page = @agent.submit(form)
    if page.root.xpath('.//body').attr("id") == "home"
      puts "login ... end (" + (Time.now - start).to_s + ")" if $DEBUG
      @logined = true
    else
      raise LoginError.new
    end
  end
  def parse
    start = Time.now
    puts "parse ... start" if $DEBUG
    ol = nil
    0.upto(RETRY_MAX){|i|
      puts "request try #{i}" if $DEBUG
      page  = @agent.get('http://twitter.com/')
      ol = page.root.xpath(".//ol")[0]
      break if ol
    }
    raise OverLimitError.new unless ol
    list = []
    ol.xpath(".//li").each{|li|
      next unless li.elem?
      screen_name = li.xpath(".//a[@class='screen-name']")[0].inner_text
      name = li.xpath(".//img")[0].attribute("alt").value
      img  = li.xpath(".//img")[0].attribute("src").value
      text = li.xpath(".//span[@class='entry-content']")[0].inner_html.strip
      text = text.gsub(/@<a .*?>(.*?)<\/a>/ , "@\\1")
      text = text.gsub(/<a href="(.*?)".*?>.*?<\/a>/ , "\\1")
      date = li.xpath(".//span[@class='published']")[0].inner_text
      id   = li.attribute("id").value.split("_")[1]

      user = {"name" => name , "screen_name" => screen_name , "profile_image_url" => img}
      list << {"user" => user , "text" => text , "created_at" => date , "id" => id}
    }
    puts "parse ... end (" + (Time.now - start).to_s + ")" if $DEBUG
    list.to_json
  end
end

使う時は

twitter = TwitterJSON.new("id","password")
puts twitter.json