著者:飯尾 淳
前回に引き続き、筆者らの研究グループが開発したTwitterのトレンド分析システムについて解説します。前回は、そもそもTwitterのトレンドとは何かから始まり、開発者登録をしてトレンドAPIを叩き、そのリストを収集する方法まで説明しました。今回は、トレンドをキーにしてツイートを取得する方法と、ツイート群を対象として共起ネットワークグラフを作る方法を紹介します。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。
図4 トレンドを収集して分析するプログラムのコード(その1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#!/usr/bin/env python ########################################################### # ヘルパーファンクション ########################################################### import re # 特殊文字をエスケープする def escape(s, quoted=u'\'"\\', escape=u'\\'): return re.sub(u'[%s]' % re.escape(quoted), lambda mo: escape + mo.group(), s) # ノードのプリティプリンタ def pp_node(ary_of_nodes): for node in ary_of_nodes: keyword = escape(node[0]) frequency = node[1] print("node_hash['{0}'] = trend.nodes.create(word: '{1}', freq: {2:6.2f})" .format(keyword, keyword, frequency)) # リンクのプリティプリンタ def pp_link(ary_of_links): for link in ary_of_links: (kw1,kw2) = link[0].split(',') print("link = Link.create(corr: {0:6.2f})".format(link[1])) print("node_hash['{0}'].links << link".format(escape(kw1))) print("node_hash['{0}'].links << link".format(escape(kw2))) # 値を正規化する。p_fragは百分率とするときTrue def normalize(hash_obj, p_flag): maxval = max(hash_obj, key=lambda x: x[1])[1] factor = 100 if p_flag else 1 return [(x[0], x[1] / maxval * factor) for x in hash_obj] |
図5 トレンドを収集して分析するプログラムのコード(その2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
########################################################### # ワードグラフの作成 ########################################################### import MeCab # 「PATH_TO_MECAB_NEOLOGD」の部分は環境に合わせて修正 m = MeCab.Tagger("-d PATH_TO_MECAB_NEOLOGD") stopwords = {'*', 'HTTPS', 'HTTP', 'WWW', 'の', 'ん', 'ン', 'ω', '???'} def create_graph(keyword, collected, tweets): ary_of_ary = [] for tweet in tweets: ary = [] lines = m.parse(tweet).split('\n') for line in lines: if line.count('\t') == 0: continue (kw, prop) = line.split('\t') props = prop.split(',') if len({props[-3]} & stopwords) > 0: continue if props[1] == '固有名詞': ary.append(props[-3]) ary_of_ary.append(ary) KW_THRESHOLD = 20 kw_dict = {} counter = 0 for ary in ary_of_ary: for kw in ary: if kw in kw_dict: kw_dict[kw] = kw_dict[kw] + 1.0 else: kw_dict[kw] = 1.0 counter = counter + 1 for kw,val in kw_dict.items(): kw_dict[kw] = val / counter * 100 if len(kw_dict) > 0: kw_dict = sorted(kw_dict.items(), key = lambda x: x[1], reverse = True)[0:KW_THRESHOLD-1] kw_dict = normalize(kw_dict, True) print("node_hash = {}") print("trend = Trend.create(label: '{0}', collected:'{1}')" .format(escape(keyword), collected)) pp_node(kw_dict) corr_dict = {} for src in kw_dict: for dst in kw_dict: if src == dst: continue src_w = src[0] dst_w = dst[0] sd_pair = src_w + "," + dst_w if sd_pair in corr_dict: continue prob = 0.0 for ary in ary_of_ary: if ((src_w in ary) & (dst_w in ary)): prob = prob + 1.0 prob = 100 * prob / len(ary_of_ary) if prob > 0: corr_dict[dst_w + "," + src_w] = prob if len(corr_dict) > 0: lk_dict = sorted(corr_dict.items(), key = lambda x: x[1], reverse = True) # 後半3/4をカットして短くする lk_dict = lk_dict[0:int(len(lk_dict)*1/4)] if len(lk_dict) > 0: lk_dict = normalize(lk_dict, True) pp_link(lk_dict) |
図6 トレンドを収集して分析するプログラムのコード(その3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
########################################################### # すでに登録済みか否かのチェック ########################################################### import sqlite3 from contextlib import closing def has_been_registered(keyword, collected): # 「PATH_TO_THE_DATABASE_FILE」の部分は環境に合わせて修正 dbfile = "PATH_TO_THE_DATABASE_FILE(development.sqlite3)" with closing(sqlite3.connect(dbfile)) as conn: c = conn.cursor() sql = 'select label, collected from trends \ where label=? and collected=?' res = (keyword, collected) c.execute(sql, res) return (len(c.fetchall()) > 0) ########################################################### # メイン関数 ########################################################### from twitter import * from datetime import date def main(): # WOEID を希望の場所に合わせて指定。「23424856」は日本 woeid = 23424856 # アプリ登録時に取得した情報を下記に設定 CK = 'ADD_HERE_YOUR_CONSUMER_KEY' CS = 'ADD_HERE_YOUR_CONSUMER_SECRET' AT = 'ADD_HERE_YOUR_ACCESS_TOKEN' AS = 'ADD_HERE_YOUR_ACCESS_TOKEN_SECRET' twitter = Twitter(auth = OAuth(AT,AS,CK,CS)) results = twitter.trends.place(_id = woeid, exclude="hashtags") d = date.today() collected = d.strftime('%Y-%m-%d') for location in results: for trend in location["trends"]: keyword = trend["name"] if has_been_registered(keyword, collected): continue query_kw = keyword + " exclude:retweets exclude:nativeretweets" tw_rslts = twitter.search.tweets(q=query_kw, lang='ja', locale='ja', count=100) tw_ary = [] for tweet in tw_rslts["statuses"]: tw_ary.append(tweet["text"]) create_graph(keyword, collected, tw_ary) if __name__ == "__main__": main() |
図7 ワードグラフ作成部で出力されるRubyスクリプトの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
node_hash = {} trend = Trend.create(label: 'ホシガリス', collected:'2020-06-07') node_hash['アニポケ'] = trend.nodes.create(word: 'アニポケ', freq: 100.00) node_hash['ポケモン'] = trend.nodes.create(word: 'ポケモン', freq: 56.76) node_hash['ゴルーグ'] = trend.nodes.create(word: 'ゴルーグ', freq: 29.73) node_hash['思'] = trend.nodes.create(word: '思', freq: 18.92) (略) node_hash['ピカチュウ'] = trend.nodes.create(word: 'ピカチュウ', freq: 5.41) node_hash['パチリス'] = trend.nodes.create(word: 'パチリス', freq: 5.41) node_hash['スピアー'] = trend.nodes.create(word: 'スピアー', freq: 5.41) link = Link.create(corr: 100.00) node_hash['ポケモン'].links << link node_hash['アニポケ'].links << link link = Link.create(corr: 75.00) node_hash['ゴルーグ'].links << link node_hash['アニポケ'].links << link (略) link = Link.create(corr: 25.00) node_hash['マユルド'].links << link node_hash['ドクケイル'].links << link node_hash = {} trend = Trend.create(label: '孤独のグルメ', collected:'2020-06-07') (略) |