※2019年3月時点の情報です
Twitterデータを分析しようと思い、データ収集方法を調べました。
地味に色々なサイトを見て回ることになったので備忘録としてまとめておきます。
準備
Twitterは事前の承諾なしのスクレイピングを禁止しているので、APIを使う必要があります。
Twitter APIの登録については以下の記事がわかりやすいです。
Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ
300文字の英作文では以下の記事が役に立ちました。
TwitterAPIの申請に必要な英作文をユルく和訳してみた
私の場合、新規に作成したアカウントで申請から4時間程度で承認されました。
収集
使うライブラリ
- OAuth1Session
- python-twitter
- tweepy
などからどれか好きなもの。どれを使っても大差はないと思います。
今回は一番使用例が多いように感じたOAuth1Sessionを使います。
APIを投げるURL
https://api.twitter.com/1.1/search/tweets.json?tweet_mode=extended
末尾に?tweet_mode=extendedをつけない場合、ツイートが140文字を超えると省略されたものが取得されてしまいます。
シンプルな取得コード例
from requests_oauthlib import OAuth1Session
# トークン情報
CONSUMER_KEY = 'xxxxxxxxxx'
CONSUMER_SECRET = 'xxxxxxxxxx'
ACCESS_TOKEN = 'xxxxxxxxxx'
ACCESS_TOKEN_SECRET = 'xxxxxxxxxx'
# OAuth認証
api = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
# 「hogehoge」が含まれるツイートを5つ取得
url = 'https://api.twitter.com/1.1/search/tweets.json?tweet_mode=extended'
params = {'q': 'hogehoge', 'count': 5}
req = api.get(url, params=params)
Twitter開発者ドキュメント日本語訳
公式ドキュメントの日本語訳があるので非常に参考になります。
ただしドキュメントに書かれていない検索術などがあるようです。
- params
paramsで検索条件を設定します。
クエリの具体例は以下がわかりやすいです。
https://gist.github.com/cucmberium/e687e88565b6a9ca7039
- 戻り値req
- ステータス部 req.status_code
- ヘッダ部 req.headers
- テキスト部 req.text
正常に取得できている場合、取得ステータス(req.status_code)の値は200です。
取得ツイート情報はreq.textに指定数(count)分だけjson形式で入ります。
JSONの例は以下です。
https://github.com/twitterdev/tweet-updates/blob/master/samples/initial/extended_extended_14001.json
サンプルコード
ハッシュタグhogehogeが含まれるツイートをリツイートを除外して新しいものから順に取得できるだけ取得。
取得したデータはPandasのDataFrameにしてPickleで保存。
import os
import time
import json
import pandas as pd
from requests_oauthlib import OAuth1Session
CONSUMER_KEY = 'xxxxxxxxxx'
CONSUMER_SECRET = 'xxxxxxxxxx'
ACCESS_TOKEN = 'xxxxxxxxxx'
ACCESS_TOKEN_SECRET = 'xxxxxxxxxx'
class TwitterApi:
def __init__(self, search_word, count):
self.twitter_api = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_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
def get_next_tweets(self):
req = self.twitter_api.get(self.url, params=self.params)
# 正常に取得できている場合
if req.status_code == 200:
self.x_rate_limit_remaining = req.headers['X-Rate-Limit-Remaining']
self.x_rate_limit_reset = req.headers['X-Rate-Limit-Reset']
self.tweets = json.loads(req.text)
self.tweet_num = len(self.tweets['statuses'])
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:
return False
# 検索語
search_word = '#hogehoge'
count = 100
twitter_api = TwitterApi(search_word, count)
tweets_df = pd.DataFrame([])
while twitter_api.tweet_num > 0:
ret = twitter_api.get_next_tweets()
if twitter_api.tweet_num == 0:
break
if ret:
# PandasのDataFrameに変換
df = pd.io.json.json_normalize(twitter_api.tweets['statuses'])
tweets_df = pd.concat([tweets_df, df], axis=0)
print('アクセス可能回数:', twitter_api.x_rate_limit_remaining, ' リセット時間:', twitter_api.x_rate_limit_reset)
else:
# エラー時はとりあえず60秒待って再取得
print('Error!, Wait 60s...')
time.sleep(60)
# ツイート日時列をTimeStamp型にして降順に並び替え、Pickle保存
tweets_df['created_at'] = pd.to_datetime(tweets_df['created_at'])
tweets_df = tweets_df.sort_values('created_at').reset_index(drop=True)
tweets_df.to_pickle('saved_tweets_df.pickle')
本当はアクセス可能回数やリセット時間をちゃんと見て再取得するべきですが、それらが正常に取得できない場合があるみたいなのでひとまず適当に再取得しています。
ツイートIDは新しいものほど大きな値が振られているので、再取得時には前回取得した最小IDの値より小さいmax_id条件を設定します。
その他参考
末尾に?tweet_mode=extendedをつけない場合のツイート取得
http://ailaby.com/twitter_api/#id7_2