忍者ブログ
a pot of tea,
プログラミング勉強中。備忘録や、つくったものの紹介を書いています。openFrameworks(ofxiPhone)/Perl/PHP/as3(flash,AIR)などなど。
Admin / Write
2024/03/19 (Tue) 18:12
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

2011/04/19 (Tue) 00:56
第7回。
先日つくったリズムシさんbotが暴走したので(たびたびすみません...)、
あらためてコードを練り直しました。
よりよい(と思われる)ものができたので、のっけます。
いやあ、前のコード、だっさださですね...
違いがわかるくらいには前進したということで、あえて前のも残しておきます。

変更点は、
・リプライ返し・フォロー返しのプログラムを大幅に変更
・アンフォローの機能を追加
です。

以下、あたらしいソースコードです。
変更前はこちら








#!/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
Name
Title
Mail
URL
Comment
Pass   Vodafone絵文字 i-mode絵文字 Ezweb絵文字
添削(?)
すぎゃーん URL 2011/10/02(Sun)16:36:51 編集
既に知っているかもしれませんが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環境でのロギングのベストプラクティスはちょっと分かりませんが。。。
Re:
ar_tama URL 2011/10/05(Wed)15:01:45 編集
すぎゃんさん

さっそくのツッコミ、ありがとうございます!
あの後うっかり寝込んでしまい、お返事が遅くなり申し訳ありません。

モジュールまわりやモダンな書式・作法などに関してはまったく疎いので、とても勉強になります。ありがとうございます!
挙げていただいた4つのモジュール、取り入れてやってみます。
成果はまた新しいエントリにて報告いたしますね!

ログについても、URLを挙げていただきありがとうございます。
テキストファイルに書き出し、はすぐに実装できそうなのでやってみます!
ありがとうございました!
followers_ids()
通りすがり 2012/02/22(Wed)03:40:31 編集
上記ソースコードで負荷が重いのことですが。
followers()の代わりにfollowers_ids()を使えばかなり軽くできると思います。
followers_ids()は数字しか取らないのでサーバーとの通信も少ないはずです。

ではでは
この記事へのトラックバック
この記事にトラックバックする:
  HOME   30  29  28  25  24  23  22  21  20  19  18 
最新コメント
[02/22 通りすがり]
[10/05 ar_tama]
[10/02 すぎゃーん]
[10/02 すぎゃーん]
最新トラックバック
プロフィール
twitter
follow me!
@ar_tama


ar_tama / Makoto Arata
東京のはしっこでプログラミング、ときどきコンピュータ音楽。
音大生。
にゃんこともちもちが好き。
詳細

contact:arata.makoto(at)gmail.com
(at)->@
バーコード
ブログ内検索
__
忍者ブログ [PR]