初心者がTwitter APIで遊ぶ

13:36・2021/06/03 公開

19:34・2021/06/03 更新

「データビジュアライゼーションをしよう」という授業内の課題で、Twitter APIを利用してツイートのデータを取得したかったので、いろいろ調べながら実装してみるの巻。

概要

今回作るのは@shuumaiのツイートを取得してその人気度をグラフで表示するページ。

TwitterAPIには「TwitterAPI v1.1」と「TwitterAPI v2」の2種類あり、今回はv2の方を使うぞ。
(四苦八苦してJSONを取得してからこの仕様を知った)

過程

申請

まずはTwitterに開発者として申請をした。
(有効な電話番号とメールアドレスを登録しているアカウントでしか申請できなかった)
英文でAPIの利用目的を紹介し、リクエスト送信から数時間で、無事認証された。
(ネイティブではないのでほぼgoogleで翻訳したものをコピペした)

問題はここから。
アクセスキーを含むため、純粋なJavaScriptだけでは認証ができないようだ。

認証を通す

届いたメールからTwitter Developer Portalに案内されたので、そこでプロジェクトを作成し、細かい設定をした。
2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説
↑このサイトを参考にした

今回はツイートを取得するだけなのでOAuth認証はしない
ベアラー トークンの使用と生成
公式ページを見たことろ、cURLで認証できるらしいのでさっそく試した。

で、できた〜〜〜(感動)
VSCodeのターミナルでcURLを実行してみたところ、ちゃんとツイートが取れた。

エンドポイントのURLを、「https://api.twitter.com/2/tweets/search/recent?query=from:shuumai」にすると@shuumaiのツイートデータがいくつか取れるので遊ぼう。

……??(これだとターミナルでしか実行できなくない?)
そう思った我々はアマゾンの奥地へと向かった。

まずcURLとはなんぞやと思ったので、検索したところ知らない世界すぎた。
それから「cURL php」で互換性があるのか調べたところ、いくつかコマンドがあるらしく、「基本的な curl の使用法 -php.net」を見ながら色々試した。できなくない?

最終的にLINEのオープンチャットで質問したところ、「exec — 外部プログラムを実行する-php.net」このコマンドでcURLが実行できると教えてもらい事なきを得ました。
おそらく正攻法ではないけど、今回はこれで。

//$results = 10;
$Pquo = "\"";//最初のダブルクオーテーション
$Lquo = "\"";//最後のダブルクオーテーション
$pre = "https://api.twitter.com/2/tweets/search/recent?query=from:shuumai&max_results=";
//返ってくるデータの数を変えたかったのでここで分断
$late = "&tweet.fields=created_at,public_metrics";
$url = $Pquo.$pre.$results.$late.$Lquo;//全部つなげる
$bearer = '"Authorization: Bearer ******" ';//終わりの空白は必要なやつです
$output;//null
$retval;//null
$command = 'curl -X GET -H ' . $bearer . $url;
//つなげたものを代入
$data = exec($command, $output, $retval);
//$dataの中に返ってきたデータが入る。

最終的に完成したのがこのコード。
ダブルクオーテーションだけを代入した変数があるのは、そうしないとうまくコマンドを挟めなかったため。(中身は2個とも一緒)

var_dump($command);で元のcURLをちゃんと再現できているか確かめてください。空白やクォーテーションが抜けていると実行できません……。

データを使いやすくする

var_dump($data);
文字の圧が強い

このままだとやりにくいので、JavaScriptでコンソールに表示する。

<script>
const Data = <?php echo $data?>;

console.log(Data);
</script>

これでデータビジュアライゼーションの下準備が完了しました。

成果物

@shuumaiのツイートを確認する – おそらきれい
d3.jsを使ってグラフを描画しています。

簡単に今回の制作について書き留めておこうと思います。

CSS Grid

初めて使った。カラムごとに幅を親要素で指定できる。
大きい枠組みにはflexよりこっちのほうが記述が少なくて楽かもしれない。

複数のformの送信と受け取り

form1(返ってくるデータの数を設定)
<form action="index.php" method="get">
   <div class="form_select">
       <p class="text_tweet">Tweet</p>
       <input type="text" name="range" value="<?php echo $range?>" hidden>
       <select name="q" id="results" onChange="this.form.submit()">
                    <option value="10">10</option>
                    <option value="20">20</option>
                    <option value="30">30</option>
                    <option value="40">40</option>
                    <option value="50">50</option>
                    <option value="60">60</option>
                    <option value="70">70</option>
                    <option value="80">80</option>
                    <option value="90">90</option>
                    <option value="100">100</option>
       </select>
    </div>
</form>
from2(グラフの縮尺を設定)
<form action="index.php" method="get">
    <div class="range_box">
        <p class="text_graph">グラフ</p>
        <input type="text" name="q" value="<?php echo $results?>" hidden>
        <label>50<input name="range" type="range" id="range" onChange="this.form.submit()"><span id="range_val"></span></label>
    </div>
</form>
formの受け取り
<?php 
function h($str) {
    return htmlspecialchars( $str, ENT_QUOTES, "UTF-8");
}
$request = $_GET['q'];
$GetRange = $_GET['range'];
if(isset($request)) {
       $results = h($request);//データが有るときはこっち
}else {
    $results = 10;//nullのときは初期値(10)を設定
};
if(isset($GetRange)) {
    $range = h($GetRange);//データが有るときはこっち
} else {
    $range = 1000;//nullのときは初期値(1000)を設定
};

?>

初回の読み込みではどちらも初期値が適用されます。
片方のformを送信したときには、もう一方は今代入されている値が一緒に送信されるようにしています。(input hiddenが無いと一方のデータは初期値になります)

行の高さを合わせる

データを縦に入れる都合でCSSで調整する方法を思いつかなかったので、JSで無理やり高さを変更しています。

let select = document.querySelector('select');
let _text = document.getElementsByClassName('shuumai_text');
let _index = document.getElementsByClassName('shuumai_index');
let _graph_fav = document.getElementsByClassName('graph_fav');
let _graph_ret = document.getElementsByClassName('graph_ret');
let _reaction_fav = document.getElementsByClassName('fav');
let _reaction_ret = document.getElementsByClassName('ret');
let text_arr = [];

function height_fixed() {
    let text_arr = [];

    for (let i = 0; i < _text.length; i++) {
        _text[i].style.height = 'auto';
        text_arr.push(_text[i].getBoundingClientRect(innerHeight).height);
        _index[i].style.height = text_arr[i] + 'px';
        _text[i].style.height = text_arr[i] + 'px';
        _reaction_fav[i].style.height = text_arr[i] + 'px';
        _reaction_ret[i].style.height = text_arr[i] + 'px';
        _graph_fav[i].style.height = text_arr[i] + 'px';
        _graph_ret[i].style.height = text_arr[i] + 'px';
    };
}
height_fixed();

window.addEventListener('resize', function () {
    height_fixed();
});

あとはグラフをfor文でボックスの中に一つずつ入れたりとか、d3.jsのアニメーションがグラフの都合で使えないのでsetIntervalで頑張ったりとか、rangeのmaxの値をデータによって変えたりとか、リアクションが1000超えると赤くなったりとか、アイコンをFontAwesomeで入れたりとか色々しました。

ちなみにしゅうまい君の画像はTwitterの画像データにリンクしてある(OGPもそう)ので、もし変わったら使えなくなってるかもしれないです。