トップ «前の日記(2012-01-20) 最新 次の日記(2012-01-28)» 編集

Public Diary


2012-01-27

[プログラミング] TwitterのツイートをtDiaryにポストするスクリプト

(1/29追記) 手っ取り早くスクリプトをダウンロードしたいという方は、1/29の記事の最後の段落を参照してください。

Twitterは短いツイートを簡単に投稿できる点が便利な反面、あっという間にタイムラインが流れ、あとでまとめて読んだりするのに向いていません。そのため、ブログを運営している人であれば一日分のツイートをまとめてブログに掲載したり、メーリングリストを運営している人であればメーリングリストに転載するなどの工夫が行われてきました。

すでにtw2tdiaryなどのプログラムも公開されていますが、過去可能な限りさかのぼって全部のツイートを日別にまとめてtdiaryに投稿するようなプログラムは見つからなかったので、Rubyの勉強もかねて、自分で一から作ってしまうことにします。

環境 (Disclaimer)

想定する環境は CentOS 6.2 で動作している Ruby 1.8.7 です。

また、私はC系プログラム(C/C++/C#/Java)やレガシーインタープリター(Perl/bash)でのプログラミング経験はありますが、Ruby はまったくの初心者ですので、プログラムの作法がRuby的でなかったり、一部の用語を誤って使っている可能性があります。

(1) ライブラリの準備

まず、一部のライブラリを利用するため、gems と irb をインストールします。

 # yum -y install ruby-irb rubygems

(でもなんでgemsは ruby-gems ではなく rubygems (ハイフンが入らない)なんですかねぇ)

Ruby には Twitter なる便利そうなライブラリがあるので、これをインストールします。

 # gem install twitter

(これまた、なんでパッケージ名は rubygems なのにコマンド名は gem (単数形)なんですかねぇ)

とりあえずこれで必要最低限のライブラリはそろったようです。

(2) 過去のツイートを取得するテスト

で、とりあえず過去のツイートを数件取得するには、こんなプログラムでいいそうです。

require 'rubygems'
require 'twitter'

tester = Twitter.user_timeline('wassy')
tester.each do |t|
  puts "=" * 70
  puts "* created_at: " + t.created_at
  puts "* id: "         + t.id.to_str
  puts t.text
end

なるほど、確かに簡単です。

このTwitterライブラリは、ある程度TwitterのREST APIに忠実に作られているようで、オプションの引数は、user_timeline(SCREEN_NAME, {:count=>COUNT, :max_id=>MAX_ID, ...}) といった形で与えてあげれば良いようです。

ただ、このライブラリのドキュメントを読んでも(初学者には)こういったことはさっぱりわからないわけで、もうちょっとドキュメント周りがしっかりしててくれればなぁと思わないわけでもありません。

(3) 「昨日一日分」のツイートを取得する

(2)で作った簡易プログラムは、過去20件分のツイートだけを取得します。これに、さらにツイートが「昨日」に含まれるかどうかを判定しながら表示する処理を加えます。

Twitterライブラリの時刻の表現は、Timeオブジェクトを用いて行われています。Time オブジェクトはUNIXタイム(UTCでの1970年1月1日0時0分0秒)経過秒なので、ローカルタイムにおける「昨日」を求めるのはちょっとトリッキーです。ツイッターブログで教えていただき、

 today     = Time.local(*(Time.now).to_a[3..5].reverse)
 yesterday = today - 86400

あたりが相当するだろう、ということになりました。(86400は一日あたりの秒数です)

この値を使い、ツイートの created_at が yesterday <= created_at < today を満たすかどうか調べながら、繰り返しツイートを取得します。

require 'rubygems'
require 'twitter'

today     = Time.local(*(Time.now).to_a[3..5].reverse)
yesterday = today - 86400
lasttime  = today

begin
  tester = Twitter.user_timeline('wassy')
  if tester then
    tester.each do |t|
      lasttime = t.created_at
      if yesterday <= t.created_at and t.created_at < today then
        puts "=" * 70
        puts "* created_at: " + t.created_at.to_s
        puts "* id: "         + t.id.to_s
        puts t.text
      end
    end
  else
    lasttime = 0
  end
end while (yesterday <= lasttime)

(4) ツイートを処理する部分

Rubyでツイートを扱うのは結構簡単そうだということがわかったので、今度はツイートをファイルに書き出す処理を考えます。とりあえず当座の処理として、カレントディレクトリに YYYY-mm-dd という形式のファイル名で書き出します。

def write_to_file(day, tweets)
  if tweets then
    filename = day.strftime("%Y-%m-%d.txt")
    fh = open(filename, "w")
    tweets.each do |t|
      fh.puts "=" * 70
      fh.puts "* created_at: " + t.created_at.to_s
      fh.puts "* id: "         + t.id.to_s
      fh.puts t.text
    end
    fh.close
  else
    return 0
  end
end

なんだかRubyのTimeクラスって暗黙にローカルタイムを利用してるのがちょっと気持ち悪いですかねぇ…。あと、fh.puts にカッコを付けようとするとインタープリターに怒られるのですが、結合順位というかオペレーターの優先順位が見た目にはっきりせず、なんだか落ち着きませんねぇ。

(5) 過去数日分のツイートをまとめて取得

というわけで、「昨日」だけでなく、過去数日分をまとめて取得するプログラムにします。

$screen_name = 'wassy'
$max_days    = 10

dayend   = Time.local(*(Time.now).to_a[3..5].reverse)
daystart = dayend - 86400
data = []
cont = true
lastid = 0
days = 0

def write_to_file(day, tweets)
  # 省略
end
begin
  if lastid == 0 then
    tester = Twitter.user_timeline($screen_name)
  else
    tester = Twitter.user_timeline($screen_name, {:max_id => lastid-1})
  end

  if tester then
    tester.each do |t|
      lastid = t.id
      if dayend <= t.created_at then
        # 今日分のツイート: 何もしない
      elsif daystart <= t.created_at then
        # 対象とするツイート
        data << t
      else
        # さらに昔のツイート
        write_to_file(daystart, data)
        days = days + 1
        if $max_days <= 0 or days < $max_days then
          data     = [t]
          dayend   = dayend - 86400
          daystart = daystart - 86400
        else
          data = []
          cont = false
          break
        end
      end
    end
  else
    # ツイート取得エラー
    cont = false
  end
end while (cont)

if data.size > 0 then
  write_to_file(daystart, data)
  days = days + 1
end

puts "Acquired tweets for " + days.to_s . " days."

最初、days = days + 1 の部分を、C系言語風に days++ としてはまってしまいました。Ruby にはインクリメント演算子はないようで、((days).+).+ と解釈され左辺値がないことになってしまうから、だとか。うぅむ。

とりあえず今日はここまで。今日の成果物は、tweet2file_20120127 として置いておきます。明日以降の日記に続きます。


1980|03|
1986|04|
1998|04|
2002|01|11|
2003|03|04|05|07|08|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|02|03|04|06|07|08|11|12|
2008|01|02|03|04|06|07|08|09|10|
2009|01|12|
2011|05|10|11|
2012|01|02|10|