読者です 読者をやめる 読者になる 読者になる

GIS奮闘記

現役GISエンジニアの技術紹介ブログ。主にPythonを使用。

PythonでTwitterデータを取得しマップ上で表現する

さて、本日は「PythonTwitterデータを取得しマップ上で表現する」です。つまり、ツイート情報から緯度経度を取得し、その緯度経度ごとにポイントを配置してみようと思います。手順は以下を参照してください。

Twitter APIに接続するためのアカウント情報取得

Twitter APIにアクセスするための情報(consumer_key、consumer_secret、access_token、access_secret)を取得してください。

各種必要なライブラリ等のインストール

必要な各種ライブラリをインストールしてください。

Twitter REST APIsを使うための認証ライブラリは必ずインストールしてください。
pip install requests_oauthlib

また、MongoDBもインストールが必要です。詳細は以下を参照してください。
PythonでMongoDBを弄ってみる - GIS奮闘記

これで準備完了です。まずはツイート情報を取得してみたいと思います。今回は「フットサル」という文言を含んだツイートを取得します。
ただ、ここでちょっと制限が。ツイート情報は1回のアクセスで100ツイートしか取得できず、アクセスも15分で180回までになったらしいです。

■GetTweet.py

# -*- coding: utf-8 -*-
from requests_oauthlib import OAuth1Session
import json, datetime, time, pytz, re, sys,traceback, pymongo
from pymongo import MongoClient
import numpy as np

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

twitter = None
tweetdata = None
meta    = None

def initialize(): # twitter接続情報や、mongoDBへの接続処理等initial処理実行
    global twitter, tweetdata, meta
    twitter = OAuth1Session(KEYS['consumer_key'],KEYS['consumer_secret'],
                            KEYS['access_token'],KEYS['access_secret'])
    connect = MongoClient('localhost', 27017)
    db = connect.footsal     #「footsal」というDBを作成する
    tweetdata = db.tweetdata #「tweetdata」というコレクション(テーブル)を作成する
    meta = db.metadata       #「metadata」というコレクション(テーブル)を作成する

def getData():
    #「フットサル」を含んだツイートを検索
    res = getTweetData(u'フットサル')

    #成功した場合のみ
    if res['result']==True:
        # 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

# 検索ワードを指定して100件のTweetデータをTwitter REST APIsから取得する
def getTweetData(search_word):
    url = 'https://api.twitter.com/1.1/search/tweets.json'
    params = {'q': search_word,
              'count':'100',
             }

    # Tweetデータの取得
    req = twitter.get(url, params = params)

    # 取得したデータの分解
    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}

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

if __name__ == '__main__':

    #初期化
    initialize()

    count = 0

    #Tweetデータを取得(とりあえず180回1セットで)
    while(count < 180):
        try:
            count = count + 1
            #データ取得
            getData()
        except:
            print "Unexpected error:", sys.exc_info()[0]
            break

こんな感じでツイート情報を取得しました。一回の実行ではデータが少ないので、何度か実行した方がいいかもしれません(←今さらながら自動化すればよかったと。。。)。

そして、ツイート情報自体に"coordinates"というフィールドが含まれており、GPSなどの位置情報付きでつぶやいた場合はここに緯度経度が含まれます。だがしかし、位置情報付でツイートを行うユーザーは皆無に等しいことが判明・・・(←ツイッター未経験のため知りませんでしたorz)。そこで形態素解析ですね!
形態素解析 - Wikipedia

Mecabという形態素解析エンジンを使用するのですが、Windowsで使用するにはMinGWVisual Studioのインストール、コードの修正が必要でかなり面倒くさいので、Pythonモジュールはid:fgshunさんがコンパイルしたバイナリを使わせてもらいました。形態素解析エンジン MeCab 0.98pre3 野良ビルド - 銀月の符号
以下、導入方法です。

1.MeCabの本サイトでダウンロードしたWindows版のmecab-0.98.exeをインストール
2.形態素解析エンジン MeCab 0.98pre3 野良ビルドからダウンロードしたlibmecab-1.dll、MeCab.py、_MeCab.pydをパッケージフォルダ(Python2.7ならC:\Python27\Lib\site-packages)にコピー。

早速試してみます。

import MeCab
tagger = MeCab.Tagger("-Ochasen")
text = u"私は横浜に住んでいます。"
text_encode = text.encode('utf-8')
result = tagger.parse(text_encode)
result = result.decode('utf-8') # 必ずdecode
print result

「私は横浜に住んでいます。」という文章を形態素解析した結果が以下です。

私	ワタシ	私	名詞-代名詞-一般		
は	ハ	は	助詞-係助詞		
横浜	ヨコハマ	横浜	名詞-固有名詞-地域-一般		
に	ニ	に	助詞-格助詞-一般		
住ん	スン	住む	動詞-自立	五段・マ行	連用タ接続
で	デ	で	助詞-接続助詞		
い	イ	いる	動詞-非自立	一段	連用形
ます	マス	ます	助動詞	特殊・マス	基本形
。	。	。	記号-句点		

「横浜」という文言は固有名詞-地域と認識されます。この仕組みを利用してツイート本文から地名を抜き出したいと思います。

ツイート本文からの位置情報推測

以下ツイート本文から地名を抜き出しMongoDBにデータを取込ます。

■GetLocationName.py

#coding:utf-8
from pymongo import MongoClient
from collections import defaultdict
import MeCab

def main():
    connect = MongoClient('localhost', 27017)
    db = connect.footsal
    tweetdata = db.tweetdata

    for d in tweetdata.find({'spam':None,'retweeted_status': None},{'_id':1, 'text':1}):
        ret = location_name_mecab(d['text'])
        if len(ret) > 0:
            #地域名称をDBに入れる(強引だが一番最初の要素を地名とする)
            tweetdata.update({'_id' : d['_id']},{'$push': {'location_name':ret[u'地域名称'][0]}})

def location_name_mecab(sentence):
    #形態素解析
    t = MeCab.Tagger('-Ochasen')
    sentence = sentence.replace('\n', ' ')
    text = sentence.encode('utf-8')
    node = t.parseToNode(text)
    result_dict = defaultdict(list)
    for i in range(140):
        if node.surface != "":  # ヘッダとフッタを除外
            # 固有名詞かつ、地域の単語を選択
            if (node.feature.split(",")[1] == "固有名詞") and (node.feature.split(",")[2] == "地域"):
                plain_word = node.feature.split(",")[6]
                if plain_word !="*":
                    result_dict[u'地域名称'].append(plain_word.decode('utf-8'))
        node = node.next
        if node is None:
            break
    return result_dict

if __name__ == '__main__':
    main()

地名から緯度経度に変換する

上記で地名を取得することができましたので、ジオコーディングの出番ですね。ジオコーディングについては以下参照です。
Pythonでジオコーディングをやってみる - GIS奮闘記

■GetLatLng.py

# -*- coding: utf-8 -*-
import geocoder
from pymongo import MongoClient

def main():
    connect = MongoClient('localhost', 27017)
    db = connect.footsal
    tweetdata = db.tweetdata

    for d in tweetdata.find({'location_name':{"$ne":None},'spam':None,'retweeted_status': None},{'_id':1, 'location_name':1}):
        for name in d['location_name']:
            loc = get_coordinate(name)
            if loc.lat is not None:
                #緯度経度をDBにつっこむ
                tweetdata.update({'_id' : d['_id']},{'$push': {'lat':loc.lat, 'lng': loc.lng}})
            loc = None

def get_coordinate(location_name):
    try:
        #地名から座標を取得する
        ret = geocoder.google(location_name)
    except KeyError, e:
        "KeyError(%s)" % str(e)
        return

    return ret

if __name__ == '__main__':
    main()

これで緯度経度の取得完了です。

位置情報の可視化

それでは上記で取得した緯度経度を元に地図上にポイントをプロットします。今回はGoogleMapではなくMatplotlib basemapというライブラリを使用しますので、以下サイトでWheelをダウンロードしてpip install basemap-1.0.8-cp27-none-win32.whl のような感じでお願いします。
http://www.lfd.uci.edu/~gohlke/pythonlibs/

■PlotPoint.py

# -*- coding: utf-8 -*-
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from pymongo import MongoClient

def main():
    connect = MongoClient('localhost', 27017)
    db = connect.footsal
    tweetdata = db.tweetdata

    #マップの設定
    fig = plt.figure(figsize=(10,10))

    #緯度経度は日本全体が入るように適当に設定
    m = Basemap(projection='merc',llcrnrlat=25.836887 ,urcrnrlat=47.336893 ,\
                llcrnrlon=126.919922,urcrnrlon=152.671875 	 ,lat_ts=20, resolution='f')
    m.drawcoastlines( linewidth=0.25, color='k' )
    m.fillcontinents(color='#eeeeee',lake_color='#ddeeff')
    m.drawstates( linewidth=0.25, color='k' )
    m.drawcountries()
    m.drawmapboundary(fill_color='#ddeeff')

    #ポイントをプロット
    for d in tweetdata.find({'lat':{"$ne":None},'lng':{"$ne":None},'spam':None,'retweeted_status': None},{'_id':1, 'lat':1,'lng':1}):
        lat = d['lat']
        lon = d['lng']
        t_x, t_y = m(lon,lat)
        m.plot( t_x, t_y, "bo",markersize=5)

    #マップを表示
    plt.show()

if __name__ == '__main__':
    main()

これでポイントの配置が完了しました。結果を見てみます。

f:id:sanvarie:20151211093343p:plain

おぉーちゃんとプロットされましたね!今回はデータが少なかったので、あまり数がありませんが、本格的にデータを格納すれば分布図などができそうです(都市部を中心にプロットされそう)。
ただ、マップ上の表現としてはGoogleMapにしておけばよかったです(笑)。これはマップの鮮明さなどがちょっといまいちですかね。ただ、これはこれで便利なライブラリだと思うので、使いこなすことができたらまたおもしろいことができるかもしれませんね。

今回わかったことは
・ツイート情報に位置情報はほとんど付与されていないこと
・ツイート本文から位置情報を推測してもやはり正確な位置をつかむのは難しいこと(例えば、「川崎フロンターレのユニフォームを着て、フットサルなう」というツイートの場合、川崎として認識してしまう)
形態素解析の素晴らしさ
・ツイート情報は1回のアクセスで100ツイートしか取得できず、アクセスも15分で180回という制限がある
・geocoderの使用制限(同一IPから一日2500件が上限らしいです)
・自分のプログラミング能力の低さ
あたりでしょうか。

色々なサイトを参考にさせてもらい、とても勉強になりました。今回の経験をもとにbotを作ってみてもおもしろいかも!

以上、本日は「PythonTwitterデータを取得しマップ上で表現する」でした。