2011/04/19 (Tue) 00:56
第7回。
先日つくったリズムシさんbotが暴走したので(たびたびすみません...)、
あらためてコードを練り直しました。
よりよい(と思われる)ものができたので、のっけます。
いやあ、前のコード、だっさださですね...
違いがわかるくらいには前進したということで、あえて前のも残しておきます。
変更点は、
・リプライ返し・フォロー返しのプログラムを大幅に変更
・アンフォローの機能を追加
です。
以下、あたらしいソースコードです。
変更前はこちら。
Tweet
先日つくったリズムシさんbotが暴走したので(たびたびすみません...)、
あらためてコードを練り直しました。
よりよい(と思われる)ものができたので、のっけます。
いやあ、前のコード、だっさださですね...
違いがわかるくらいには前進したということで、あえて前のも残しておきます。
変更点は、
・リプライ返し・フォロー返しのプログラムを大幅に変更
・アンフォローの機能を追加
です。
以下、あたらしいソースコードです。
変更前はこちら。
Tweet
#!/usr/bin/perl use warnings; use strict; use CGI::Carp qw( fatalsToBrowser ); # モジュールのパスを追加 use lib qw(パス); # Net::Twitter::Lite、Data::Dumperモジュールを使用 use Net::Twitter::Lite; use Data::Dumper; # utf8エンコードを使用 use utf8; use Encode; use JSON 'decode_json'; binmode( STDOUT, ":utf8" ); # HTML出力 print "Content-Type: text/html\n\n"; # twitter認証 my %CONSUMER_TOKENS = ( consumer_key => 'コンシューマトークンのキー', consumer_secret => 'コンシューマトークンのパスワード'); my $ACCESS_TOKEN = 'アクセストークンのキー'; my $ACCESS_TOKEN_SECRET = 'アクセストークンのパスワード'; # 呼び出し、トークンセット my $tweet = Net::Twitter::Lite->new(%CONSUMER_TOKENS); $tweet->access_token($ACCESS_TOKEN); $tweet->access_token_secret($ACCESS_TOKEN_SECRET); ###################### ## 定時ツイート ## # localtime()関数は、秒、分、時、日にち、月、年、曜日の配列になっている # ※年は1900をプラス、月は1をプラスして使う (my $sec, my $min, my $hour, my $mday, my $mon, my $year, my $wday) = localtime(); # $min=分が10分未満のとき=毎時間実行 if( $min < 10 ) { my $rnd = int(rand(2)); $rnd = 0; if( $rnd == 0 ) { my $kotobaResult; # サブルーチン"pattGenarate"を実行 $kotobaResult = pattGenerate(); $tweet->update( $kotobaResult ); } } sub pattGenerate{ # できたことばを返す return $result; } ###################### ## リプライ返し ## # 未リプライのものを探す # カウントオプションで自分のツイート最新100件を取得 my $timelines = $tweet->user_timeline({ count => 100 }); my @myReplies = (); foreach my $status ( @$timelines ) { # 曜日、月、日付、時間、タイムゾーン、年の順 # 今回は日付だけ欲しいので、$much[2]を使う my @much = split(" ", $status->{ created_at }); # 検索範囲を前日からに設定 my $muchDate = $mday - 1; while ($muchDate < $mday + 1 ){ # 日付がマッチ、かつundef(未定義)=リプライじゃないものを除いて格納 if( $muchDate == $much[2] && $status->{ in_reply_to_status_id }){ push @myReplies, $status->{ in_reply_to_status_id }; } $muchDate ++; } } # 取得した100件のツイートのうち、他人へのリプライの数 my $replyCount = @myReplies; # その数+10件自分へのリプライを取得 my $getSize = $replyCount + 10; my $mentions = $tweet->mentions({ count => $getSize } ); my @replies = (); foreach my $status ( @$mentions ) { my @much = split(" ", $status->{ created_at }); my $muchDate = $mday - 1; while ($muchDate < $mday + 1 ){ if( $muchDate == $much[2] ){ push @replies, $status->{ id }; } $muchDate ++; } } # 差をもとめる my @intersection = my @difference = (); my %count = (); # $count{$element} はハッシュ%countのキー$elementに対応するvalueをあらわす foreach my $element ( @replies, @myReplies ) { $count{$element} ++; } # カウントが1以上(=共通項があった) -> @intersectionに代入 # 1 -> @differenceに代入 foreach my $element (keys %count) { push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element; } print "diff : ", $#difference + 1, " intersection : ", $#intersection + 1, "<br />\n"; # リプライの内容の配列 my @replyStatus = ("リプライの内容"); # @differenceの中身を調べる foreach my $id ( @difference ) { # 発言者のユーザ名を取得 my $name = $tweet->show_status( $id )->{ user }{ screen_name }; my $rand = int(rand(整数)); # $rand番目にあたる文字列を取得 my $s = $replyStatus[$rand]; $s = join('', "@",$name, " ", $s); # 投稿 $tweet->update({ status => $s, in_reply_to_status_id => $id }); print $s, " ", $id, "<br />\n"; } ###################### ## リフォロー ## # カーソル(-1にしておく) my $cursor = -1; my $followCount = 0; FOLLOW_LOOP : while($cursor != 0) { # 引数にカーソルを my $followers = $tweet->followers({ cursor => $cursor }); # 次のカーソルを取得(100件あとのIDが返ってくる?) $cursor = $followers->{ next_cursor }; foreach my $follower (@{$followers->{ users }}) { # こちらもすでにフォローしていた場合 if($follower->{following}) { # 人数をカウント $followCount ++; print "following : ", $follower->{ screen_name }, "<br />"; } # フォローしていない、かつ鍵つきでない場合 elsif($follower->{protected} =~ /0/) { $followCount = 0; # 失敗するかもしれないのでeval{}; eval{$tweet->create_friend($ follower->{ screen_name })}; if($@) { print "error : ", $@; } else { my $s; # @ユーザ名 発言 の形に $s = join('', "@", $follower->{ screen_name }, " リプライ内容"); print "follow! : ", $follower->{ screen_name }, "tweet : ",$s, "\n<br />"; } } # 既に10人連続でフォローしていた=フォローするユーザはいない とみなして、ループを抜ける last FOLLOW_LOOP if($followCount >= 10); } } ###################### ## アンフォロー ## if( $mday % 2 == 0 && $hour == 0 ) { my $cursor = -1; my @followers = my @friends = (); # フォロワー取得(鍵つきは除外) while ( $cursor != 0 ) { my $fos = $tweet->followers({ cursor => $cursor }); $cursor = $fos->{ next_cursor }; foreach my $fo ( @{ $fos ->{ users } } ) { if( $fo->{ protected } =~ /0/ ) { push @followers, $fo->{ screen_name }; } } } # フレンド取得(鍵つきは除外) $cursor = -1; while ( $cursor != 0 ) { my $fis = $tweet->friends({ cursor => $cursor }); $cursor = $fis->{ next_cursor }; foreach my $fi ( @{ $fis -> { users } } ) { if( $fi->{ protected } =~ /0/ ) { push @friends, $fi->{ screen_name }; } } } # 差をもとめる my @intersection = my @difference = (); my %count = (); # $count{$element} はハッシュ%countのキー$elementに対応するvalueをあらわす foreach my $element (@followers, @friends) { $count{$element} ++; } # カウントが1以上(=共通項があった) -> @intersectionに代入 # 1 -> @differenceに代入 foreach my $element (keys %count) { push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element; } my $foLen = @followers; my $fiLen = @friends; print "follower : ", $foLen, " friends : ", $fiLen, " diff : ", $#difference + 1, "<br />"; my $removeCount = 0; foreach my $removes ( @difference ) { # フォロワー数 < フォロー数 のときだけ実行 if( $foLen < $fiLen ) { eval { $tweet->destroy_friend( $removes ) }; if ($@) { print "error! : ", $@, "<br />"; } else { print "unfollow ", $removeCount, " : ", $removes, "<br />"; $removeCount ++; } } if( $removeCount > 40 ) { last; } } } exit; __END__
以上です。
リプライ返しですが、in_reply_to_status_idとかを取ってなんとかファイル書き込みなしでできないかなあ...
と思ってやってみました。
自分のつぶやきを100件までしか取っていない / 検索範囲が前日と当日のみなのは、
10分おきに更新しているプログラムだからです。
自分へのリプライは、それ+10件しか取っていませんので、それ以上のリプライが一気に飛んできたら反応できません。
ここは、負荷と更新頻度によって変えればいいかなと思ってます。
リフォローのコードは、よそのブログさまのコードを参考にさせていただいたのですが、
うっかりアドレスを控えそこねてしまったので、見つけしだいリンクします。
リプライ返し、アンフォローの差分を求める部分は
- 配列 | 2つの配列の差、もしくは共通する要素数を調べる / perl講座[Smart]
こちらを参考にしました。
大きな違いは、カーソルオプションを使っているところ。
前回のものを書いたとき、カーソルについて全く理解していなかったのでした。
今の仕様では、カーソルを用いないと最新100件しか取ってこないんですね。
pageオプションのようなものと捉えればいいのかな。
カーソルをprintすると、ユーザのIDっぽいのが吐かれます。
100件後のIDが返ってくるのかな?
すべて参照し終えると、0が返ってくるしくみなので、
としています。FOLLOW_LOOP : while($cursor != 0) {
負荷軽減のため、リフォローの部分では10人連続でフォローしている相手だった場合、
ループを抜けるようにしてあります。
アンフォローのほうは、一度配列にすべてのユーザを格納したうえで、比較をしています。
そのぶん負荷がかかるので、頻度は2日に1回に設定。
ちなみに、この差分を求めるやりかたは、「配列内に重複がない状態」が前提です。
PR
Comment
添削(?)
既に知っているかもしれませんがconsumer_key, access_tokenなどをソースコードに直書きせずに済むようにするためにConfig::Pitというモジュールがあります。
http://perl-users.jp/modules/config_pit.html
cgi環境では設定yamlファイルをローカルで用意して転送して使うことになるかと思いますが 作ったソースコードを気軽にそのまま貼付けられるようになるので便利です。
"foreach"という書き方はあまり見ないですね。まったく同じ意味なので短く書ける"for"をよく使います。
for文の中で$muchDateを定義してwhileループを回す必要は無さそうですね。forループを始める前に一回定義してしまえば、ループ内で$much[2]と比較するだけで済むんじゃないでしょうか。
ただし月の始め、1日だと$mday - 1は0になってしまい、前月の末日とはマッチしないので修正が必要な気がします。
mentionsとreply済みの集合差分を取る部分は自前でやっても良いけどList::CompareやSet::Arrayのようなモジュールを使った集合演算をしてみるのも良いかも?
最近のモダンな作法としてはdieし得る処理を行う場合evalで囲むのではなく use Try::Tiny; してtry { .. } catch { ... }; するのが一般的ですね。
とりあえず気になったところはこれくらいで。
あとは問題が起こったときに原因解明しやすくするためにログをしっかり残しておくようにしたいですね。
「いつ」「コードの どの箇所で」「どういう理由で(どんなパラメータによって」起こったのかが分かるようになっていると運用や問題解決に役立ちます。
http://d.hatena.ne.jp/sfujiwara/20110603/1307121730
cgi環境でのロギングのベストプラクティスはちょっと分かりませんが。。。
http://perl-users.jp/modules/config_pit.html
cgi環境では設定yamlファイルをローカルで用意して転送して使うことになるかと思いますが 作ったソースコードを気軽にそのまま貼付けられるようになるので便利です。
"foreach"という書き方はあまり見ないですね。まったく同じ意味なので短く書ける"for"をよく使います。
for文の中で$muchDateを定義してwhileループを回す必要は無さそうですね。forループを始める前に一回定義してしまえば、ループ内で$much[2]と比較するだけで済むんじゃないでしょうか。
ただし月の始め、1日だと$mday - 1は0になってしまい、前月の末日とはマッチしないので修正が必要な気がします。
mentionsとreply済みの集合差分を取る部分は自前でやっても良いけどList::CompareやSet::Arrayのようなモジュールを使った集合演算をしてみるのも良いかも?
最近のモダンな作法としてはdieし得る処理を行う場合evalで囲むのではなく use Try::Tiny; してtry { .. } catch { ... }; するのが一般的ですね。
とりあえず気になったところはこれくらいで。
あとは問題が起こったときに原因解明しやすくするためにログをしっかり残しておくようにしたいですね。
「いつ」「コードの どの箇所で」「どういう理由で(どんなパラメータによって」起こったのかが分かるようになっていると運用や問題解決に役立ちます。
http://d.hatena.ne.jp/sfujiwara/20110603/1307121730
cgi環境でのロギングのベストプラクティスはちょっと分かりませんが。。。
カテゴリー
最新記事
(12/07)
(12/06)
(10/17)
(08/09)
(07/18)
最新トラックバック
プロフィール
follow me!
@ar_tama
ar_tama / Makoto Arata
東京のはしっこでプログラミング、ときどきコンピュータ音楽。
音大生。
にゃんこともちもちが好き。
詳細
contact:arata.makoto(at)gmail.com
(at)->@
ブログ内検索
__
リンク