tweepyでRT直後のツイートを全件取得してCSVに出力

# はじめに
どうも，Heroku無料枠廃止のニュースに戦々兢々としているyagaodekawasuです。
友人と作ったTRPGの補助ツール「[Shinobi-Mas](https://shinobi-mas.herokuapp.com/index)」について友人がTwitterで宣伝したところ，思いの外拡散されました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【お知らせ】<br>秘匿ハンドアウト有りのTRPGシステム向けのハンドアウト管理ツール「Shinobi-Mas」を公開します。<br>ハンドアウトの一覧管理と、テーブル形式での秘密所持状況の確認・更新ができます。<br>是非使ってみてください。<br><br>▼Shinobi-Mas<a href="https://t.co/FNRvMvRT3R">https://t.co/FNRvMvRT3R</a><a href="https://twitter.com/hashtag/TRPG?src=hash&amp;ref_src=twsrc%5Etfw">#TRPG</a> <a href="https://twitter.com/hashtag/%E3%82%B7%E3%83%8E%E3%83%93%E3%82%AC%E3%83%9F?src=hash&amp;ref_src=twsrc%5Etfw">#シノビガミ</a> <a href="https://twitter.com/hashtag/%E3%82%A4%E3%83%B3%E3%82%BB%E3%82%A4%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#インセイン</a> <a href="https://t.co/oUTFFKslLA">pic.twitter.com/oUTFFKslLA</a></p>&mdash; あべのひらが奈 (@AndNatzummy__) <a href="https://twitter.com/AndNatzummy__/status/1562743245951496192?ref_src=twsrc%5Etfw">August 25, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
せっかく伸びたので，RT先でどのように言及されているか調べて今後の開発の参考にしようと思ったんですが，RTした人全員のTweetを手動で見に行くのは限界があると思い，tweepyを使ってTwitter APIを叩いて自動収集できるようにしました。

コードは[GitHub](https://github.com/Nagyon-Jessica/collect-reaction-demo/blob/master/collect_reaction.py)においてあります。

## 注意
今回作成したスクリプトは，**ある程度伸びが落ち着いた状態でTweetの全件取得を1回行う**というものです。今まさに伸びているTweetの反響をリアルタイムで収集するためにはもっと色々工夫が必要になると思います。

また，無料で使えるTwitter APIでは7日以上前のTweetを取得することができません。その点もお気をつけください。

# 前提条件
今回のコードを動かすためには，以下の条件を満たす必要があります。
1. Twitter DeveloperサイトでTwitterAPI用のAppを作成し、APIキーを取得
2. アクセストークンキーとシークレットキーを取得
3. Elevatedにレベルをあげる
4. Pythonの実行環境を用意して`pandas`, `pytz`, `tweepy`をインストールする

1, 2のやりかたはこちら
[【Twitter】APIキー利用申請から発行までの手順解説｜ツイッター運用自動化アプリ作成に向けた環境構築](https://di-acc2.com/system/rpa/9688/)

3のやりかたはこちら
[【2022年度最新版】Twitter API 申請方法(Twitter v1.1 v2対応)](https://zenn.dev/eito_blog/articles/9a17a64ffa3c6b#elevated%E3%81%AB%E3%83%AC%E3%83%99%E3%83%AB%E3%82%92%E3%81%82%E3%81%92%E3%82%8B)

# ポイント
## APIクライアント作成
```Python
api = tweepy.API(auth, wait_on_rate_limit=True)
```
APIクライアント作成時に，オプションで`wait_on_rate_limit=True`を指定しています。

これはTwitter APIの実行回数制限（今回の検索APIは15分間に180回まで）を超えてしまった場合，異常終了せずに一定時間スリープして，再びAPIを叩けるようになったら処理を再開してくれるので，自分でエラーハンドリングを考える必要がなくなるという便利なやつです。

ターミナル上にも下のようにメッセージが出るので安心です。

```
Rate limit reached. Sleeping for: 860
```

## RTのみを検索
```Python
ret = api.search_tweets(q="「Shinobi-Mas」を公開 filter:nativeretweets", count=100, max_id=max_id)
```
RT先で何を言われているか調べるには誰がRTしたか特定する必要があり，そのためにはRTの一覧が必要です。

幸い，Search APIのクエリには（引用を含まない純粋な）RTだけを取得する`filter:nativeretweets`というコマンドがあるので，それを使いましょう。

## ループ処理
```Python
max_id = -1

while True:
    # 初回以外は，前回取得した最後のTweetのID - 1をmax_idとする
    if max_id != -1:
        max_id -= 1

    ret = api.search_tweets(q="「Shinobi-Mas」を公開 filter:nativeretweets", count=100, max_id=max_id)

    # 検索結果が0件になったら終了
    if len(ret) == 0:
        break

    for tweet in ret:
        # 何かしらの処理

    # max_idの更新
    max_id = ret[-1].id
```
これは下のサイトで紹介されているコードを`tweepy`用に書き換えたものになります。`max_id`を更新することによって，前のAPI呼び出しで取得した100件より古い次の100件を取得することができます。

[TwitterAPIを用いたクローラー作成 | DATUM STUDIO株式会社](https://datumstudio.jp/blog/twitterapi%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%A9%E3%83%BC%E4%BD%9C%E6%88%90/)


`search_tweets`の戻り値はTweetオブジェクトのリスト（新→旧の順）になっているので，`len`で検索結果の数を調べられるし，IDや本文などTweetの属性へのアクセスも簡単です。比べてみると`tweepy`の便利さがわかりますね。

## 検索期間の設定
```Python
since = tweet.created_at.strftime("%Y-%m-%d_%H:%M:%S_UTC")
until = (tweet.created_at + timedelta(minutes=10)).strftime("%Y-%m-%d_%H:%M:%S_UTC")
```
RTしたユーザーがRT直後に何を呟いているか調べるために，検索期間を指定します。

大抵の場合，RTしたTweetについて言及するなら10分以内には呟くだろうという想定で，今回は「RTした時刻から10分」という範囲で指定します。

Tweetオブジェクトの`created_at`属性は`datetime`型（UTC）になっているので，`timedelta`で調整しつつ`strftime`で`str`に変換することができます。

検索クエリの`since`や`until`はタイムゾーンを指定しなければ自動的にUTCと解釈されるとは思いますが，念のため末尾に`_UTC`を付けています。このへんはお好みで。

## タイムスタンプの整形
```Python
#関数:　UTCをJSTに変換する
def change_time_JST(u_time):
    #イギリスのtimezoneを設定するために再定義する
    utc_time = datetime(u_time.year, u_time.month,u_time.day, \
    u_time.hour,u_time.minute,u_time.second, tzinfo=timezone.utc)
    #タイムゾーンを日本時刻に変換
    jst_time_since = utc_time.astimezone(pytz.timezone("Asia/Tokyo"))
    # 文字列で返す
    str_time_since = jst_time_since.strftime("%Y-%m-%d_%H:%M:%S")
    return str_time_since

# 中略

            # Tweet日時(JST)
            ctime = change_time_JST(ret2[-1].created_at)
```
RT直後のTweetを取得してCSVに書き込む前に，Tweet作成時刻をJSTの見やすい形に整えます。

この関数は以下のサイトで紹介されているものを使用させていただきましたので，詳しい解説はそちらをご覧ください。

[【コピペするだけ！】Pythonでツイート情報を取得する方法【初心者でも簡単】](https://toxublog.com/blog/get_tweet_tweepy/)

## CSVへの書き込み
```Python
# CSVに書き込む用のマスターDataFrame
df = pd.DataFrame({}, columns=['name', 'screen_name', 'text', 'created_at'])

while True:
    # 中略
        for tweet in ret:
            # 中略

            # 1行のDataFrameを作成してマスターに結合
            df2 = pd.DataFrame({'name': [user.name], 'screen_name': [user.screen_name], 'text': [text], 'created_at': [ctime]})
            df = pd.concat([df, df2])

# DataFrameをCSVに出力
df.to_csv('out.csv', index=False)
```
今回はpandasの`DataFrame`を生成し，それをCSVに出力するという方法でやってみました。

最初にマスターを作成して，そこに1行ずつ追加していくんですが，リストのように`append`でどんどん追加していくということが`DataFrame`ではできず，別の`DataFrame`を作ってマスターに`concat`で結合するという方式を採りました。

pandasに詳しくないので，これがベストなのかわかりません。もっといいやり方があったら教えてください。

# 実行結果
スクリプトを実行して出力されたCSVをExcelで読み込むとこんな感じになります（元Tweetに関係ある部分だけ表示してます）。
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507842/b4347f91-6fef-113c-dc45-05fa475a8fba.jpeg)
約800RTに対して，出力はおよそ160行。そのうち元Tweetに関係ありそうなものは34件でした。だいたい4％ってところですかね。

好意的な意見が多くて一安心です。

# まとめ
tweepyは便利です。

最後までご覧いただきありがとうございました。

# 参考
[API — tweepy 4.10.1 documentation](https://docs.tweepy.org/en/stable/api.html#tweepy.API.search_tweets)
[Tweet object | Docs | Twitter Developer Platform](https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/tweet)
[Twitterの検索で時間を含む期間指定をする方法 - Qiita](https://qiita.com/kai_kou/items/336a9d1290c13abba7e2)
[Twitter API（Tweepy） でのレート制限をハンドリングする方法｜Tweepy Rate limit エラーで困ったら - Compass note](https://compass-note.hatenablog.com/entry/twitter-api-tweepy-rate-limit)
[pandas.DataFrameに列や行を追加（assign, appendなど） | note.nkmk.me](https://note.nkmk.me/python-pandas-assign-append/)
[pandasでDataFrameをcsvファイルに書き出す（to_csv）：インデックスのありなしに注意しよう - Irohabook](https://www.irohabook.com/pandas-to-csv)
[[Twitter API] リツイートを100件より多く取得する方法 – プログラミング生放送](https://pronama.jp/2015/04/28/twitter-api-get-retweets/)