スタバのTwitterデータをpythonで大量に取得し、データ分析を試みる その１

いつもMac Book Airによるドヤ顔で、しかも長時間滞在でご迷惑をかけつつお世話になっていますスターバックスさんに感謝の意を表して、データ分析をして何かお役に立てればと思い、Twitterから「スタバ」が本文に含まれるツイートを大量に取得して何かデータ分析から知見が得られるか試してみよう、という記事です。
ステマではありませんが、スタバさんに恩返しという意味ではステマかもしれません(・ω・)


***
その１：Twitter REST APIsでデータを取り込みmongoDBにインポート（今回）
http://qiita.com/kenmatsu4/items/23768cbe32fe381d54a2

その２：取得したTwitterデータからスパムの分離
http://qiita.com/kenmatsu4/items/8d88e0992ca6e443f446

その３：ある日を境にツイート数が増えたわけは？
http://qiita.com/kenmatsu4/items/02034e5688cc186f224b

その４：Twitterにひそむ位置情報の視覚化
http://qiita.com/kenmatsu4/items/114f3cff815aa5037535
***

##1.Twitter APIに接続するためのアカウント情報取得##
Google先生で[「twitter api アカウント」](https://www.google.co.jp/search?q=twitter+api+%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88)と検索すると登録の仕方がわかりやすく書いてあるサイトがたくさん出てくるので、それらを参照してAPIにアクセスするための情報（特にconsumer_key、consumer_secret、access_token、access_secret）を取得してください。
##2.各種必要ライブラリ等のインストール##
iPython等の基本的なPython環境は整っている前提とします。[ここ](http://nbviewer.ipython.org/github/cs109/2014/blob/master/homework/HW0.ipynb#Python-Libraries)にあるライブラリがあれば大体大丈夫と思います。
Twitter REST APIsを使うための認証ライブラリもインストールします。

```sh
pip install requests_oauthlib
```

また、データの格納にmongoDBを使うので[こちら](http://docs.mongodb.org/manual/tutorial/install-mongodb-on-os-x/)や[こちら](http://qiita.com/hajimeni/items/3c93fd981e92f66a20ce)などを参考にインストール。mongoDBの概要は[「MongoDB の薄い本」](http://www.cuspy.org/diary/2012-04-17/the-little-mongodb-book-ja.pdf)などを参照。

PythonからmongoDBにアクセスするため、pymongoも導入します。

```sh
pip install pymongo
```

##3.初期化処理##

```py
from requests_oauthlib import OAuth1Session
from requests.exceptions import ConnectionError, ReadTimeout, SSLError
import json, datetime, time, pytz, re, sys,traceback, pymongo
#from pymongo import Connection     # Connection classは廃止されたのでMongoClientに変更 
from pymongo import MongoClient
from collections import defaultdict
import numpy as np

KEYS = { # 自分のアカウントで入手したキーを下記に記載
        'consumer_key':'**********',
        'consumer_secret':'**********',
        'access_token':'**********',
        'access_secret''**********',
       }

twitter = None
connect = None
db      = None
tweetdata = None
meta    = None

def initialize(): # twitter接続情報や、mongoDBへの接続処理等initial処理実行
    global twitter, twitter, connect, db, tweetdata, meta
    twitter = OAuth1Session(KEYS['consumer_key'],KEYS['consumer_secret'],
                            KEYS['access_token'],KEYS['access_secret'])
#   connect = Connection('localhost', 27017)     # Connection classは廃止されたのでMongoClientに変更 
    connect = MongoClient('localhost', 27017)
    db = connect.starbucks
    tweetdata = db.tweetdata
    meta = db.metadata
    
initialize()
````

##4.Tweetを検索##
下記のコードで「スタバ」を本文に含むツイートをmongoDBにインポートしていきます。

```py3
# 検索ワードを指定して100件のTweetデータをTwitter REST APIsから取得する
def getTweetData(search_word, max_id, since_id):
    global twitter
    url = 'https://api.twitter.com/1.1/search/tweets.json'
    params = {'q': search_word,
              'count':'100',
    }
    # max_idの指定があれば設定する
    if max_id != -1:
        params['max_id'] = max_id
    # since_idの指定があれば設定する
    if since_id != -1:
        params['since_id'] = since_id
    
    req = twitter.get(url, params = params)   # Tweetデータの取得

    # 取得したデータの分解
    if req.status_code == 200: # 成功した場合
        timeline = json.loads(req.text)
        metadata = timeline['search_metadata']
        statuses = timeline['statuses']
        limit = req.headers['x-rate-limit-remaining'] if 'x-rate-limit-remaining' in req.headers else 0
        reset = req.headers['x-rate-limit-reset'] if 'x-rate-limit-reset' in req.headers else 0              
        return {"result":True, "metadata":metadata, "statuses":statuses, "limit":limit, "reset_time":datetime.datetime.fromtimestamp(float(reset)), "reset_time_unix":reset}
    else: # 失敗した場合
        print ("Error: %d" % req.status_code)
        return{"result":False, "status_code":req.status_code}

# 文字列を日本時間2タイムゾーンを合わせた日付型で返す
def str_to_date_jp(str_date):
    dts = datetime.datetime.strptime(str_date,'%a %b %d %H:%M:%S +0000 %Y')
    return pytz.utc.localize(dts).astimezone(pytz.timezone('Asia/Tokyo'))

# 現在時刻をUNIX Timeで返す
def now_unix_time():
    return time.mktime(datetime.datetime.now().timetuple())
```

ここからがツイート取得ループです。

```py
#-------------繰り返しTweetデータを取得する-------------#
sid=-1
mid = -1 
count = 0
 
res = None
while(True):    
    try:
        count = count + 1
        sys.stdout.write("%d, "% count)
        res = getTweetData(u'スタバ', max_id=mid, since_id=sid)
        if res['result']==False:
            # 失敗したら終了する
            print "status_code", res['status_code']
            break
        
        if int(res['limit']) == 0:    # 回数制限に達したので休憩
            # 日付型の列'created_datetime'を付加する
            print "Adding created_at field."
            for d in tweetdata.find({'created_datetime':{ "$exists": False }},{'_id':1, 'created_at':1}):
                #print str_to_date_jp(d['created_at'])
                tweetdata.update({'_id' : d['_id']}, 
                     {'$set' : {'created_datetime' : str_to_date_jp(d['created_at'])}})
            #remove_duplicates()
            
            # 待ち時間の計算. リミット＋５秒後に再開する
            diff_sec = int(res['reset_time_unix']) - now_unix_time()
            print "sleep %d sec." % (diff_sec+5)
            if diff_sec > 0:
                time.sleep(diff_sec + 5)
        else:
            # metadata処理
            if len(res['statuses'])==0:
                sys.stdout.write("statuses is none. ")
            elif 'next_results' in res['metadata']:
                # 結果をmongoDBに格納する
                meta.insert({"metadata":res['metadata'], "insert_date": now_unix_time()})
                for s in res['statuses']:
                    tweetdata.insert(s)
                next_url = res['metadata']['next_results']
                pattern = r".*max_id=([0-9]*)\&.*"
                ite = re.finditer(pattern, next_url)
                for i in ite:
                    mid = i.group(1)
                    break
            else:
                sys.stdout.write("next is none. finished.")
                break
    except SSLError as (errno, request):
        print "SSLError({0}): {1}".format(errno, strerror)
        print "waiting 5mins"
        time.sleep(5*60)
    except ConnectionError as (errno, request):
        print "ConnectionError({0}): {1}".format(errno, strerror)
        print "waiting 5mins"
        time.sleep(5*60)
    except ReadTimeout as (errno, request):
        print "ReadTimeout({0}): {1}".format(errno, strerror)
        print "waiting 5mins"
        time.sleep(5*60)
    except:
        print "Unexpected error:", sys.exc_info()[0]
        traceback.format_exc(sys.exc_info()[2])
        raise
    finally:
        info = sys.exc_info()

```
<br>
##5.Twitter REST APIデータ構造##
Twitter REST APIsの"[GET search/tweets](https://dev.twitter.com/rest/reference/get/search/tweets)"で得られたデータの構造は下記の通りです。
###TwitterListResponseの構造###
Tweet情報の主な要素の説明です。

<table>
<thead>
　　<tr>
    <td style="text-align: left ", colspan="2">アイテム</td>
　　　　<th style="text-align: left">説明</th>
　　</tr>
</thead>
<tbody>
  <tr>
    <td style="text-align: left ", colspan="2">id</td>
    <td style="text-align: left">ツイートID。新しいものが番号が古く、古いものが番号が若い。このIDより大きい、小さい、を検索時に指定してそれ以降、以前のツイートの取得ができる。</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">id_str</td>
    <td style="text-align: left">"id"の文字列版らしいのだが、もともと文字列で取得するので詳細不明。</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">user</td>
    <td style="text-align: left">ユーザー情報。以下の要素を持つ(代表的なもののみピックアップ)</td>
  </tr>
  <tr>
    <td style="text-align: center">　　</td>
    <td style="text-align: center">id</td>
    <td style="text-align: left">ユーザーID。通常目にすることない数字のID。</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">name</td>
    <td style="text-align: left">長い方のユーザーの名前。</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">screen_name</td>
    <td style="text-align: left">@などで指定するときに使うユーザー名</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">description</td>
    <td style="text-align: left">ユーザーの説明情報。プロフィール的な文章。</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">friends_count</td>
    <td style="text-align: left">フォロー数</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">followers_count</td>
    <td style="text-align: left">フォロワー数</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">statuses_count</td>
    <td style="text-align: left">ツイート数(リツイート含む)</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">favourites_count</td>
    <td style="text-align: left">お気に入り数</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">location</td>
    <td style="text-align: left">住んでいるところ</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">created_at</td>
    <td style="text-align: left">このユーザの登録日</td>
  </tr>  <tr>
    <td style="text-align: left ", colspan="2">text</td>
    <td style="text-align: left">ツイート本文</td>
  </tr>


  <tr>
    <td style="text-align: left ", colspan="2">retweeted_status</td>
    <td style="text-align: left">リツイートか否か(True:リツイート/False:通常ツイート)</td>
  </tr>

  <tr>
    <td style="text-align: left ", colspan="2">retweeted</td>
    <td style="text-align: left">リツイートされたか否か(True/False)</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">retweet_count</td>
    <td style="text-align: left">リツイートされた回数</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">favorited</td>
    <td style="text-align: left">お気に入りされたか否か(True/False)</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">favorite_count</td>
    <td style="text-align: left">お気に入りされた数</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">coordinates</td>
    <td style="text-align: left">緯度経度</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">entities</td>
    <td style="text-align: left">以下に示す追加的情報</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">symbols</td>
    <td style="text-align: left"></td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">user_mentions</td>
    <td style="text-align: left">本文中に@で指定されたユーザー情報</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">hashtags</td>
    <td style="text-align: left">本文に記載のあるハッシュタグ</td>
  </tr>
  <tr>
    <td style="text-align: center"></td>
    <td style="text-align: center">urls</td>
    <td style="text-align: left">本文に記載されたURL情報</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">source</td>
    <td style="text-align: left">ツイートを行ったアプリ・サイト等の情報</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">lang</td>
    <td style="text-align: left">言語情報</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">created_at</td>
    <td style="text-align: left">ツイート日時</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">place</td>
    <td style="text-align: left">ツイートに関連した場所情報</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">in_reply_to_screen_name</td>
    <td style="text-align: left">ツイートがリプライだった時のツイート元のユーザー名</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">n_reply_to_status_id</td>
    <td style="text-align: left">ツイートがリプライだった時のツイート元のツイートID</td>
  </tr>
  <tr>
    <td style="text-align: left ", colspan="2">in_reply_to_status_id_str</td>
    <td style="text-align: left">n_reply_to_status_idの文字列版</td>
  </tr>
</tbody>
</table>

###metadataの構造###
'https://api.twitter.com/1.1/search/tweets.json'で検索した時に返されるmetadataの説明です。

|アイテム|説明|
|:------:|:-------|
|query|検索ワード|
|count|１回の検索でいくつのツイートを取得したか|
|completed_in|何秒で取得完了したか|
|max_id|取得したツイートのなかで一番新しいID|
|max_id_str|max_idの文字列版？(どちらも文字列だけど・・・)|
|since_id|取得したツイートのなかで一番古いID|
|since_id_str|since_idの文字列版？(どちらも文字列だけど・・・)|
|refresh_url|同じ検索ワードでこれよりも新しいツイートを取得したいときのURL|
|next_results|同じ検索ワードでこれよりも古いツイートを取得したいときのURL|

##今回取得できたデータの概要##
<dl>
  <dt>取得総数</dt>
  <dd>227,599件</dd>
  <dt>取得データの期間</dt>
  <dd>2015-03-11 04:43:42 から 2015-03-22 00:01:12</dd>
  <dt>1秒あたりツイート数</dt>
  <dd>4.101 tweet/sec</dd>
</dl>

##現時点の課題##
GET search/tweetsで10万件台後半まで取得すると、それ以前のツイートが取得できず、'statuses'要素が空になり、'next_results'要素がそもそも返却されない事象に遭遇しています・・・
現時点で解決できていないのですが、20万件ほど取得できたので、次回以降このデータを分析していきたいと思います。
**更新：** コメントいただいたのですが、どうやら１週間分のツイートしか取得できないようです。

[その2](http://qiita.com/kenmatsu4/items/8d88e0992ca6e443f446)につづきます。

このページで説明したコード全文は[こちら](https://gist.github.com/matsuken92/72a0da8d9b42bed28e61)

##参考にしたページ##
[Python で Twitter API にアクセス](http://qiita.com/yubais/items/dd143fe608ccad8e9f85)
[Twitter official REST API document](https://dev.twitter.com/rest/reference/get/search/tweets)
- Twitter Official API Documents
　https://dev.twitter.com/rest/reference/get/search/tweets
　https://dev.twitter.com/overview/api/tweets
　https://dev.twitter.com/overview/api/users
　https://dev.twitter.com/overview/api/entities