TwitterAPIからXMLを取得してMakeYourDayを動かせるようにするまで
03月18日修正:写ツの発言を取得するように修正しました。
03月23日追記:写ツのその日一個目の発言がうまく投稿されません。メール送信までは正常なので、はてなグループの問題?
05月31日追記:日本語を文字コードの参照で投稿しているため(はてなグループ内で)うまく検索できません。時間があれば改善します。アドバイスがあればよろしくお願いします。
2009-07-20修正:上の問題を解決しました。また、LoudTwitterが停止したようです。twitter2Blog 何ができるの? つぶやきをブログへ - twitter2Blog を つくるぞーを参照ください。
いつのまにかTwitterのHTMLからタイムスタンプがなくなっていたのでMakeYourDayスクリプトが動かなくなりました。APIしかないなあという感じなので修正。個人のログ収集ならAPI制限も問題ないでしょう。
以下は個人的なメモです。修正版は最後に載せました。ちゃんと動くかはわかりません。
惰性で修正しているだけなので単にログを取りたいならLoudTwitterでも使うのがよいです。→twitter2Blog(試験中)
さくらインターネット
関係ないですが、さくらインターネットでTelnetが使えなくなったのでサポートの内容に従ってSSHを試してみました。だいたい同じですね。
スクリプトをcronで動かす方法についてはさくらインターネットのCRON設定を可能な限り簡単に解説したいがわかりやすいです。具体的にはコマンド「/usr/local/bin/ruby /home/アカウント/www/スクリプト.rb」を「3」時「0」分に実行します。
TwitterAPI
今回は自分のログを収集することが目的なので「user_timeline」APIを使います。
「http://twitter.com/statuses/user_timeline/アカウント.xml?page=ページ数」を辿っていきます。
参考:Twitter API 仕様書 (日本語) [最新版]
以下のようなXMLが返ってきます。必要であればReply先の情報なども取得できますね。
<statuses type="array"> - <status> <created_at>Sun Mar 16 10:10:10 +0000 2009</created_at> <id>0000000</id> <text>こんにちは。</text> </status> </statuses>
BASIC認証
TwitterAPIの利用にはBASIC認証が必要です。ID(メールアドレス)&パスワードとのことですが、メールアドレスでなくTwitterIDでも通るような?
よくわかりませんが、以下のようにXMLを(正規表現を使うので)文字列として取得しました。リクエストのあたりとか、これでいいのかなあ。
参考:Rubyist Magazine - 標準添付ライブラリ紹介 【第 7 回】 net/http
req = Net::HTTP::Get.new('/statuses/user_timeline/'+id+'.xml?page='+p.to_s) req.basic_auth("kiwofusi", "パスワード><") xml = Net::HTTP.start("twitter.com").request(req) body = xml.body
データの取得
本来ならXMLをパースするのが自然でしょうが、ソースをあまりいじらくて済むためパターンマッチを使いました。「
日時の取り方はもっとスマートにできそう。日時に関するオブジェクトの扱いに慣れず戸惑った。UNIX時間をいろんなかたちで表現できるもの、と考えるとちょっとわかる。
参考:正規表現 - Rubyリファレンスマニュアル、Time - Rubyリファレンスマニュアル
その他
XMLをパースするならRubyを使ってTwitterにBASIC認証をしてAPIを利用する :: Webプログラマー+WebデザイナーなZARU日記が参考になります。
いろいろAPIをいじるならOn the Rails Twitter API にアクセスできる Ruby ライブラリ: Twitter4Rを導入すると便利かも。
修正版ソース
2009-07-20版。
問題:116文字を超える発言の投稿がおかしくなる。はてなグループ側の問題? 手作業で修正できるレベル。数値文字参照のデコードによって解決されたかも。
問題2:写ツの発言「ほげほげ http://f.hatena〜」を取得できない。過去のMakeYourDayでも同様?改行を含む発言に正規表現で対応しました。ただし3行(改行2箇所)以上の取得は未対応です。
require 'net/smtp' require 'net/http' require 'nkf' require 'time' require 'kconv' # 原作 id:worris2 # http://d.hatena.ne.jp/worris2/20081012/1223828088 # 改変 id:kiwofusi # http://d.hatena.ne.jp/kiwofusi/20090316/1237147695 # 更新履歴 # 2009-07-20 数値文字参照のデコードに対応 # 2009-03-18 写ツの投稿に対応 # 2009-03-16 公開 # 参考 # Rubyで数値文字参照をUTF-8にする - くふんを狙え # http://d.hatena.ne.jp/eclipse-a/20070905/1189001865 mailserver="さくらID.sakura.ne.jp" maildomain="さくらID.sakura.ne.jp" mailid="さくらメールID@さくらID.sakura.ne.jp" mailpass="さくらメールIDパスワード" mail="さくらメールID@さくらID.sakura.ne.jp" backupmail="バックアップ送信用メールアドレス" hatenad={"はてなID" => "はてなダイアリー投稿用アドレス"} #"@d.hatena.ne.jp"と".g.hatena.ne.jp"は省く twitter={"はてなID" => "ツイッターID"} twittertitle={"はてなID" => "はてなダイアリー投稿時のタイトル"} #"[ライフログ]今日のツイッター"とか t=Time.now-60*60*24 $yesterday=Time::mktime(t.year,t.month,t.day,3,0,0) #午前3時から現在までのログを投稿 ### スクレイピング def fetch(p,id) rt=0 begin req = Net::HTTP::Get.new('/statuses/user_timeline/'+id+'.xml?page='+p.to_s) req.basic_auth("【ID・メールアドレス】", "【パスワード】") xml = Net::HTTP.start("twitter.com").request(req) body = xml.body rescue Exception => err sleep 10 rt+=1 retry if rt<3 $stderr.print err,":",id,"\n" $yesterday else $stderr.print "404:That page doesn't exist!:",id,"\n" if /That page doesn't exist!/=~body body.split(/<\/status>/).each{|line| if match = /<created_at>\w+ (\w+) (\d\d) (\d\d):(\d\d):\d\d \+\d\d\d\d (\d\d\d\d)<\/created_at>\s*<id>(\d+)<\/id>\s*<text>(.*\s*.*)<\/text>/.match(line) $comment << match[7].gsub(/&#(\d+?);/) { [$1.to_i].pack('U') } # 数値文字参照をutf-8にデコードする $link << match[6] $date << Time::mktime(match[5].to_i,Time.parse(match[1]).month.to_i,match[2].to_i,match[3].to_i,match[4].to_i)+60*60*9 # 60*60*9→日本時間に換算 end } $date[-1] ? $date[-1] : $yesterday end end ### メイン hatenad.each{|user,mailto| next if mailto.to_s=="" || twitter[user].to_s=="" $mailout="" page=1 $last="" $comment=[] $link=[] $date=[] while page<11 && fetch(page,twitter[user])>$yesterday #11ページから先のpostは破棄 page+=1 end $comment.each_with_index{|c,i| mailbody="" if $date[i]>$yesterday mailbody << "[http://twitter.com/"+twitter[user]+"/statuses/"+$link[i]+":title="+("0"+$date[i].hour.to_s).slice(-2,2)+":"+("0"+$date[i].min.to_s).slice(-2,2)+"] " mailbody << c.gsub(/<a href="([^\/][^"]+)"[^>]*>[^<]*<\/a>/,'\1').gsub(/<[^>]*>/,'').gsub(">",">").gsub("<","<").gsub(""",'"').gsub("&","&") mailbody << "\n" end $mailout=mailbody+$mailout } ### メール送信 if $mailout!="" retry_count=0 begin address=mailto+'@d.hatena.ne.jp' address=mailto+'.g.hatena.ne.jp' if mailto=~/@/ Net::SMTP.start( mailserver, 25, maildomain, mailid, mailpass, :login ) {|smtp| smtp.send_mail <<EOM, mail, address, backupmail From: #{mail} To: #{address} Subject: =?iso-2022-jp?B?#{NKF.nkf('-EjMB',twittertitle[user])}?= Content-Transfer-Encoding: 7bit Content-Type: Text/Plain; charset=iso-2022-jp #{NKF.nkf('-Wj',$mailout)} EOM } #↑はてなダイアリー投稿時のタイトルがEUCなので'-EjMB' rescue => err $stderr.print user,":",address,":",err,"\n" retry_count+=1 if retry_count<2 sleep 10 retry end end end }