[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
リプライに反応できるTwitter botの作り方 | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

4月で計画停電終了か、というニュースが流れていますので「Twitterで計画停電のグループ検索BOT作りました 」で作ったbotが今後活躍することはないような気がしていますが、せっかくなのでリプライに反応して情報を返すようなTwitter botの作り方を書いてみます。

基本的な作りは、今までにこのブログの中でまとめたPHPプログラムを使いまわしているのが大部分なんですが。

似たような動きをするのに@recipetter とかがありますね。(実際の動作はどうなっているかわかりませんが・・・)



botを動かすプログラム群


@teiden_kensaku のbotは4つのプログラムから成り立っています。

図にするとこんな感じ。


A Day In The Boy's Life-Twitter-BOTA Day In The Boy's Life-Twitter-BOT


それぞれ役割を分担させ、1つの処理が他の処理のボトルネックにならないようにしています。

東京電力のHPで公開されている計画停電のグループの情報を取得するプログラム(group_get.php)、Twitterユーザーが@teiden_kensaku宛に飛ばしたリプライの情報を取得するプログラム(twitter_get.php)、取得したリプライから住所を読み取り該当のグループ情報をユーザーに返すプログラム(twitter_post.php)、そしてこれらのプログラムが正常に動作しているかを監視するプログラム(check_deamon.php)です。


以下、各プログラムの詳細。



計画停電のグループ情報を取得するプログラム


東京電力のHP上に、計画停電のグループ情報が掲載されたExcelファイルが公開されています。

このプログラムでは、このExcelファイルからデータを読み取ってデータベースに情報を格納するという処理をしています。


Excelファイルの取り込み処理は、下記のような感じ。


- Excelファイルを取り込み保存する処理


$prefecture = array(array('prefix' => 'tochigi',   'name' => '栃木県'),
                    array('prefix' => 'ibaraki',   'name' => '茨城県'),
                    array('prefix' => 'gunma',     'name' => '群馬県'),
                    array('prefix' => 'chiba',     'name' => '千葉県'),
                    array('prefix' => 'kanagawa',  'name' => '神奈川県'),
                    array('prefix' => 'tokyo',     'name' => '東京都'),
                    array('prefix' => 'saitama',   'name' => '埼玉県'),
                    array('prefix' => 'yamanashi', 'name' => '山梨県'),
                    array('prefix' => 'numazu',    'name' => '静岡県'));
$pre_cnt = count($prefecture);

for ($i = 0; $i < $pre_cnt; $i++) {

    $hp = NULL;

    $file_name = $prefecture[$i]['prefix'] . ".xls";

    $url = "http://www.tepco.co.jp/images/$file_name";
    $hp = file_get_contents($url);

    $fp = fopen("./tmp/$file_name", "w");
    fwrite($fp, $hp);
    fclose($fp);

    sleep(10);
}


以前は、HPトップから各県のグループ情報のExcelがリンクされていたんですが、4月8日現在でなくなってしまっています。(検索システムが公開されているので、そっち使えって事かもしれませんけど)

ただし、まだ上記のリンクは活きていてExcelファイルは定期的に更新されているようです。


ファイルの取得は、file_get_contents 関数を使っていますが、エラーチェックを厳密にしていませんので、したければ$http_response_header とか使って確認した方がよいでしょう。

cURL関数 使っても出来ると思いますが)

このプログラム自体は、Cronにセットしており6時間に1度実行するようにしています。

Excelファイルが1日に1度ぐらいしか更新されていませんし、あまり頻度を高めるとHPへ負荷をかけてしまって問題にもなるので。


PHPでExcelファイルからデータを抜き取る方法ですが、PHPExcel を使いました。


- PHPでExcelファイルからデータを読み取る処理
$objReader = PHPExcel_IOFactory::createReader('Excel5');
$objPHPExcel->setActiveSheetIndex(0);
$worksheet = $objPHPExcel->getActiveSheet();

$list = array();

// Excel内の全行でループ
foreach ($worksheet->getRowIterator() as $row) {
// 最初の3行はグループデータじゃない
    if (($rnum = $row->getRowIndex()) < 4) {
// 一行目にデータの更新日時があるので、そのデータを取得
        if ($rnum === 1) {
            $data = array();
            foreach ($row->getCellIterator() as $cell) {
                if (!is_null($cell)) {
                    $data[] = $cell->getValue();
                }
            }

            for ($j = 0; $j < count($data); $j++) {
                if (!is_null($cell)) {
// Excel内にある更新日時のデータを取得
                       if (preg_match("/^平成[0-9]{2}年[0-9]{1,2}月[0-9]{1,2}日/", $update) === 1) {
                        // ここで更新日時のデータをDBにセット 
                            break;
                    }
                }
            }
        }
        continue;
    }
    $data = array();

// 各列のデータを取得
    foreach ($row->getCellIterator() as $cell) {
        if (!is_null($cell)) {
            $data[] = $cell->getValue();
        }
    }
    // ここで各列の住所、グループ情報をDBにセット
}


少し端折って書いてますが、大体上記のようにExcel内の各データを一行ずつ読み込んでDB内に取り込んでいくという処理をしています。

DBは、PostgreSQLを使っていますがグループ情報を格納しているテーブル構造は下記のようにしています。


- グループ情報を格納しているテーブル構造
 Column |     Type     | Modifiers
--------+--------------+-----------
 pid    | integer      |
 city   | text         |
 town   | text         |
 bgroup | integer      |
 sgroup | character(1) |


まぁ、Excelデータの構造を基本的にそのままDBとして持たせた感じです。



Twitterからリプライを取得するプログラム


こちらは、以前書いた「OAuthを通してPHPからタイムラインの情報を取得する 」のプログラムがベースになっています。

メンションを取得するには、下記のAPIのURLから情報が取得できます。


$api_url = http://api.twitter.com/1/statuses/mentions.xml;

since_idオプションをつけて、TwitterのIDを指定すればそのID以降のメンションを取得することができます。

なので、定期的にメンションを取得するのもそのID以降の情報を取得するようにすれば、重複した情報を処理しないようにすることが出来ます。


取得したメンションのデータは、こちらもDBへ取り込んでいます。

TwitterのIDや送られてきたメンションのデータそのものを取り込み、その日時や処理をしたかどうかをあらわすためのフラグを持たせたりしています。


- リプライの情報を格納しているテーブル構造

    Column     |            Type             | Modifiers
---------------+-----------------------------+-----------
 tid           | text                        |
 screen_name   | text                        |
 twitter_text  | text                        |
 response_text | text                        |
 request_time  | timestamp without time zone |
 post_time     | timestamp without time zone |
 post_flg      | boolean                     |

このプログラム自体は、PHPをデーモン化して動かしています。

PHPをデーモンとして動かすのは「[PHP] デーモンとして動かせるTwitter botの作り方 」を参考にしてみてください。

1分に1度、TwitterのAPIを通してリプライが無いかをチェックして、あればDBに溜め込むという処理だけをさせています。



リプライに反応してPOSTするプログラム


今度は、リプライに反応するプログラムです。

こちらのプログラムも以前に書いた「OAuthを使ってPHPからTwitterへ投稿する 」をベースにしています。


まずは、ユーザーからのリプライはDBに溜め込まれているので、そのDB内で未処理になっている情報を降順に取得していきます。

そのリプライの情報から住所データを抜き出していき、予め取り込んでいる住所とグループ情報のテーブルと突合せを行っていき、該当のものがあればそのグループ情報をPOSTしていきます。

住所のデータではないもの(都道府県から始まってないもの)やリプライではないと思われるツイート(@teiden_kensakuから始まらないもの)に関しては、基本無視させています。


処理が終わったものは順次、処理をしたフラグを更新して行き次回以降に処理をしないようにさせています。

こちらのプログラムもデーモン化させ、1分に1度処理を繰り返すということをしています。



デーモンの稼動状況をチェックするプログラム


リプライを取得するプログラム、リプライに反応するプログラムはそれぞれPEAR::System_Daemon でデーモン化させて稼動させていますが、長時間稼動させていると落ちることがたびたびあったため、その稼動状況をチェックして、デーモンが落ちてたら再起動させるプログラムとして作っています。


落ちる理由は、PEAR::System_Daemonが不安定というより、Twitterからのレスポンスが不安定だったりして、例外処理が発生し、その例外処理を全て処理し切れなかったというものが多かったです。

また、常に私自身がこのbotを監視できる状況ではなかったため、とりあえず落ちたら再起動させる、というようなことを自動化しておきたかったというのもあります。


System_Deamonで動かすプログラムは、起動時に任意の場所に動かすプロセスのIDが書かれたファイルを保存する仕様になっています。

また、自動起動スクリプトを簡単に作ることができるので、このプロセスIDのファイルを読み取りそのプロセスが稼動しているかをチェックし、落ちているようであれば自動起動スクリプトから再起動させてやるという処理をさせています。


- プロセスが落ちてたら再起動する処理
if (!file_exists($get_app_pid)) {
    `/etc/init.d/twitter_get start > /dev/null`;
} else {
    $pid = trim(file_get_contents($get_app_pid));
    if (!(file_exists("/proc/$pid"))) {
        `/etc/init.d/twitter_get restart > /dev/null`;
    }
}

if (!file_exists($post_app_pid)) {
    `/etc/init.d/twitter_post start > /dev/null`;
} else {
    $pid = trim(file_get_contents($post_app_pid));
    if (!(file_exists("/proc/$pid"))) {
        `/etc/init.d/twitter_post start > /dev/null`;
    }
}


プロセスが落ちたと判断しているのは、デーモンを起動させた際に作られるプロセスIDが書かれたファイルが存在しなかった場合、/proc/プロセスIDのディレクトリが存在しなかった場合、ということで判断させています。



まぁまぁ大掛かりにはなってしまったんですが、簡単なTwitter botであれば処理を統合してしまってもっと少ないプログラムで動かすことが出来ると思います。

Twitter上で何かbotを動かしてみたい、って言う場合の参考になれば幸いです。