Python3で特定のツイートに対するリプライの取得

開発環境:MacOS
使用言語:Python3.7（3.6以前でも実行可能）
TwitterAPIで取得できる情報にはツイートに対するリプライ取得などが公式で準備されていないので自作
特定のユーザもしくはツイートに対するリプライを取得してpandasでcsvファイルに保存
実行に必要なモジュールは大体`pip install`か`pip3 install`でインストールできます。
※TwitterAPIの利用には認証キーが必要です。登録の方法などは[こちら](https://website-planner.com/twitterアプリケーションの作成（consumer-key、consumer-secret、access-token、access-token-secret/)を参考にしました。

```python:get_replies.py
from requests_oauthlib import OAuth1Session
import json
import datetime, time, sys
import codecs
import pandas as pd
from collections import defaultdict
CK = ''    # Consumer Keyを入れてください
CS = ''    # Consumer Secretを入れてください
AT = ''    # Access Tokenを入れてください
AS = ''    # Accesss Token Secretを入れてください
session = OAuth1Session(CK, CS, AT, AS)

url = 'https://api.twitter.com/1.1/search/tweets.json?'

TweetList = defaultdict(list)

def get_replies(to_user, since_id):
    N = 5 #繰り返しの数
    query = " -rt to:" + to_user
    lang = "ja"
    result_type="recent" # 最新のツイートを取得
    count = 100 # 1回あたりの最大取得ツイート数（最大100）
    max_id = ''
    for i in range(N):
        params = {
                    'q':query,
                    'lang':lang,
                    'count':count,
                    'since_id':since_id,
                    'max_id':max_id
                 }
        res = session.get(url, params = params)

        if res.status_code != 200:
            print ("Twitter API Error: %d" % res.status_code)
            sys.exit(1)

        limit = int(res.headers['X-Rate-Limit-Remaining'])
        print ('アクセス可能回数 %d' % limit)
        print ('リセット時間 %s' % res.headers['X-Rate-Limit-Reset'])
        sec = int(res.headers['X-Rate-Limit-Reset'])\
            - time.mktime(datetime.datetime.now().timetuple())
        print ('リセット時間 （残り秒数に換算） %s' % sec)
        if limit < 2:
            print("リセット時間まで待機します\n残り時間：%s" % abs(sec))
            time.sleep(abs(sec))

        res_text = json.loads(res.text)
        for tweet in res_text['statuses']:
            # 最後のidを格納
            max_id = str(tweet['id']-1)

            try:
                if(since_id not in tweet['in_reply_to_status_id_str']):
                    continue
            except:
                print('リプライの取得に失敗したか、このツイートはリプライではありません')
                continue
            print('--------------------------------------')
            print ('[作成日時]\n %s' % tweet['created_at'])
            print ('[内容]\n %s' % tweet['text'])
            print ('[ユーザー]\n %s' % tweet['user']['screen_name'])
            print ('[ID]\n %s' % tweet['id_str'])
            print ('[リプライ先ID]\n %s' % tweet['in_reply_to_status_id_str'])
            print('--------------------------------------')
            print ('\n')

            TweetList['id'].append(tweet['id'])
            TweetList['name'].append(tweet['user']['screen_name'])
            TweetList['created_at'].append(tweet['created_at'])
            TweetList['retweet_count'].append(tweet['retweet_count'])
            TweetList['favorite_count'].append(tweet['favorite_count'])
            TweetList['in_reply_to_status_id_str'].append(tweet['in_reply_to_status_id_str'])
            TweetList['text'].append(tweet['text'])

        print('%d回目のループ終了\n'%(i+1))

    df = pd.DataFrame(TweetList)
    now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    new_filename = "./"+to_user+"_replies"+now+".csv"
    df.to_csv(new_filename,encoding='utf-8-sig')
    TweetList.clear()

if __name__ == '__main__':
    get_replies('TwitterJP','')
```

実行するとカレントディレクトリに`TwitterJP_replies2019010120574.csv`(後ろの数字は作成した年月日時分秒)のようなcsvファイルが生成されていると思います。

関数の引数は以下のようになっています。
第1引数: リプライ元のユーザ名
第2引数: リプライ元のツイートのID（str型）

第2引数は例のように空でも構いませんが、その場合は指定したユーザーの複数のツイートに対するリプライを取得することになります。
# リプライ取得の仕組み
Twitter REST APIのGET/Search Tweetsを利用しました。公式サイトは[こちら](https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets)

はじめにも書きましたが、TwitterAPIには直接リプライを取得するメソッドはありません。その代わり、取得できる情報に'in_reply_to_status_id'という項目があって、これはツイートがリプライだった場合、リプライ先のツイートのIDが入っています。
つまり、ツイートAのリプライが欲しいとして、ツイートBを取得した時、
ツイートAのIDと取得したツイートBの'in_reply_to_status_id'を比較して一致していれば、ツイートBはツイートAのリプライであると分かるのです。

ソースコードの

~~~python
try:
    if(since_id not in tweet['in_reply_to_status_id_str']):
        continue
~~~

が比較を行っている部分になります。取得したツイートがリプライでない（引用RTなど）場合には、そもそも'in_reply_to_status_id_str'の項目が存在せず、エラーが発生するのでtry:の中に入れてあります。

また、paramsの'q'ではツイートの条件指定が可能です（[こちら](https://gist.github.com/cucmberium/e687e88565b6a9ca7039)を参考にしました）。これを利用すると、to:ユーザ名　を検索クエリに含めることで特定のアカウントに対するツイートのみを取得することができます。この中からリプライを探し出すわけですね。

Search/tweetsでは1回のリクエストで最大100件のツイートを取得可能です。for文で回すことで100×N個のツイートを取得するようにしています（そんな数のリプライが付くツイートはなかなかないと思いますが）。ただし、15分で180回までしかリクエストできないのでアクセス可能回数が残り1になったらリセット時間までtime.sleep()で待機するようにしています。

# 注意点
search/tweetsの仕様として、Standard APIでは１週間前までのツイートしか取得することができません。１ヶ月前のツイートに対するリプライを取得しようとか思ったら別の方法が必要になると思います。