HTML 解析

UI 変更があって以来 API 経由だと Twitter のリアルタイム性が失われてしまったので、以前ちょこっと作っていた Twitter にログインしてホームの HTML を解析して json を起こすプログラムを手直ししてみた。
久々に触ったら hpricot の API がさっぱりになっていて焦った・・・search を押さえとけば今後はなんとかなるかな。

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[1]
    form["session[username_or_email]"] = @user_name
    form["session[password]"] = @password
    page = @agent.submit(form)
    body = page.root.get_elements_by_tag_name("body")[0]
    if body && body[:class] == "account"
      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
    table = nil
    0.upto(RETRY_MAX){|i|
      puts "request try #{i}" if $DEBUG
      page  = @agent.get('http://twitter.com/home')
      table = page.root.get_element_by_id("timeline")
      break if table
    }
    raise OverLimitError.new until table
    list = []
    table.search("tr.hentry"){|tr|
      next unless tr.elem?
      screen_name = $1 if tr.find_element("a")[:href] =~ /.*\/(.*)$/
      name = tr.find_element("img")[:alt]
      img  = tr.find_element("img")[:src]
      text = tr.find_element("span").innerHTML.strip
      text = text.gsub(/@<a .*?>(.*?)<\/a>/ , "@\\1")
      text = text.gsub(/<a href="(.*?)".*?>.*?<\/a>/ , "\\1")
      date = tr.at("span.published")[:title]
      id   = tr[:id].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

次はこれを呼び出すサーバアプリ

#ruby -Ku
require 'webrick'
require 'rubygems'
require 'kconv'
require 'twitter_json'

def main
  server = WEBrick::HTTPServer.new({
    :Port => 3000,
    :BindAddress => '0.0.0.0',
  })
  server.mount('/', Bookcious)
  ['INT', 'TERM'].each do |sig|
    Signal.trap(sig) { server.shutdown }
  end
  server.start
end

class Bookcious < WEBrick::HTTPServlet::AbstractServlet
  include WEBrick::HTMLUtils
  def do_GET(req, res)
    do_POST(req,res)
  end
  def do_POST(req, res)
    res['Content-Type'] = 'text/plain; charset=utf-8'
    @@twitter_json ||= TwitterJSON.new("id","password")
    res.body = @@twitter_json.json.toutf8
  end
end
main

後は Twitter クライアントからサーバプログラムにアクセスするようにすれば OK。必要最低限のデータしか取ってないのは (ry
快適になったヽ(*´∀`)ノ キャッホーイ!!