Twitter のアーカイブを取得するツールをリファクタしてみた

Twitterのアーカイブを取得するツール作りました。No Use API版 - Seasons.NETスクリプト
OptionParser や scrapi の勉強がてらにリファクタしてみた。
リファクタといっても僕なりに読みやすいように書き換えただけなので
そんなの全然リファクタリングになってないよ!っていう厳しいツッコミや
ここはこうした方がいいんじゃね?っていうやさしい指摘など、歓迎します。

元のスクリプトとの違い

大まかなものをあげるとこんな感じ。

  • スクリプト中にユーザ名やパスを書くのは嫌だったので別途「.twitter-user-pass.rb」を読み込むようにしている
    • スクリプトを貼るときに誤って自分のを晒す危険をなくしたかった。それくらいの意味しかないかも
  • スクレイピングするページ数の指定を「-p」オプションでする
  • 「-l」オプションで結果を保存するファイルを指定できる
  • 「-b」オプションで「-k」で指定したキーワードを [] (大括弧)で囲んだものとして扱う
  • 標準出力にも結果を出力する

その他の細かい違いは割愛。

使い方

以下のような「.twitter-user-pass」を用意しておいて

USERNAME = 'your_username'
PASSWORD = 'your_password'
ruby twitterarchivefilter.rb -k vim -p 10

を実行する。
すると、自分の最近の発言「10」ページ分から、キーワード「vim」が付いているものを拾ってくる。

スクリプト

UTF-8」で保存してね!

twitterarchivefilter.rb
#!/usr/bin/env ruby -Ku
$KCODE = "u"

require 'rubygems'
require 'scrapi'
require 'pp'
require 'net/http'
require 'kconv'
require 'optparse'
require '.twitter-user-pass'

$stdout.sync = true

BASEPATH    = '/account/archive'
logfilename = 'archive.log'

class TwitterArchiveFilter
  def initialize (keyword, pagenum, use_brace)
    @items = []
    @pagenum = pagenum
    @http = Net::HTTP.new('twitter.com', 80)
    @keyword_reg = use_brace ? /\[#{keyword}\]/i : /#{keyword}/i
  end

  def filter
    getArchives()
  end

  def dump(filename)
    open(filename, "w") do |f|
      @items.each do |msg, time|
        f.puts "#{time} : #{msg}"
        puts "#{time} : #{msg}".tosjis
      end
    end
  end

  private
  def addItems(items)
    return unless items
    @items.concat items[:messages].zip(items[:times])
  end

  def getArchives
    count = 0
    nextlink = BASEPATH
    while(nextlink)
      break if count == @pagenum
      html = getPageArchive(nextlink)
      addItems getItems(html)
      nextlink = getNextLink(html)
      puts "GetPage [#{count += 1}]"
    end
    @items = @items.reject{|msg, time| msg !~ @keyword_reg } if @keyword_reg
  end

  def getItems(html)
    items = Scraper.define do
      process 'td.content>span.entry-title', "messages[]" => :text
      process 'td.content>span.meta>a>abbr.published', "times[]" => "@title"
      result :messages, :times
    end.scrape(html, :parser_options => {:char_encoding => 'utf8'})
    items
  end

  def getNextLink(html)
    link = Scraper.define do
      process 'div.pagination>a', :url => "@href", :kind => :text
      result :url, :kind
    end.scrape(html, :parser_options => {:char_encoding => 'utf8'})
    link[:kind] =~ /Older/ ? link[:url] : nil
  end

  def getPageArchive(page)
    req = Net::HTTP::Get.new(page)
    req.basic_auth(USERNAME , PASSWORD)
    res = @http.request(req)
    res ? res.body : ""
  end
end

if $0 == __FILE__
  keyword   = nil
  pagenum   = -1
  use_brace = false
  opt       = OptionParser.new
  
  opt.banner = "\nUsage: #{$0} -k KEYWORD -s STOPOLDERPAGE\n   ex) #{$0} -k vim -p 10"
  opt.on('-k', '--keyword KEYWORD', String) {|k| keyword = k }
  opt.on('-p', '--pagenum PAGENUM', Integer) {|p| pagenum = p if p > 0 }
  opt.on('-l', '--logfile LOGFILE', String) {|l| logfilename = l }
  opt.on('-b', '--brace') {|b| use_brace = true if keyword }
  def opt.error(msg = nil)
    $stderr.puts msg if msg
    $stderr.puts help()
    exit 1
  end
  
  begin
    opt.parse!
  rescue OptionParser::ParseError => err
    opt.error err.message
  end
  puts "Keyword => " + (keyword ? use_brace ? "[#{keyword}]" : keyword : "")
  puts "PageNum => #{pagenum}"
  puts "LogFile => #{logfilename}"
  taf = TwitterArchiveFilter.new(keyword, pagenum, use_brace)
  taf.filter()
  taf.dump(logfilename)
  puts "Succeed Twitter Archive!!"
end

追記

バッファリングの遅延に対処するために puts を再定義していた部分を
「$stdout.sync = true」に置き換えた。