【TwitterAPI+PHP】Phorehoseを使ってTwitter Streaming APIでリアルタイムに超膨大なツイートを取得する

アイキャッチ画像

こんにちは。今回はPHPのライブラリ「Phirehose」を使って文章解析用データcorpusを作成してみようって記事です。

特定ワードを設定するとリアルタイムにTwitter Streaming APIからデータが送られてきます。

嘗てはUser streamっていう種類もあったんですが提供終了しまいた。

取り敢えずサンプルコードを見て学べ!ってことで早速実装しましょう。

Phirehoseをダウンロード

今回は先述したとおりPhirehoseっていうPHPライブラリを使用します。TwitterAPIを操るライブラリはTwistOauthやAbraham/TwitterOauthが有名ですがストリーミングAPIには対応してないのでPhorehoseを使います。

Composerを使ってでもインストール出来ます。ですが今回は手っ取り早くGitからパクります。cloneしてください。

git clone https://github.com/fennb/phirehose

これで導入は終わりです。早速コードを書いていましょう。

因みにPhirehoseって言うなの由来はStreamingAPIの種類に大企業専門、とんでもない額がかかるらしいfirehoseっていう奴がありそれをもじったものらしいです。

コードを実装

以下が特定ワード(複数指定可)を含むツイートを付加情報を付けてCSVファイルにエクスポートするコードです。

<?php
// タイムアウトしないように…
set_time_limit(0);
// ライブラリを読み込む(適宜ディレクトリを変更してください)
require_once('lib/Phirehose.php');
require_once('lib/OauthPhirehose.php');

class FilterTrackConsumer extends OauthPhirehose
{
    public function enqueueStatus($status)
    {
        // ここの関数内で受信時の処理を書いていきます
        $data = json_decode($status, true);
        if (is_array($data) && isset($data['user']['screen_name'])) {
            $text = preg_replace('/[\n\r\t]/', '', $data['text']);
            $timeline =  $data["id_str"] . "," . $data['user']['screen_name'] . ',' . $data['created_at'] . ',' . urldecode($text) . ",\n";
            echo $data["id_str"]; // 確認のためIDを出力しています
            $timeline = mb_convert_encoding($timeline, 'SJIS-win', 'ASCII, JIS, UTF-8, SJIS');
            file_put_contents("sample.csv", $timeline, FILE_APPEND);
        }
    }
}

// APIの情報を定義
define("TWITTER_CONSUMER_KEY", "*******************************");
define("TWITTER_CONSUMER_SECRET", "***************************************************");
define("OAUTH_TOKEN", "************************************************");
define("OAUTH_SECRET", "*********************************************");

// 実行
$sc = new FilterTrackConsumer(OAUTH_TOKEN, OAUTH_SECRET, Phirehose::METHOD_FILTER);
$sc->setTrack(array('keyword1', 'keyword2'));

$sc->setLang('ja'); // 日本語指定
$sc->consume();

とても手短で簡単に実装できますね。一応Gistにもあげときます~

コード解説

べつに解説することもないんですけど…

単純にFilterTrackConsumerクラスのenqueueStatus関数の中に受信してきたデータをどう処理するかを書いて行きます。

そしてAPIの認証情報を定義してキーワードやリージョン・位置情報を指定したあと$sc->consume();で取得開始。

ツイートIDを取得したいなら$data[‘id’]ではなく$data[‘id_str’];を指定してください。浮動小数点型になってしまいます。

Windowsの場合の注意

Windowsで操作したいならファイル書き込みの際必ずSJIS-winでエンコードしてください。

Excelとかメモ帳で開くとバグります。文字化けね。なかなか治らなくなるのでご注意を…

$timeline = mb_convert_encoding($timeline, 'SJIS-win', 'ASCII, JIS, UTF-8, SJIS');

まぁmb_convert_encoding()関数で一発ですが。第3関数を”auto”じゃまずいらしいので一応全部選択してみました。

注意事項

何故かStreamingAPIはエラーに厳しいです。

なのであまり失敗しないように…って言うわけにも行かないと思いますがもしHTTP Errorとか言われたらAPI KeyとAccess Token Secretを再生成したら治ります。

+α ブラウザから取得したい場合

ターミナルシェル上以外のブラウザとかで取得したいなら次のコードをさっきのなんとか関数に追記してください。

ob_flush();
flush();

これはサーバーとの通信中でも逐次画面上に表示するために書くやつです。StreamingAPIは常にTwitterと通信をしっぱなしで取得するため(XMLHttpRequestのxhr.ReadyState 3の通信中みたいな感じ。)こういう処理が必要です…

※ターミナル上で↑を付けたままやるとエラー返されます。

サンプルデータ

一昨日あったシャニマスの福岡公演のハッシュタグをキーワードにセットして約12万件のツイートがCSV形式で取得できました

浮動小数点がある型(float)で保存してしまったからツイートIDがミスっちゃってます…まぁ良いでしょう。

ダウンロードは↓から。リンク切れはお問い合わせフォームからお願いします。コメントでは対応しません。

受信されるJSONデータ型

APIから帰ってくるツイートデータのJSONを配列に変換したやつを掲載します。

一個一個紹介してられないのでまぁご自分で確認してください…

array(25) {
  ["created_at"]=>
  string(30) ""
  ["id"]=>
  float(0.000000000000E+17)
  ["id_str"]=>
  string(18) ""
  ["text"]=>
  string(78) ""
  ["source"]=>
  string(82) ""
  ["truncated"]=>
  bool(false)
  ["in_reply_to_status_id"]=>
  NULL
  ["in_reply_to_status_id_str"]=>
  NULL
  ["in_reply_to_user_id"]=>
  NULL
  ["in_reply_to_user_id_str"]=>
  NULL
  ["in_reply_to_screen_name"]=>
  NULL
  ["user"]=>
  array(38) {
    ["id"]=>
    float(0000000000)
    ["id_str"]=>
    string(10) ""
    ["name"]=>
    string(13) ""
    ["screen_name"]=>
    string(8) ""
    ["location"]=>
    string(27) ""
    ["url"]=>
    NULL
    ["description"]=>
    string(135) ""
    ["protected"]=>
    bool(false)
    ["verified"]=>
    bool(false)
    ["followers_count"]=>
    int()
    ["friends_count"]=>
    int()
    ["listed_count"]=>
    int()
    ["favourites_count"]=>
    int()
    ["statuses_count"]=>
    int()
    ["created_at"]=>
    string(30) ""
    ["utc_offset"]=>
    NULL
    ["time_zone"]=>
    NULL
    ["geo_enabled"]=>
    bool(true)
    ["lang"]=>
    string(2) ""
    ["contributors_enabled"]=>
    bool(false)
    ["is_translator"]=>
    bool(false)
    ["profile_background_color"]=>
    string(6) ""
    ["profile_background_image_url"]=>
    string(48) ""
    ["profile_background_image_url_https"]=>
    string(49) ""
    ["profile_background_tile"]=>
    bool(false)
    ["profile_link_color"]=>
    string(6) ""
    ["profile_sidebar_border_color"]=>
    string(6) ""
    ["profile_sidebar_fill_color"]=>
    string(6) ""
    ["profile_text_color"]=>
    string(6) ""
    ["profile_use_background_image"]=>
    bool(true)
    ["profile_image_url"]=>
    string(74) ""
    ["profile_image_url_https"]=>
    string(75) ""
    ["profile_banner_url"]=>
    string(59) ""
    ["default_profile"]=>
    bool(true)
    ["default_profile_image"]=>
    bool(false)
    ["following"]=>
    NULL
    ["follow_request_sent"]=>
    NULL
    ["notifications"]=>
    NULL
  }
  ["geo"]=>
  NULL
  ["coordinates"]=>
  NULL
  ["place"]=>
  array(9) {
    ["id"]=>
    string(16) ""
    ["url"]=>
    string(56) ""
    ["place_type"]=>
    string(4) ""
    ["name"]=>
    string(16) ""
    ["full_name"]=>
    string(23) ""
    ["country_code"]=>
    string(2) ""
    ["country"]=>
    string(6) ""
    ["bounding_box"]=>
    array(2) {
      ["type"]=>
      string(7) ""
      ["coordinates"]=>
      array(1) {
        [0]=>
        array(4) {
          [0]=>
          array(2) {
            [0]=>
            float()
            [1]=>
            float()
          }
          [1]=>
          array(2) {
            [0]=>
            float()
            [1]=>
            float()
          }
          [2]=>
          array(2) {
            [0]=>
            float()
            [1]=>
            float()
          }
          [3]=>
          array(2) {
            [0]=>
            float()
            [1]=>
            float()
          }
        }
      }
    }
    ["attributes"]=>
    array(0) {
    }
  }
  ["contributors"]=>
  NULL
  ["is_quote_status"]=>
  bool(false)
  ["retweet_count"]=>
  int(0)
  ["favorite_count"]=>
  int(0)
  ["entities"]=>
  array(4) {
    ["hashtags"]=>
    array(0) {
    }
    ["urls"]=>
    array(0) {
    }
    ["user_mentions"]=>
    array(0) {
    }
    ["symbols"]=>
    array(0) {
    }
  }
  ["favorited"]=>
  bool(false)
  ["retweeted"]=>
  bool(false)
  ["filter_level"]=>
  string(3) ""
  ["lang"]=>
  string(2) ""
  ["timestamp_ms"]=>
  string(13) ""
}