Colabでツイートデータを取得してスプレッドシートに読み書きする

こんにちは

この記事ではGoogle ColaboratoryからTwitterAPIを使用してツイートを取得しGoogleスプレットシートに保存（追記も）する方法を紹介します。

初めてのコードを共有する記事なので緊張しています。お手柔らかにお願いします。

### 参考サイト

+ [スタバのTwitterデータをpythonで大量に取得し、データ分析を試みる その１](https://qiita.com/kenmatsu4/items/23768cbe32fe381d54a2)
+ [Google ColabからGoogle スプレッドシートを扱うときのサンプルコードまとめ](https://qiita.com/safa/items/bfa52430f920ac562bec)
+ [pandas.Dataframeからスプレッドシートに簡単に反映するgspread-dataframeライブラリ](https://qiita.com/zakoyama_com/items/23ad7c4ab552d410590e)

### twitterAPI取得

まずはtwitter APIを申請して承認してもらいAPIキーを用意します。

ググって上位に出てくるサイトを元に作業したらすぐに承認してもらえました。（申請内容は例文でなく正直に分析の勉強する旨を書きました。

### tweet取得部分

``` python
KEYS = {
        'consumer_key'   :'**********',
        'consumer_secret':'**********',
        'access_token'   :'**********',
        'access_secret'  :'**********',
        }
twitter = OAuth1Session(KEYS['consumer_key'],
                        KEYS['consumer_secret'], 
                        KEYS['access_token'], 
                        KEYS['access_secret'])
# 本文がurlとかで140字以上になってるのでそこまで引っ張ってくるurl
url = "https://api.twitter.com/1.1/search/tweets.json?tweet_mode=extended"
params = {'q': 'hogehoge', 'count': '100', 'result_type': 'recent', 'exclude': 'retweets'}

response = twitter.get(url, params=params)
```

これで動けばapiの取得、KEY値はあってると思います。

### スプレッドシートとの連携

``` python
import pandas as pd
from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread
from gspread_dataframe import get_as_dataframe, set_with_dataframe

# スプレッドシートを扱うための認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# 読み取りたいスプレッドのid
# https://docs.google.com/spreadsheets/d/この部分/edit#gid=0
ss_id = "**************************************"

workbook = gc.open_by_key(ss_id)
worksheet = workbook.get_worksheet(0)

# sheetからdataframeで受け取る
spread_df = get_as_dataframe(worksheet, skiprows=0, header=0, index_col=0)
spread_df =spread_df[spread_df.index.notnull()]

# スプレッドに保存
set_with_dataframe(worksheet, spread_df.reset_index())

```

一旦スプレッドとの連携の作業が必要です。  

### ソースコード全体

``` python

# [ツイート取得]参考サイト
# https://qiita.com/kenmatsu4/items/23768cbe32fe381d54a2
# =========================================================
from requests_oauthlib import OAuth1Session
import json, datetime, time
from collections import defaultdict
import numpy as np
import pandas as pd
# [スプレッド連携]参考サイト
# https://qiita.com/safa/items/bfa52430f920ac562bec
# https://qiita.com/zakoyama_com/items/23ad7c4ab552d410590e
# =========================================================
from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread
from gspread_dataframe import get_as_dataframe, set_with_dataframe

# dictionary型
KEYS = {
        'consumer_key'   :'',
        'consumer_secret':'',
        'access_token'   :'',
        'access_secret'  :'',
        }

class TwitterApi:
    # 初期化
    def __init__(self, search_word, count):
        # 接続情報の設定
        self.twitter = OAuth1Session(KEYS['consumer_key'], KEYS['consumer_secret'], KEYS['access_token'], KEYS['access_secret'])
        self.url     = "https://api.twitter.com/1.1/search/tweets.json?tweet_mode=extended"
        self.params = {'q': search_word, 'count': count, 'result_type': 'recent', 'exclude': 'retweets'}
        self.tweet_num = count

    # Tweet取得
    def get_tweet_data(self):

        # Tweetデータの取得
        response = self.twitter.get(self.url, params=self.params)
    
        # 成功した場合
        if response.status_code == 200: 
            self.tweets = json.loads(response.text)
        
            self.limit = response.headers['X-Rate-Limit-Remaining']
            self.reset = response.headers['X-Rate-Limit-Reset']
        
            self.tweet_num = len(self.tweets['statuses'])
            print(self.tweet_num)
            if self.tweet_num == 0:
                return True

            self.max_id = self.tweets['statuses'][0]['id']
            self.min_id = self.tweets['statuses'][-1]['id']
            next_max_id = self.min_id - 1
            self.params['max_id'] = next_max_id

            return True

        else:
            print ("Error: %d" % response.status_code)
            return False

# スプレッド操作
# =========================================================
# スプレッドシートを扱うための認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# 読み取りたいスプレッドのid
# https://docs.google.com/spreadsheets/d/この部分/edit#gid=0
ss_id = ""

workbook = gc.open_by_key(ss_id)
worksheet = workbook.get_worksheet(0)

# sheetからdataframeで受け取る
spread_df = get_as_dataframe(worksheet, skiprows=0, header=0, index_col=0)
spread_df =spread_df[spread_df.index.notnull()]

# twitterAPI操作
# =========================================================

# =========================================================
search_word = "'pytorch' OR 'keras' lang:ja"
count = 100
# =========================================================

twitter_api = TwitterApi(search_word, count)

tweets_df = pd.DataFrame([])

while twitter_api.tweet_num > 0:
    result = twitter_api.get_tweet_data()
    
    # エラーが出たら終了
    if result == False:
        print('error')

    # 取得数が0だと終了
    if twitter_api.tweet_num == 0:
        print('Finish')
        break

    # DataFrameに変換
    if result:
        df = pd.io.json.json_normalize(twitter_api.tweets['statuses'])
        tweets_df = pd.concat([tweets_df, df], axis=0)
        print('アクセス可能回数:', twitter_api.limit, ' リセット時間:', twitter_api.reset)

    # エラー時はとりあえず180秒待って再取得
    if int(twitter_api.limit) == 0:
        print('limit is Zero!, Wait 180s...')
        time.sleep(180)

# ツイート日時列をTimeStamp型にして昇順に並び替え
tweets_df['created_at'] = pd.to_datetime(tweets_df['created_at'])
tweets_df = tweets_df.sort_values('created_at').reset_index(drop=True)

spread_df = pd.concat([spread_df, tweets_df])
spread_df = spread_df.drop_duplicates(subset='id', keep='last')
spread_df.reset_index(inplace=True, drop=True)
# スプレッドに保存
set_with_dataframe(worksheet, spread_df.reset_index())
```

追記をしたいので、``` spread_df.drop_duplicates(subset='id', keep='last')``` を使いました。  
取得済みツイートの続き以降から取得するのもスマートと思いますが出来ませんでした。  
今回のコードだと検索ワードを変えたときに対応はできるはず

### 結果

無事取得できて、アップデートインサート的な動きもできました。