【GetOldTweets-python】無料でTwitterの過去７日より前のツイートを取得してみる

# はじめに
研究のデータ収集のために大量のツイートを取得したかったのですが、Twitterの無料のSearch APIには
・180リクエスト/15分 かつ 100ツイート/１リクエストという取得件数の制限
・過去７日間分のみという取得対象期間の制限
があり、非常に不便でした。特に取得対象期間が過去７日間分のみなのがツラい...

有料のAPIを用いれば
・900リクエスト/15分 かつ 500ツイート/１リクエスト
・過去30日間分
まで機能を拡張できるのですが、その前に`GetOldTweets-python`と呼ばれるツイート収集用のパッケージ（無料）を見つけたので、まずこれを使ってみることに。

参考文献が少ないようなので記事にしました。

パッケージはこちら
https://github.com/Jefferson-Henrique/GetOldTweets-python

# 環境
Ubuntu 18.04.2
Python3.7
Anaconda3 5.3.1
Jupyter Notebook

# インストール
Githubからパッケージをクローンした後、`GetOldTweets-python`フォルダ内にある`got3`フォルダを丸ごと、`〇〇/anaconda3/lib/python3.7/site-packages/`の中に置きます。
Anacondaを特別なディレクトリ下にインストールしていなければ、`~/anaconda3/lib/python3.7/site-packages/`になるかと思います。
Python2系を使用している場合は、`got3`ではなく`got`を用いるそうです。

```
git clone https://github.com/Jefferson-Henrique/GetOldTweets-python
cd GetOldTweets-python
mv got3 ~/anaconda3/lib/python3.7/site-packages
```

また、pyqueryというライブラリも必要になるようなので、

```
pip install pyquery
```

を実行。

これでAnacondaのPythonから、ツイートの取得に必要なモジュールを呼び出すことができます。

# バグの修正（2019/5/29現在）
2019/5/29現在では、（Python 3を使う場合）デフォルトのコードだと重大なバグが多い...（あまり使われない原因かも）。思うような結果が取得できず、修正に苦戦しました。
今回は僕が不便に感じた&直せる箇所のみ修正しましたが、人によっては他にも直したい箇所が見つかるかもしれません。

## １箇所目
ドキュメントでは取得できることになっている、ツイート投稿者のユーザー名（アカウントの@付き文字列）を取得できませんでした。

`got3/manager/TweetManager.py`における59行目の

```python
tweet.username = usernameTweet
```

を

```python
tweet.username = re.split('/', permalink)[1]
```

に変更すれば直ります。


## ２箇所目
これまたドキュメントでは取得できることになっている、`ハッシュタグ(#)` や `メンション(@)`の内容が取得できませんでした。
原因としては、ツイートのテキストを整形した際に、`#`や`@`の後に半角スペースが挿入されてしまうためであると思われます。
とりあえずこのバグを直したところ、`@`に関して、`@`の後にスペースor改行が出るまで全ての文字をメンションの対象として取得してしまうという新たなバグが発生。

`got3/manager/TweetManager.py`における66~67行目の

```python
tweet.mentions = " ".join(re.compile('(@\\w*)').findall(tweet.text))
tweet.hashtags = " ".join(re.compile('(#\\w*)').findall(tweet.text)) 
```

というコードを

```python
tweet.mentions = " ".join(re.compile(r'(@\s[a-zA-Z0-9]*)').findall(tweet.text))
tweet.hashtags = " ".join(re.compile(r'(#\s\w*)').findall(tweet.text))
```

というコードに修正することで、まとめて解決できました。

## 補足
修正できていないのですが、他に不便に感じた箇所を記入しておきます。

* 取得したツイートが、あるツイートに対する返信ツイートだった場合、メンションの内容を取得できない（返信先を取得できない）
* キーワード指定で検索した際に、キーワードがユーザーの名前（アカウントの@付き文字列**ではない方の名前**）に含まれる場合、内容にかかわらずそのユーザーのツイートを取得してしまう

## 追記

got3/manager/TweetManager.pyの38行目を、

```python
usernameTweet = tweetPQ.attr("data-name")
```

に変更するとアカウント文字列（@付きの文字列）ではなく、ユーザーの名前でツイートを取得できるようです。ご参考までに。
コメント欄にてアドバイスして頂いた@gknさんありがとうございます。

# 実行
実際に試したコードを示します。

```python
# -*- coding: utf-8 -*-
import got3 as got

def print_tweets(tweets):
    print("取得件数：", len(tweets))
    for tweet in tweets: 
        print("---------------------------------")
        print("ツイートID：", tweet.id)
        print("ツイートURL：", tweet.permalink)
        print("アカウントの文字列：", tweet.username)
        print(tweet.text)
        print("投稿日：", tweet.date)
        print("リツイート数：", tweet.retweets)
        print("いいねの数：", tweet.favorites)
        if tweet.mentions: 
            print("メンションの内容：", tweet.mentions)
        if tweet.hashtags:
            print("ハッシュタグの内容", tweet.hashtags)

# アカウントの文字列で取得
tweetCriteria = got.manager.TweetCriteria().setUsername("sugoiyamanaka").setMaxTweets(5)
tweets = got.manager.TweetManager.getTweets(tweetCriteria)
print("---------------------------------")
print("①アカウントの文字列で取得")
print_tweets(tweets)

# キーワードで取得
tweetCriteria = got.manager.TweetCriteria().setQuerySearch("ポケモン").setMaxTweets(5)
tweets = got.manager.TweetManager.getTweets(tweetCriteria)
print("---------------------------------")
print("②キーワードで取得")
print_tweets(tweets)

# 複雑なクエリで取得
tweetCriteria = got.manager.TweetCriteria().setQuerySearch("西武 AND (ソフトバンク OR 楽天) -失点 #野球").setMaxTweets(5)
tweets = got.manager.TweetManager.getTweets(tweetCriteria)
print("---------------------------------")
print("③複雑なクエリで取得")
print_tweets(tweets)

# 期間を指定して取得
tweetCriteria = got.manager.TweetCriteria().setUsername("sugoiyamanaka").setSince("2018-11-10").setUntil("2018-11-30")
tweets = got.manager.TweetManager.getTweets(tweetCriteria)
print("---------------------------------")
print("④期間を指定して取得")
print_tweets(tweets)
```

各ツイートに関して取得できるデータは以下のとおりです。

* id (str)
* permalink (str)
* username (str)
* text (str)
* date (date)
* retweets (int)
* favorites (int)
* mentions (str)
* hashtags (str)
* geo (str)

TwitterCriteria()において指定できるパラメータは以下の通りです。

* setUsername (str)： アカウントの文字列（@を除いて指定する）
* setSince (str. "yyyy-mm-dd")：検索対象の下限日時（〜日から）
* setUntil (str. "yyyy-mm-dd")：検索対象の上限日時（〜日まで）
* setQuerySearch (str)： 検索クエリ
* setTopTweets (bool)： Trueの場合はトップツイートのみを取得する
* setNear(str)： ツイートが投稿されたエリア
* setWithin (str)： "setNear"の位置からの距離半径（例:15mi）
* setMaxTweets (int)： 取得するツイートの最大数（未設定の場合は全てのツイートを取得）

上記コード内の`③複雑なクエリ`では`AND` `OR` `-` `#` といった演算子を使用しましたが、Twitter公式APIで使用できる他の演算子も使えそうです。
参考：http://westplain.sakuraweb.com/translate/twitter/Documentation/REST-APIs/Public-API/The-Search-API.cgi

実際に実行してみると、過去７日より前のツイートがきちんと取得できることを確認できると思います。また、setMaxTweetsを30,000に設定しても、ちゃんと取得してくれました（時間はかかる）。
便利ですね。

# 長所
・APIキーが必要ない（＝Twitter Developerアカウントの作成および審査が不要）
・無料で制限なしにツイートを取得できる

# 短所
* Twitter公式のAPIに比べると、取得できるデータの種類が乏しい
* （パッケージが更新されない限り）バグが多いため、必要に応じて自分で直さなければならない
* 取得数が多いと取得にかなり時間がかかる

# 感想
導入時に自分でコードを修正するのは面倒かもしれませんが、Twitter Developerの審査が不要ですし、各ツイートの簡単なデータであれば無制限に取得できるので、ツイートを遊びや簡単なアプリの制作に使用する場合は、公式APIよりもGetOldTweets-pythonの方が便利かもしれません。
取得できるデータの種類は少ないので、高度な事がしたいようであれば、素直に有料のAPIを導入したほうが良さそうですね。

以上