twitter で 60 日以上発言が無い人を remove する ワンライナー を作る

twitter で 60 日以上発言が無い人を remove する - Djangoへの片思い日記
僕自身はFollowはまだ200人弱で、全然制限に引っかからないので困らないのだけど、面白そうなので自分でも書いてみる。


まずはAPIの選定。
http://watcher.moe-nifty.com/memo/docs/twitterAPI.txt
自分がフォローしているユーザーを拾うにはこれしかなさそう。

  friends
     自分の friend の一覧を(各 friend の最新ステータス付きで)取得する
     引数 id を指定すれば、その id のユーザの friend の一覧を取得できる
     ただし、この API で取得できるデータは最大100件(100人分)である

     URL: http://twitter.com/statuses/friends.format
       (format は xml, json のうちのいずれかを指定)

100件以上の場合はpage指定して分けるしかない。

        page=ページ番号 (オプション)
          (1ページを100件とみなしたときの)ページ番号を指定することで、指定ユーザの friend の一覧を100件単位で取得する

             例:
               http://twitter.com/statuses/friends.xml?page=2
                 API実行時点で101件目から200件目に相当する(自分の)friend の一覧を XML 形式で取得する


とりあえず試しに取ってみる。

$ perl -MLWP::Simple -le 'getprint "http://twitter.com/statuses/friends.json?id=sugyan"'
[{"following":false,"verified":false,"description":"...

たくさん出てきた。BASIC認証は必要ないらしい。これを使ってワンライナーで色々やってみよう。


まず、何度も色々ためしているとあっという間にAPI制限に引っかかってしまうので、一度データをローカルに保存してそれを使う。

$ wget "http://twitter.com/statuses/friends.json?id=sugyan"
$ mv friends.json\?id=sugyan friends.json
$ ls -l friends.json
-rw-r--r--  1 sugyan  staff  134252  6 29 22:11 friends.json


というわけで、まずはJSONの解析。JSONモジュールを使う。

$ perl -MJSON -le 'print from_json <>' < friends.json
ARRAY(0x8104c8)
$ perl -MJSON -le 'print scalar @{from_json <>}' < friends.json
100

ちゃんと100件のデータが詰まっているらしい。

$ perl -MJSON -le 'print for @{from_json <>}' < friends.json
HASH(0x8192d8)
HASH(0x810738)
HASH(0x84a920)
...

中身はハッシュのリファレンス。とりあえず1件だけ取り出して中身を見てみよう。Data::Dumperで覗いてみる。

$ perl -MJSON -MData::Dumper -le 'print Dumper shift @{from_json <>}' < friends.json
$VAR1 = {
          'friends_count' => 91,
          'profile_background_tile' => bless( do{\(my $o = 0)}, 'JSON::XS::Boolean' ),
          'status' => {
                        'source' => '<a href="http://d.hatena.ne.jp/Kiri_Feather/20071121">Tween</a>',
                        'favorited' => $VAR1->{'profile_background_tile'},
                        'truncated' => $VAR1->{'profile_background_tile'},
                        'created_at' => 'Mon Jun 29 10:04:43 +0000 2009',
...

情報大杉。。。ハッシュのキーだけに絞ろう。

perl -MJSON -le 'print for sort keys %{shift @{from_json <>}}' < friends.json
created_at
description
favourites_count
followers_count
following
friends_count
id
location
name
notifications
profile_background_color
profile_background_image_url
profile_background_tile
profile_image_url
profile_link_color
profile_sidebar_border_color
profile_sidebar_fill_color
profile_text_color
protected
screen_name
status
statuses_count
time_zone
url
utc_offset
verified

全部のrefを取ってみるとわかるけど、"status"に対応するvalueはさらにハッシュになっている。

$ perl -MJSON -le 'print for sort keys %{shift(@{from_json <>})->{status}}' < friends.json
created_at
favorited
id
in_reply_to_screen_name
in_reply_to_status_id
in_reply_to_user_id
source
text
truncated


色々調べてみるとわかるけど、

$ perl -MJSON -le 'print shift(@{from_json <>})->{screen_name}' < friends.json
daiki_kameya
$ perl -MJSON -le 'print shift(@{from_json <>})->{status}->{created_at}' < friends.json
Mon Jun 29 10:04:43 +0000 2009

というカンジで、自分がフォローしているヒトのidと最終POST日時を知ることができる。
取得した100件全員の最終POST日時を一覧表示したければ、

$ perl -MJSON -le 'print "$_->{status}->{created_at}: $_->{screen_name}" for @{from_json <>}' < friends.json
Mon Jun 29 10:04:43 +0000 2009: daiki_kameya
Mon Jun 29 00:06:52 +0000 2009: sakaik
Mon Jun 29 13:06:54 +0000 2009: tsuka
Mon Jun 29 12:13:29 +0000 2009: shohu33
Mon Jun 29 10:39:43 +0000 2009: haru860
Mon Jun 29 13:03:32 +0000 2009: tenja
Mon Jun 29 11:45:47 +0000 2009: dameninngenn
Mon Jun 29 13:07:59 +0000 2009: ksorano
Mon Jun 29 01:50:56 +0000 2009: omega2
Sun Jun 28 21:26:31 +0000 2009: gunjisatoshi
...

とやることができる。


さて、しかしこの"created_at"、この形式の文字列だとどうにも困る。
Date::Parseモジュールを使うとepoch秒に置き換えることができる。

$ perl -MJSON -MDate::Parse -le 'print str2time($_->{status}->{created_at}) for @{from_json <>}' < friends.json
1246269883
1246234012
1246280814
1246277609
1246271983
...

ということは、取得した100件のfriendsを最新POST順に並び替えることもできるわけで。

$ perl -MJSON -MDate::Parse -le 'print join ": ", @$_ for sort { $b->[0] <=> $a->[0] } map [str2time($_->{status}->{created_at}), $_->{screen_name}], @{from_json <>}' < friends.json
1246281071: furuhouse
1246280896: Yoshikazu
1246280879: ksorano
1246280872: voluntas
1246280836: tsuyoshikawa
1246280815: mdaisuke
1246280814: tsuka
1246280752: jugyo
1246280727: otenki_bot
1246280717: kekoyana
...

ちょっと長いけどシュワルツ変換を使って最新POST日時順に並べ替えて表示してみた。
http://perl-mongers.org/2008/05/schwartzian_transform_and_orcish_maneuver.html


…というわけで、「60日以上発言が無い人」というのは上記のようなsortをする必要も無く、ただgrepでフィルタリングすれば良いだけ。
例えば最近24時間でPOSTしていないヒトを列挙するのであれば

$ perl -MJSON -MDate::Parse -le 'print $_->{screen_name} for grep { str2time($_->{status}->{created_at}) < time - 60*60*24 } @{from_json <>}' < friends.json 
yapcasia
KAZUTO1967
katsumic
willpon
kazukichop
...

というカンジでできる。60*60*24*60と数字を変えれば「60日以上発言が無い人」というフィルタリングが可能。


さて、ここまで出来たところで、ページングにチャレンジ。
実際にAPIからデータを取得して上記までのことをやろうとすると、LWP::Simpleモジュールを使用して以下のようになる。

$ perl -MJSON -MDate::Parse -MLWP::Simple -le 'print $_->{screen_name} for grep { str2time($_->{status}->{created_at}) < time - 60*60*24 } @{from_json get "http://twitter.com/statuses/friends.json?id=sugyan"}' 

ここに、終了条件を加えてwhileループを回してやりたい。
終了する条件は、取得されたデータが0件になること。イメージとしてはこんなカンジ。

$ perl -MJSON -MLWP::Simple -le 'sleep(60), print $n while @{from_json get "http://twitter.com/statuses/friends.json?id=sugyan&page=".$n++}'


ということでwhile文を回しつつ60日以上発言が無い人を列挙していく。
while文を使っているとprint部分でforを使えないのでmapで代用する。

$ perl -MJSON -MDate::Parse -MLWP::Simple -le 'map { sleep(60); print $_->{screen_name} } grep { str2time($_->{status}->{created_at}) < time - 60*60*24*60 } @a while (@a=@{from_json get "http://twitter.com/statuses/friends.json?id=sugyan&page=".++$n})'

完成!!


…って、ここまで書いてようやく気付いたけど、これだとBASIC認証してないから非公開のユーザーについては判定できないや。
もしかすると60日以上前まで公開していてそこから非公開にしたユーザーが含まれてしまうかも。
ちゃんとBASIC認証してタイムラインを取得するには…

$ perl -MJSON -MDate::Parse -MLWP::UserAgent -le 'map { sleep(60); print $_->{screen_name} } grep { str2time($_->{status}->{created_at}) < time - 60*60*24*60 } @a while (@a=@{($r=HTTP::Request->new(GET=>"http://twitter.com/statuses/friends.json?id=sugyan&page=".++$n))->authorization_basic(@ARGV), from_json(LWP::UserAgent->new->request($r)->content)})' sugyan ********

こうか!?


あとは出力を使ってNet::Twitterとかでremoveすればいいと思います。もう疲れたw