Twitter APIでリプライを集めたい！

##環境
ubuntu 16.04 LTS
python 3.7.3

##やること
pythonでTwitter APIを利用し、特定のツイートに対するリプライを取得します。ただし、現在公開されているAPIには、特定のツイートを指定してリプライを全部取得できるような機能はありません。そこでsearch/tweetsを使って、リプライを抽出する方法を紹介します。

##リプライ抽出までの流れ
Twitter APIやpythonにおけるsearch/tweetsの基本的な使い方については前記事のhttps://qiita.com/h_tashiro/items/ed119c237f5595c3d7b8 をご覧ください。
###①ユーザー・ツイートIDを調べる
ツイートのURLの末尾にある数字がツイートIDです。
###②ユーザーIDでツイートを検索する
テキストにユーザーIDが含まれるツイート(=そのユーザーへのリプライ)を取得します。
ここで注意点として、コメント付きの引用RTでなく通常のRTでも、以下のような形でレスポンスに含まれるのですが、その際元のツイートをしたユーザーのIDもテキストに含まれてしまいます。そのため「exclude:retweets」というオプションを検索文字列に追加しあらかじめRTを除いておきます。

```
# 「Yahoo」で検索した例
'RT @YahooNewsTopics: 【不発弾に遺骨 終わらぬ沖縄戦】\nhttps://t.co/hbLTEnZvoY\n\n戦後74年目の「慰霊の日」を迎えた。激戦地だった沖縄では不発弾処理や戦没者の遺骨収集、所有者不明土地の情報収集など課題が残されたままで、「戦後」はまだ終…', 
'RT @YahooNewsTopics: 【梶と竹達「ポプテ婚?」と話題】\nhttps://t.co/eZTXGY3nNI\n\n声優の梶裕貴と竹達彩奈が、きょう23日に結婚したことを自身のツイッターで発表した。梶と竹達はアニメ「ポプテピピック」で同じ「ピピ美」役を担当しており、フ…'
```
###③特定のツイートに対するリプライを抽出する
取得したデータについて、statuses直下のin_reply_to_user_id_strでリプライかどうか判別ができます。リプライの場合は対象となるツイートのID、通常のツイートの場合はnullとなっていますので、①で調べたツイートIDと一致するものを抽出すればオッケーです。

###補足
不適切と判断されたアカウントやいわゆる鍵アカウントのツイートはsearch/tweetsでヒットしません。なのでアプリ上で表示されているリプライ数に対して、実際に取得できるリプライ数は1,2割程度少なくなる場合が多いです。
またAPIの仕様上1週間前までのツイートしか検索できないことに注意してください。
###対象とするツイート
こちらのツイートに多くのリプライが寄せられていたので対象にさせて頂きます。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">いつも応援してくださるファンの皆様へ<br><br>この度、かねてよりお付き合いしておりました女性と入籍致しました事をご報告させて頂きます。そして実は新たな命を授かり、より一層俳優として精進し真摯に向き合って参りたいと思っております。</p>&mdash; 古川雄輝 (@yuki_furukawaHP) <a href="https://twitter.com/yuki_furukawaHP/status/1142628461053280257?ref_src=twsrc%5Etfw">2019年6月23日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
##コード
1回あたりの検索数(最大100)×リクエスト回数（15分毎に最大180）で取得できるツイートの最大数が決まりますが、対象のリプライだけでなくユーザーIDがテキストに含まれる1週間前までの全てのツイートを取得するので、表示されているリプライ数に対して余裕を持ってパラメーターを決めてください。

```python
import urllib
from requests_oauthlib import OAuth1Session, OAuth1
import requests
import sys

def main():

    # APIの秘密鍵
    CK = 'xxxxxxxxxxxxxxxx'
    CKS = 'xxxxxxxxxxxxxxxx'
    AT = 'xxxxxxxxxxxxxxxx'
    ATS = 'xxxxxxxxxxxxxxxx'
    # ユーザー・ツイートID
    user_id = '@@yuki_furukawaHP'
    tweet_id = '1142628461053280257' # str型で指定
    # 検索時のパラメーター
    count = 100 # 一回あたりの検索数(最大100/デフォルトは15)
    range = 100 # 検索回数の上限値(最大180/15分でリセット)
    # ツイート検索・リプライの抽出
    tweets = search_tweets(CK, CKS, AT, ATS, user_id, tweet_id, count, range)
    # 抽出結果を表示
    print(tweets[0:5])


def search_tweets(self, CK, CKS, AT, ATS, user_id, tweet_id, count, range):
    # 文字列設定
    user_id += ' exclude:retweets' # RTは除く
    user_id = urllib.parse.quote_plus(user_id)
    # リクエスト
    url = "https://api.twitter.com/1.1/search/tweets.json?lang=ja&q="+user_id+"&count="+str(count)
    auth = OAuth1(CK, CKS, AT, ATS)
    response = requests.get(url, auth=auth)
    data = response.json()['statuses']
    # ２回目以降のリクエスト
    cnt = 0
    reply_cnt = 0
    tweets = []
    while True:
        if len(data) == 0:
            break
        cnt += 1
        if cnt > range:
            break
        for tweet in data:
            if tweet['in_reply_to_status_id_str'] == tweet_id: # ツイートIDに一致するものを抽出
                tweets.append(tweet['text'])  # ツイート内容
                reply_cnt += 1
            maxid = int(tweet["id"]) - 1
        url = "https://api.twitter.com/1.1/search/tweets.json?lang=ja&q="+user_id+"&count="+str(count)+"&max_id="+str(maxid)
        response = requests.get(url, auth=auth)
        try:
            data = response.json()['statuses']
        except KeyError: # リクエスト回数が上限に達した場合のデータのエラー処理
            print('上限まで検索しました')
            break
    print('検索回数 :', cnt)
    print('リプライ数 :', reply_cnt)
    return tweets


if __name__ == '__main__':
    main()
```
##実行結果
見やすいようにリスト内は改行しています。URLは伏せてます。

```
検索回数 : 32
リプライ数 : 1061
['@yuki_furukawaHP おめでとうございます( /// ´ิϖ´ิ/// )\n結婚と子供とダブルですね♪\n こんなに好きになった俳優さん初めてだったのでついに既婚者かぁと失恋気分ですが、古川くんの幸せが嬉しい！これからも… https://t.co/xxxxxx',
 '@yuki_furukawaHP ショック……\nしかもベビーまで…\nこんなイケメンと結婚とか…\nはう…\n結婚しないで…',
 '@yuki_furukawaHP おめでとうございます！！', 
'@yuki_furukawaHP おめでとうございます㊗🎉🎉🎉', 
'@yuki_furukawaHP おめでとう🍾🎉']
```
##conversation/show/:idについて
「現在公開されているAPIには、特定のツイートを指定してリプライを全部取得できるような機能はありません」といいましたが、非公開のAPIとしてconversation/show/:idが存在しています。これを使えばツイートのIDを指定してそれに対するリプライをサクッと取得できると思われますが、一般ユーザーのコンシューマーキーでは認証されません。調べてみると公式のコンシューマーキー（過去に流出していて調べればすぐ出てきます）や非公式クライアントのコンシューマーキーだと認証が通るようですが、あくまで非公開のAPIですので、やはりsearch/tweetsを使うのが正攻法かなと思います。