複数のTwitterユーザーの情報を長期的に記録し続けるプログラム

標準

はじめに

Aqoursキャストさんたちのフォロワー数の推移を長期的に観測したかったので、即席で作ってみました。なるべく汎用性の高いように作ったので、ご自身の好きなユーザーの好きなステータスを記録することも可能になっています。もしよろしければ私に影響の出ない範囲でご利用ください。

データ保存用データベースを作る

ご利用するデータベース サーバーにアクセスして以下のような構造になるようにセットアップしてください。(Sakuraのレンタルサーバーの場合はコントロールパネルからMySQLのサーバーにアクセスできます。)

  • mysql***.db.sakura.ne.jp
    • yoshipc_aqours
      • Aikyan_
        • date (種別: date)
        • followers_count (種別: int(11))
      • aina_suzuki723
      • anju_inami
      • box_komiyaarisa
      • furihata_ai
      • Kanako_tktk
      • Rikako_Aida
      • Saito_Shuka
      • suwananaka

テーブル名を@idに、その中のフィールドをそれそれdate, followers_countに分けていきます。

情報を集め、記録する

まず必要な情報を集めて記録する必要があります。ユーザーに表示するのはその後です。
本プログラムは任意のユーザーのフォロワー数を長期的に観測できるようにしたものです。

<?php
require_once __DIR__.'/twitteroauth/autoload.php';

use Abraham\TwitterOAuth\TwitterOAuth;

$consumer_key = '*************************';
$consumer_secret = '**************************************************';
$access_token = '**************************************************';
$access_token_secret = '*********************************************';

$connection = new TwitterOAuth($consumer_key, $consumer_secret, $access_token, $access_token_secret);

//調べたいユーザーの@idを配列にして格納します
$target_array = Array(
  "box_komiyaarisa",
  "Kanako_tktk",
  "Saito_Shuka",
  "Rikako_Aida",
  "aina_suzuki723",
  "furihata_ai",
  "suwananaka",
  "anju_inami",
  "Aikyan_"
);

$i = (int)"0";
$result_array = Array();

while (true) {
  //まずユーザー情報を全て取得します
  $followers = $connection->get("users/show",["screen_name" => $target_array[$i]]);
  $followers_array = json_decode(json_encode($followers),true);
  
  //フォロワー数を抜き取り、配列に追加します
  array_push($result_array, $followers_array["followers_count"]);
  if($i == 8) {
    break;
  } else {
    $i++;
  }
}

date_default_timezone_set('Asia/Tokyo');
$today = date('Y-m-d');

//データベース接続情報
//予めデータ保存用のデータベースを作っておく必要があります
$sql = mysql_connect("mysql***.db.sakura.ne.jp","*******","*************");
$db_connection = mysql_select_db("yoshipc_******",$sql);

$i = "0";
while (true) {
  //保存用クエリ
  $query = "INSERT INTO {$target_array[$i]} (date, followers_count) VALUES ('{$today}', '{$result_array[$i]}')";
  
  //クエリを叩きます
  $query_result = mysql_query($query);
  if($i == "8") {
    break;
  } else {
    $i++;
  }
}
?>

上記プログラムがフォロワー数をデータベースに保存するためのものです。実行するたびに保存してしまうので、cronで毎日0時00分に実行するように登録しておいてください。

データを表示する

データベースに保存しただけでは参照するにも推移がよく分かりませんので、Webページ上でデータを閲覧できるようにしていきます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="author" content="yoshipc" />
    <title>Aqoursキャスト フォロワー数統計</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
    <link rel="stylesheet" href="style/main.css" />
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">
  </head>
  <body>
    <?php
      date_default_timezone_set('Asia/Tokyo');
      $today = date('Y-m-d');

      //データベース接続情報
      $sql = new mysqli("mysql***.db.sakura.ne.jp","*******","*************","yoshipc_aqours");

      //ここで表示したい全員の情報を以下のフォーマットで配列に格納
      //Array(@ID, 表示名, カラーコード, カラーコード)
      //後ろ2つのカラーコードはグラフの色になりますので、なるべくユニークなものを登録してください
      $target_name_array = Array(
        Array("box_komiyaarisa","小宮有紗","rgba(255,0,0,1.0)","rgba(255,0,0,.6)"),
        Array("Kanako_tktk","高槻かなこ","rgba(240,240,30,1.0)","rgba(240,240,30,.6)"),
        Array("Saito_Shuka","斉藤朱夏","rgba(127,247,250,1.0)","rgba(127,247,250,.6)"),
        Array("Rikako_Aida","逢田梨香子","rgba(250,112,140,1.0)","rgba(250,112,140,.6)"),
        Array("aina_suzuki723","鈴木愛奈","rgba(232,30,242,1.0)","rgba(232,30,242,.6)"),
        Array("furihata_ai","降幡愛","rgba(247,158,248,1.0)","rgba(247,158,248,.6)"),
        Array("suwananaka","諏訪ななか","rgba(0,247,178,1.0)","rgba(0,247,178,.6)"),
        Array("anju_inami","伊波杏樹","rgba(245,193,51,1.0)","rgba(245,193,51,.6)"),
        Array("Aikyan_","小林愛香","rgba(192,192,192,1.0)","rgba(192,192,192,.6)")
      );
    ?>
    <div id="wrapper">
      <header class="col-lg-10 col-lg-offset-1">
        <h1>Aqoursキャスト フォロワー数統計</h1>
      </header>
      <div class="col-lg-10 col-lg-offset-1">
        <!--// ここにグラフが表示されます //-->
        <canvas id="followers_graph"></canvas>
      </div>
      <div class="col-lg-10 col-lg-offset-1">
        <div class="container col-lg-12">
          <table class="table table-striped">
            <thead>
              <tr>
                <th></th>
                <th>全期間平均変化量(/日)</th>
                <th>5日間平均変化量(/日)</th>
                <th>最新変化量</th>
                <th>最新データ</th>
              </tr>
            </thead>
            <tbody>
              <?php
              $k = (int)"0";
              
              //表示されたときに綺麗にソースを見せたいので...
              $indent = "              ";

              //データを表示するためのテーブルをwhileループで出力
              while(true) {
                echo "<tr>";
                echo "<td>{$target_name_array[$k][1]}</td>";
                echo "<td id=\"{$target_name_array[$k][0]}\"></td>";
                echo "<td id=\"{$target_name_array[$k][0]}_5days\"></td>";
                echo "<td id=\"{$target_name_array[$k][0]}_1day\"></td>";
                echo "<td id=\"{$target_name_array[$k][0]}_latest\"></td>";
                echo "</tr>\r\n{$indent}";
                if($k == "8") {
                  echo "\r\n";
                  break;
                } else {
                  $k++;
                }
              }
              ?>
            </tbody>
          </table>
        </div>
      </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
    <script>
      $(function() {
        var data = {
          type: 'line',
          data: {
            datasets: [
                <?php
                $i = (int)0;
                $count = (int)"0";
                $indent = (string)"                "; //ソースで綺麗に見せるため

                //各種データを保存できるように配列を用意
                $avg = Array();
                $avg_5days = Array();
                $avg_1day = Array();
                $latest = Array();

                while (true) {  //各人のフォロワー数データセットをwhileで出力
                  //指定されたjson形式で出力
                  echo "\r\n" . $indent . "{\r\n";
                  echo $indent . "label: '{$target_name_array[$i][1]}',\r\n";
                  echo $indent . "fill: false,\r\n";
                  echo $indent . "borderColor: '{$target_name_array[$i][2]}',\r\n";
                  echo $indent . "pointBackgroundColor: '{$target_name_array[$i][3]}',\r\n";
                  echo $indent . "data: [";
                  $query = "SELECT * FROM {$target_name_array[$i][0]} ORDER BY date ASC LIMIT 50";
                  $result_query = $sql->query($query);
                  $count = $result_query->num_rows; //全員レコード数は同じはずなので

                  //その人の統計値を出すために変数初期化
                  $j = (int)"0";
                  $diff_sum = (int)"0";
                  $diff_start = (int)"0";
                  $diff_end = (int)"0";
                  $diff_5days_start = (int)"0";
                  $diff_5days_sum = (int)"0";
                  $diff_yesterday = (int)"0";
                  $diff_1day = (int)"0";

                  while ($data = $result_query->fetch_assoc()) {  //1日ずつ出力
                    //ここのwhileループがjsonでの各日データになる

                    echo "{$data["followers_count"]}";

                    //全期間平均
                    if ($diff_start == "0") {
                      $diff_start = $data["followers_count"];
                    } else if ($j == ($count - 1)) {
                      $diff_end = $data["followers_count"];
                    }

                    //5日間平均
                    if ($j == ($count - 5)) {
                      $diff_5days_start =  $data["followers_count"];
                    }

                    //最新増加量
                    if ($j == ($count - 2)) {
                      $diff_yesterday = $data["followers_count"];
                    }

                    //最新データ
                    if ($j == ($count - 1)) {
                      array_push($latest, $data["followers_count"]);
                    }

                    if($j != ($count - 1)) {
                      echo ",";
                      $j++;
                    }
                  } //各日データループはここでおしまい

                  //各統計値計算
                  $diff_sum = $diff_end - $diff_start;
                  $diff_5days_sum = $diff_end - $diff_5days_start;
                  $diff_1day = $diff_end - $diff_yesterday;

                  //統計値を保存用の配列に格納
                  array_push($avg, round(($diff_sum / ($count - 1))));
                  array_push($avg_5days, round($diff_5days_sum / 4));
                  array_push($avg_1day, $diff_1day);

                  echo "]";
                  //ここまでがdata:[]

                  if($i == 8) {
                    echo "}\r\n"; //ここまでがdata: {}
                    break;
                  } else {
                    echo "},\r\n";
                    $i++;
                  }

                } //各日ループはここでおしまい

                ?>
            ],
            labels: [
              <?php
              //今日までの日付を連番で出力
              $i = $count - 1;
              while (true) {

                echo "'" . date("Y-m-d",strtotime("- {$i} day")) . "'";

                if($i == 0) {
                  echo "\r\n";
                  break;
                } else {
                  echo ",";
                  $i--;
                }
              }
              ?>
            ]
          },
          options: {}
        };
        new Chart(document.getElementById("followers_graph"), data);
        <?php

        //このパート以降は各統計値のデータは使わないので、色々と改造しちゃう
        //コード上の事情でテーブルへの値代入はjQuery使います
        $k = "0";
        $indent = "        ";
        $max = number_format(max($avg));
        $min = number_format(min($avg));

        while(true) { //全期間平均をテーブルに出力
          $avg[$k] = number_format($avg[$k]);
          if ($max == $avg[$k]) {
            echo "$(\"#{$target_name_array[$k][0]}\").html(\"<b id='max'>{$avg[$k]}</b>\");\r\n{$indent}";
          } else if ($min == $avg[$k]){
            echo "$(\"#{$target_name_array[$k][0]}\").html(\"<b id='min'>{$avg[$k]}</b>\");\r\n{$indent}";
          } else {
            echo "$(\"#{$target_name_array[$k][0]}\").text(\"{$avg[$k]}\");\r\n{$indent}";
          }

          if($k == "8") {
            echo "\r\n{$indent}";
            break;
          } else {
            $k++;
          }
        }

        $k = "0";
        $max = number_format(max($avg_5days));
        $min = number_format(min($avg_5days));

        while(true) { //5日間平均をテーブルに出力
          $avg_5days[$k] = number_format($avg_5days[$k]);
          if ($max == $avg_5days[$k]) {
            echo "$(\"#{$target_name_array[$k][0]}_5days\").html(\"<b id='max'>{$avg_5days[$k]}</b>\");\r\n{$indent}";
          } else if ($min == $avg_5days[$k]){
            echo "$(\"#{$target_name_array[$k][0]}_5days\").html(\"<b id='min'>{$avg_5days[$k]}</b>\");\r\n{$indent}";
          } else {
            echo "$(\"#{$target_name_array[$k][0]}_5days\").text(\"{$avg_5days[$k]}\");\r\n{$indent}";
          }

          if($k == "8") {
            echo "\r\n{$indent}";
            break;
          } else {
            $k++;
          }
        }

        $k = "0";
        $max = number_format(max($avg_1day));
        $min = number_format(min($avg_1day));

        while(true) { //最近増加量をテーブルに出力
          $avg_1day[$k] = number_format($avg_1day[$k]);
          if ($max == $avg_1day[$k]) {
            echo "$(\"#{$target_name_array[$k][0]}_1day\").html(\"<b id='max'>{$avg_1day[$k]}</b>\");\r\n{$indent}";
          } else if ($min == $avg_1day[$k]){
            echo "$(\"#{$target_name_array[$k][0]}_1day\").html(\"<b id='min'>{$avg_1day[$k]}</b>\");\r\n{$indent}";
          } else {
            echo "$(\"#{$target_name_array[$k][0]}_1day\").text(\"{$avg_1day[$k]}\");\r\n{$indent}";
          }

          if($k == "8") {
            echo "\r\n{$indent}";
            break;
          } else {
            $k++;
          }
        }

        $k = "0";
        $max = number_format(max($latest));
        $min = number_format(min($latest));

        while(true) { //最新データ
          $latest[$k] = number_format($latest[$k]);
          if ($max == $latest[$k]) {
            echo "$(\"#{$target_name_array[$k][0]}_latest\").html(\"<b id='max'>{$latest[$k]}</b>\");\r\n{$indent}";
          } else if ($min == $latest[$k]){
            echo "$(\"#{$target_name_array[$k][0]}_latest\").html(\"<b id='min'>{$latest[$k]}</b>\");\r\n{$indent}";
          } else {
            echo "$(\"#{$target_name_array[$k][0]}_latest\").text(\"{$latest[$k]}\");\r\n{$indent}";
          }

          if($k == "8") {
            echo "\r\n{$indent}";
            break;
          } else {
            $k++;
          }
        }
        ?>
      });
    </script>
  </body>
</html>

これでグラフが表示されます。
めちゃくちゃ長ったらしいソースになってしまい申し訳ないです。
サーバーにアップロードして、ページを開くと以下のようになります。

ダイブいい感じ!

index.phpも配列さえいじれば別の人に入れ替えることが出来るので、ぜひぜひ。
完成形はこちらです。
https://yoshipc.net/aqours_cast_followers/

ラブライブ!サンシャイン!!楽曲一覧

標準

なかなかネットで調べていて最新の情報が見つからないので、自分で作りました。間違えなどありましたら、コメントでお知らせください。
最新情報が発表され次第、更新していきます。情報量が多いので、横にスクロールできるようになっています。

→→→→ スクロール →→→→

曲名 発売日 備考 収録アルバム キャッチコピー センター
君のこころは輝いてるかい? 2015/10/07 1stシングル 君のこころは輝いてるかい? 高海千歌、桜内梨子 Aqours
Step! ZERO to ONE
Aqours☆HEROES
恋になりたいAQUARIUM 2016/04/27 2ndシングル 恋になりたいAQUARIUM 渡辺曜 Aqours
待ってて愛のうた
届かない星だとしても
元気全開DAY!DAY!DAY! 2016/05/11 1stミニユニットシングル 元気全開DAY!DAY!DAY! CYaRon!
夜空はなんでも知ってるの?
トリコリコPLEASE!! 2016/05/25 1stミニユニットシングル トリコリコPLEASE!! AZALEA
ときめき分類学
Strawberry Trapper 2016/06/08 1stミニユニットシングル Strawberry Trapper Guilty Kiss
Guilty Night, Guilty Kiss!
青空Jumping Heart 2016/07/20 1期アニメOP 青空Jumping Heart 高海千歌 Aqours
ハミングフレンド
決めたよHand in Hand 2016/08/03 1期1話挿入歌 決めたよHand in Hand
/ダイスキだったらダイジョウブ!
──手に手をとって行こう! 2年生 2年生
ダイスキだったらダイジョウブ! 1期3話挿入歌
ユメ語るよりユメ歌おう 2016/08/24 1期アニメED ユメ語るよりユメ歌おう 進む時だよ、あたらしい場所へ! Aqours
サンシャインぴっかぴか音頭
夢で夜空を照らしたい 2016/09/14 1期6話挿入歌 夢で夜空を照らしたい
/未熟DREAMER
歌ってみよういっしょにね! 1、2年生
未熟DREAMER 1期9話挿入歌 松浦果南 Aqours
Pops heartで踊るんだもん! 2016/09/27 Blu-ray第1期第1巻 Aqours オリジナルソングCD (1) Aqours
空も心も晴れるから 2016/10/26 Blu-ray第1期第2巻 Aqours オリジナルソングCD (2) 2年生
想いよひとつになれ 2016/11/09 1期11話挿入歌 想いよひとつになれ
/MIRAI TICKET
あこがれ抱きしめて次へ進むんだ! 高海千歌、渡辺曜 桜内梨子以外の8人
MIRAI TICKET 2016/11/09 1期13話挿入歌 Aqours
ジングルベルがとまらない 2016/11/23 スクフェスオリジナル ジングルベルがとまらない Aqoursのシャンシャン♪
Christmas♪
Aqours
聖なる日の祈り
Waku-Waku-Week! 2016/11/25 Blu-ray第1期第3巻 Aqours オリジナルソングCD (3) 1年生
2016/11/30 1期アニメOST Sailing to the Sunshine 輝きの欠片を集めて…
Daydream Warrior 2016/12/22 Blu-ray第1期第4巻 Aqours オリジナルソングCD (4) Aqours
G線上のシンデレラ 2017/01/27 Blu-ray第1期第5巻 Aqours オリジナルソングCD (5) 3年生
P.S.の向こう側 2017/02/16 Blu-ray第1期ゲーマーズ
全巻購入特典(日付は初出)
P.S.の向こう側 CYaRon!
LONELY TUNING Blu-ray第1期ソフマップ
全巻購入特典(日付は初出)
LONELY TUNING AZALEA
Guilty Eyes Fever Blu-ray第1期アニメイト
全巻購入特典(日付は初出)
Guilty Eyes Fever Guilty Kiss
スリリング・ワンウェイ 2017/02/24 Blu-ray第1期第6巻 Aqours オリジナルソングCD (6) Aqours
太陽を追いかけろ! 2017/03/24 Blu-ray第1期第7巻 Aqours オリジナルソングCD (7) Aqours
HAPPY PARTY TRAIN 2017/04/05 3rdシングル HAPPY PARTY TRAIN 松浦果南 Aqours
SKY JOURNEY
少女以上の恋がしたい
近未来ハッピーエンド 2017/05/10 2ndミニユニットシングル 近未来ハッピーエンド 近未来を選べ! CYaRon!
海岸通りで待ってるよ
GALAXY HidE and SeeK 2017/05/31 2ndミニユニットシングル GALAXY HidE and SeeK やけどするほど、危険な3人! AZALEA
INNOCENT BIRD
コワレヤスキ 2017/06/21 2ndミニユニットシングル コワレヤスキ 愛にとどめをさす! Guilty Kiss
Shadow gate to love
Landing action Yeah!! 2017/06/30 「Aqours NEXT Step! Project」
テーマ曲
Aqours CLUB CD SET Aqours
夏への扉 Never end ver. 2017/08/02 「ハリケーン・ブロッサム」 ラブライブ!サンシャイン!!
デュオトリオコレクションCD
VOL.1
桜内梨子、国木田花丸、小原鞠莉
真夏は誰のモノ? 「インフェルノ・フェニックス」 黒澤ダイヤ、黒澤ルビィ
地元愛♡満タン☆サマーライフ 「ユニコーン・ブリザード」 渡辺曜、津島善子
夏の終わりの雨音が 「トワイライト・タイガー」 高海千歌、松浦果南
未来の僕らは知ってるよ 2017/10/25 2期アニメOP 未来の僕らは知ってるよ 私たち、輝きたい! 高海千歌 Aqours
君の瞳を巡る冒険 リアル脱出ゲーム イメージソング
勇気はどこに?君の胸に! 2017/11/15 2期アニメED 勇気はどこに?君の胸に! 夢は、消えない Aqours
“MY LIST” to you!
MY舞☆TONIGHT 2017/11/29 2期3話挿入歌 MY舞☆TONIGHT
/MIRACLE WAVE
あたらしい光 つかめるんだろうか? Aqours
MIRACLE WAVE 2期6話挿入歌 高海千歌
Awaken the power 2017/12/20 2期9話挿入歌 Awaken the power Saint Aqours Snow
CRASH MIND 2期5話挿入歌 Saint Snow
DROPOUT!? 2期8話挿入歌

PHPを使った位置情報収集(ラブライバーはどこから来るの?)

標準

開発に至った経緯

9月29日、30日に「Aqours 2nd ラブライブ! HAPPY PARTY TRAIN TOUR 埼玉公園」(公式サイト)が開催されまして、1日目に私はライブ・ビューイングに参戦しました。(2日目は残念ながらTwitterを眺めるだけでしたが😥)(感想は後述)
そこで、ライブのために埼玉まで来た方はどこから来ているのかと言うのを簡易的に調査するために、Twitterを使った位置情報収集アプリを作ることにしました。

調査方式

  1. ある任意の座標(今回は西武ドームの 35.768653,139.419132 / 国土地理院の地図)をプログラムに与えると、その地点の半径1km以内で投稿されたツイートを集めます。
  2. そのツイート主を全員それぞれ200ツイートずつ遡ってツイートを取得します。
  3. その中でジオタグ(位置情報)があれば集めます。
  4. Excelで収集したデータセットを整理します。
  5. 最後にそれを地図に出力します。

といった感じで、本当に簡易的です。

コード

<!DOCTYPE html>
<html lang="ja">
<head>
    <title>Twitter trace</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="index.css" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
    <?php
    ini_set('display_errors', 1);
    require_once(__DIR__ . "/twitteroauth/autoload.php");
    use Abraham\TwitterOAuth\TwitterOAuth;
    $consumer_key = "***************";
    $consumer_secret = "****************************************************";
    $access_token = "***************************************************";
    $access_token_secret = "*******************************************";

    $connection = new TwitterOAuth($consumer_key, $consumer_secret, $access_token, $access_token_secret);

    ////////////////////////////////////////
    $count_limit = (int)"100"; //0-100
    $get_users_status = (int)"200"; //0-200
    $default_lat = "35.768653";
    $default_long = "139.419132";
    ////////////////////////////////////////

    if(isset($_GET["lat"]) && isset($_GET["long"]) && $_GET["lat"] != "" && $_GET["long"] != "") {
        $set_lat = $_GET["lat"];
        $set_long = $_GET["long"];
    } else {
        $set_lat = $default_lat;
        $set_long = $default_long;
    }

    if(isset($_GET["max_id"]) && $_GET['max_id'] != "") {
        $max_id = $_GET["max_id"];
    } else {
        $max_id = "";
    }

    $geo_params = Array(
        "geocode" => "{$set_lat},{$set_long},1.0km",
        "count" => $count_limit,
        "result_type" => "mixed",
        "max_id" => $max_id,
        "include_entities" => false

    );

    $result_tweets = $connection->get("search/tweets", $geo_params);

    $result_tweets_array = json_decode(json_encode($result_tweets),true);

    $result_coordinate = Array();
    $oldest_id = "";
    $debug_info = "";

    $i = (int)"0";
    while (true) {
        $debug_info .= "{$i}\r\n";
        $status = $result_tweets_array["statuses"][$i];
        if($created_at = $status["created_at"] == "") {
            break;
        }
        $screen_name = $status["user"]["screen_name"];
        $lat = $status["geo"]["coordinates"][0];
        $long = $status["geo"]["coordinates"][1];

        if($screen_name != "mgn_tokorozawa") {
            if($lat != "" && $long != "") {
                array_push($result_coordinate,Array("lat" => $lat,"long" => $long));
            }
        }

        $users_info = Array(
            "screen_name" => $screen_name,
            "count" => $get_users_status,
            "exclude_replies" => true,
            "include_rts" => false,
            "trim_user" => true,
            "exclude_replies" => true
        );

        $users_data = $connection->get("statuses/user_timeline",$users_info);

        $users_data_array = json_decode(json_encode($users_data),true);

        $i2 = (int)"0";
        while(true) {
            $status_data = $users_data_array[$i2];
            if($status_data["geo"] != "" && $lat != $status_lat && $long != $status_long) {
                $status_lat = $status_data["geo"]["coordinates"][0];
                $status_long = $status_data["geo"]["coordinates"][1];
                $debug_info .= "status_lat:{$status_lat}\r\n";
                $debug_info .= "status_long:{$status_long}\r\n";
                $debug_info .= "http://maps.gsi.go.jp/#16/{$status_lat}/{$status_long}/&base=std\r\n";
                if ($screen_name != "mgn_tokorozawa") {
                    if($status_lat != "" && $status_long != "") {
                        array_push($result_coordinate,Array("lat" => $status_lat,"long" => $status_long));
                    }
                }
            }

            $i2++;
            if($i2 >= $get_users_status) {
                break;
            }
        }

        if ($status['id'] != "") {
            $oldest_id = $status['id'];
        }

        $debug_info .= "screen_name:{$screen_name}\r\n";
        $debug_info .= "id:{$status['id']}\r\n";
        $debug_info .= "text:{$status['text']}\r\n";
        $debug_info .= "lat:{$lat}\r\n";
        $debug_info .= "long:{$long}\r\n";
        $debug_info .= "http://maps.gsi.go.jp/#16/{$lat}/{$long}/&base=std\r\n";
        $debug_info .= "https://twitter.com/{$status['user']['screen_name']}/status/{$status['id']}\r\n=================\r\n";
        $i++;
        if($i >= $count_limit) {
            break;
        }
    }

    $api_limit_info = $connection->get("application/rate_limit_status");
    $api_limit_info_array = json_decode(json_encode($api_limit_info),true);
    ?>
    <div class="container" id="main">
        <?php
        echo "<h2>API LIMIT INFO</h2>";
        echo "<ul class='list-group'><li>/search/tweets:{$api_limit_info_array['resources']['search']['/search/tweets']['remaining']}/{$api_limit_info_array['resources']['search']['/search/tweets']['limit']}</li>";
        echo "<li>/statuses/user_timeline:{$api_limit_info_array['resources']['statuses']['/statuses/user_timeline']['remaining']}/{$api_limit_info_array['resources']['statuses']['/statuses/user_timeline']['limit']}</li></ul>";

        $result = (int)"0";
        $result_count = count($result_coordinate);
        $output = (string)"";
        while(true) {
            $output .= "{lat: {$result_coordinate[$result]['lat']},lng: {$result_coordinate[$result]['long']}},";
            $result++;
            if($result >= $result_count) {
                break;
            }
        }
        ?>

        <h2>Custom search</h2>
        <form method="get" action="#" class="form-horizontal">
            <div class="form-group">
                <label for="lat" class="control-label" >latitude</label>
                <input type="text" id="lat" name="lat" class="form-control input-lg" value="<?php echo $set_lat; ?>" />
            </div>
            <div class="form-group">
                <label for="long" class="control-label" >longitude</label>
                <input type="text" id="long" name="long" class="form-control input-lg" value="<?php echo $set_long; ?>" />
            </div>
            <div class="form-group">
                <label for="submit" class="control-label">search</label>
                <input type="submit" class="form-control input-lg" id="submit" value="Search" />
            </div>
        </form>

        <h2>Result</h2>
        <form action="#" class="form-horizontal">
            <div class="form-group">
                <label for="coordinates">Result coordinates dataset</label>
                <textarea rows="40" cols="100" class="form-control input-lg" name="coordinates"><?php echo $output; ?></textarea>
            </div>
            <div class="form-group">
                <label for="debug_info">Debug information</label>
                <textarea rows="40" cols="100" class="form-control input-lg" name="debug_info"><?php echo $debug_info; ?></textarea>
            </div>
        </form>

        <h2>Re-search</h2>
        <form action="#" method="get" class="form-horizontal">
            <div class="form-group">
            <label for="max_id" class="control-label">max_id</label>
            <input type="text" class="form-control input-lg" name="max_id" id="max_id" value="<?php echo $oldest_id; ?>" />
        </div>
        <div class="form-group">
            <label for="submit" class="control-label">search</label>
            <input type="submit" class="form-control input-lg" value="Search" />
        </div>
        </form>
        <p><a href="direct_input.php">input page</a></p>
    </div>
</body>
</html>

人に見せることを目的としていないコードなので本当に適当ですが、一応自分の中では使いやすいように色々とカスタムしてあります。このプログラムを順次走らせていくことで、ライブ期間中の全ツイートを調べることが出来ます。

データを整理する

上記のプログラムで収集した座標のデータセットは大量の重複を含んでいて、そのままのデータを地図にプロットすると閲覧するのにちょっと不便です。なので、重複データを排除するためにExcelで整理します。
適当に集めたJSON形式のデータをExcelに打ち込んでいきます。(改行挟んだりの整形はPHP使うと便利です。)

ワンクリックで重複削除できるんですね。便利!整理したデータを再びJSONにして、次はいよいよ地図にプロットしていきます。

地図にプロット

今回はGoogleが公開している Maps JavaScript API を使っていきたいと思います。APIの使い方のページに記載されている方法で自分用のAPIキーを取得します。そして、以下のようなページを作成して、いざプロットしていきます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <title>Twitter trace</title>
    <meta charset="UTF-8" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="index.css" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
        <script>
        var map;
        var marker = [];
        var infoWindow = [];
        var markerData = [
            {lat:35.768575,lng:139.420501},
            {lat:35.768493,lng:139.420522},
            {lat:35.77059347,lng:139.4194273},
            {lat:35.7707,lng:139.4198},
            {lat:35.76880072,lng:139.4209541},
            {lat:35.76879838,lng:139.4209604},
            {lat:35.7112046,lng:139.4640989},
            {lat:35.77136271,lng:139.4202191},
            {lat:35.8353742,lng:139.6503192},
            {lat:35.83783604,lng:139.6495825},
            {lat:35.76880259,lng:139.4209549},
            {lat:35.76946619,lng:139.4382386}
        ];
        function initMap() {
            var mapLatLng = new google.maps.LatLng({lat: markerData[0]['lat'], lng: markerData[0]['lng']});
            map = new google.maps.Map(document.getElementById('result_map'), {
                center: mapLatLng,
                zoom: 15
            });
            for (var i = 0; i < markerData.length; i++) {
                markerLatLng = new google.maps.LatLng({lat: markerData[i]['lat'], lng: markerData[i]['lng']});
                marker[i] = new google.maps.Marker({
                    position: markerLatLng,
                    map: map
                });
            }
        }
        </script>

        <div id="result_map"></div>

        <script async defer
        src="https://maps.googleapis.com/maps/api/js?key=*************************************&callback=initMap">
        </script>
    </div>
</body>
</html>

これだけで好きなだけ地図に地点をプロットできるなんて凄いですね。

まとめ

今回は最終的に8,000件くらいのツイートを収集して2,405件のユニークなデータを取得しました。このデータに関しては、誰でも簡単に閲覧することができるように公開してありますので、参考までに見てみてください。
データ閲覧

日本国内でも北は北海道、南は鹿児島県(確認できた中で最南端)から、世界にまで目を向けると上海やシンガポールなどからも来ていることが分かりました。こんなにも津々浦々から人々を呼び寄せることができるなんてラブライブ凄いなぁ、と言った感じです。

(ライブの)感想

初披露の曲や衣装やダンスを拝めて本当に素晴らしかった!初めてライブを見たんですけど、楽しすぎる…!というかキャスト同士にイチャつきが尊すぎる!!!!!もっと見たかった、というか2日目も参戦したかった!
次はファンミーティングや3rdツアーなどが予定されていますので、こちらの方はぜひ現地で参戦したいですね。