655
697

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-03-20

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


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

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

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

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


##1.Twitter APIに接続するためのアカウント情報取得##
Google先生で「twitter api アカウント」と検索すると登録の仕方がわかりやすく書いてあるサイトがたくさん出てくるので、それらを参照してAPIにアクセスするための情報(特にconsumer_key、consumer_secret、access_token、access_secret)を取得してください。
##2.各種必要ライブラリ等のインストール##
iPython等の基本的なPython環境は整っている前提とします。ここにあるライブラリがあれば大体大丈夫と思います。
Twitter REST APIsを使うための認証ライブラリもインストールします。

pip install requests_oauthlib

また、データの格納にmongoDBを使うのでこちらこちらなどを参考にインストール。mongoDBの概要は「MongoDB の薄い本」などを参照。

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

pip install pymongo

##3.初期化処理##

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にインポートしていきます。

# 検索ワードを指定して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())

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

#-------------繰り返し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()
            
            # 待ち時間の計算. リミット+5秒後に再開する
            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()


##5.Twitter REST APIデータ構造## Twitter REST APIsの"[GET search/tweets](https://dev.twitter.com/rest/reference/get/search/tweets)"で得られたデータの構造は下記の通りです。 ###TwitterListResponseの構造### Tweet情報の主な要素の説明です。           
アイテム説明
id ツイートID。新しいものが番号が古く、古いものが番号が若い。このIDより大きい、小さい、を検索時に指定してそれ以降、以前のツイートの取得ができる。
id_str "id"の文字列版らしいのだが、もともと文字列で取得するので詳細不明。
user ユーザー情報。以下の要素を持つ(代表的なもののみピックアップ)
   id ユーザーID。通常目にすることない数字のID。
name 長い方のユーザーの名前。
screen_name @などで指定するときに使うユーザー名
description ユーザーの説明情報。プロフィール的な文章。
friends_count フォロー数
followers_count フォロワー数
statuses_count ツイート数(リツイート含む)
favourites_count お気に入り数
location 住んでいるところ
created_at このユーザの登録日
text ツイート本文
retweeted_status リツイートか否か(True:リツイート/False:通常ツイート)
retweeted リツイートされたか否か(True/False)
retweet_count リツイートされた回数
favorited お気に入りされたか否か(True/False)
favorite_count お気に入りされた数
coordinates 緯度経度
entities 以下に示す追加的情報
symbols
user_mentions 本文中に@で指定されたユーザー情報
hashtags 本文に記載のあるハッシュタグ
urls 本文に記載されたURL情報
source ツイートを行ったアプリ・サイト等の情報
lang 言語情報
created_at ツイート日時
place ツイートに関連した場所情報
in_reply_to_screen_name ツイートがリプライだった時のツイート元のユーザー名
n_reply_to_status_id ツイートがリプライだった時のツイート元のツイートID
in_reply_to_status_id_str n_reply_to_status_idの文字列版

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

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

##今回取得できたデータの概要##

取得総数
227,599件
取得データの期間
2015-03-11 04:43:42 から 2015-03-22 00:01:12
1秒あたりツイート数
4.101 tweet/sec

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

その2につづきます。

このページで説明したコード全文はこちら

##参考にしたページ##
Python で Twitter API にアクセス
Twitter official REST API document

655
697
24

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
655
697

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?