2017年07月27日

フォロー・フォロワーの分類を自動で実施してみた

たまにフォロワー分類みたいなことをしている人がいると思います。TwitterのUser情報やTweet情報を用いて自動で分類ができないかなーと思って、次の様な分類を実施してみました。

分類方法

  • X軸:投資方法がアクティブかどうか
  • Y軸:Twitter上での活動がアクティブがどうか

図にしてみると下記となります。Userの自己紹介文やTweet頻度を参考にして、この図の上にフォロワーやフォローの方のアイコンを分類わけして置いてみました。

20170730_activeGraphBase_0.png

結果


結果としては、下記のような分類分けになりました。投資方針の軸は株式・海外投資・不動産な人をアクティブにしてみました。いい加減なアルゴリズムですがインデックス投資な人はX軸の左寄りになるようにしています。

20170730_activeGraphBase_1.jpg

というわけで、そのいい加減なアルゴリズムに基づいたプログラムは次に続きます。


ソースコード

Tweetの頻度については、1週間分のTweet数を数えて多ければTwitter上でアクティブな人と認識させています。投資方針がアクティブかどうかは、自己紹介文の中にどのような単語が使われているかで点数をつけて決めています。かなり適当ではあるのですが、Pythonで記述して何か結果が出てきたら楽しいなーという感じで作っているだけなのでご容赦を。

# coding='utf-8'
###########################################################################
# 特定アカウントのフォロワーとフォローのActiveさを画像に出力する
# アカウント情報が必要になるのでforegoで.envを読みだす
# Usage : .\forego.exe run python.exe .\tweepy_analizeFF.py <Twitter ID> <ImageFile> <DebugFile>
# Example : .\forego.exe run python.exe .\tweepy_analyzeFF.py kumana_be activeFF.png debugFF.txt

from PIL import Image
import datetime
import os,shutil
import random
import sys
import tweepy
import urllib.request
from requests_oauthlib import OAuth1Session
from janome.tokenizer import Tokenizer

# 環境変数から認証情報を取得する。
CONSUMER_KEY = os.environ['CONSUMER_KEY']
CONSUMER_SECRET = os.environ['CONSUMER_SECRET']
ACCESS_TOKEN = os.environ['ACCESS_TOKEN']
ACCESS_TOKEN_SECRET = os.environ['ACCESS_TOKEN_SECRET']

# 初期化
screenName = sys.argv[1] # Twitter ID
outImageName = sys.argv[2] # 画像ファイル
outTxtName = sys.argv[3] # Debug用Textファイル
iconImage = {}
iconActive = {}
iconTweetNum = {}
maxTweetNum = 0
tweetNumSum = 0
tweetNumIndex = 0
minTweetNum = 9999999999

# Tweetを取得する期間(1週間)を指定するために1週間前の時間を取得
currTime = datetime.datetime.now()
deltaTime = datetime.timedelta(days=7)
startTime = currTime - deltaTime

# 認証情報を使う
# wait_on_rate_limit = True はAPI制限に引っかかったときにWaitする設定
auth = tweepy.OAuthHandler(CONSUMER_KEY,CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN,ACCESS_TOKEN_SECRET)
api = tweepy.API(auth, wait_on_rate_limit = True)
#api = tweepy.API(auth)

# 日本語の解析
# Userの辞書を使用するときは前者を使う
#t = Tokenizer("userdic.csv", udic_type="simpledic", udic_enc="utf8")
t = Tokenizer("userdic.csv", udic_enc="utf8")
#t = Tokenizer()

# 各種情報(iconImage,Tweet数,Bioからの解析値)の追加
def appendInfo(IdUser):
# iconの画像も使用するのでダウンロードしてくる
iconUrl = IdUser.profile_image_url
iconPath = os.path.join(os.getcwd(), "icon", "get_for_analyze", IdUser.id_str + "-" + IdUser.screen_name + ".jpg")
iconImage[IdUser.id_str] = iconPath
urllib.request.urlretrieve(iconUrl, iconPath)

# 取得できる1週間のTweet数を取得する
maxId = 999999999999999999 # 検索結果用ID
maxIdOld = 999999999999999999 # 検索結果用ID
breakForTime = 0 # 1週間前のTweetを取得したらBreak
while True:
global maxTweetNum
global minTweetNum
global tweetNumSum
global tweetNumIndex
# 取れる分だけTweetを取得する
tweets = tweepy.Cursor(api.user_timeline, id = IdUser.id, max_id = maxId-1, cursor = -1).items()
for tweet in tweets:

if IdUser.id_str in iconTweetNum:
iconTweetNum[IdUser.id_str] += 1
else:
iconTweetNum[IdUser.id_str] = 1

if tweet.created_at < startTime:
# Tweetが1週間以上前のになったらbreak
breakForTime = 1;
break

if breakForTime == 1:
# Tweetが1週間以上前のになったらbreak
break

# idが今までのより小さければ代入
if(tweet.id < maxIdOld):
maxId = tweet.id

if maxIdOld == maxId:
# Tweetが見つからなければmaxIdOldと変わらないのでTweet結果をすべて受けとったと判断
break
else:
maxIdOld = maxId

# 全てのIDにおける最大Tweet数と最少Tweet数を保存しておく
if(maxTweetNum < iconTweetNum[IdUser.id_str]):
maxTweetNum = iconTweetNum[IdUser.id_str]
if(minTweetNum > iconTweetNum[IdUser.id_str]):
minTweetNum = iconTweetNum[IdUser.id_str]

# 全てのIDにおける平均Tweet数を取得するための情報
tweetNumSum += iconTweetNum[IdUser.id_str]
tweetNumIndex += 1

print("Tweet Num: ", iconTweetNum[IdUser.id_str])
writeStr = "Tweet Num: " + str(iconTweetNum[IdUser.id_str]) + "\n"
outTxt.write(writeStr)

# 自己紹介内容を解析して単語にばらす
# この単語で投資方針のActiveさを解析する
tokens = t.tokenize(IdUser.description)
iconActive[IdUser.id_str] = 0
for token in tokens:
if token.part_of_speech.split(',')[0] == u'名詞':
# 単語に応じて評価地を代入
if(token.surface in ["株式","株主","株"]):
iconActive[IdUser.id_str] += (20 + (random.randint(0,5)))
elif(token.surface in ["米国","海外","新興国"]):
iconActive[IdUser.id_str] += (30 + (random.randint(0,5)))
elif(token.surface in ["FX","不動産","専業"]):
iconActive[IdUser.id_str] += (50 + (random.randint(0,5)))
elif(token.surface in ["分散","保険","長期","優待"]):
iconActive[IdUser.id_str] -= (20 - (random.randint(0,5)))
elif(token.surface in ["インデックス","貯蓄","貯金","ETF"]):
iconActive[IdUser.id_str] -= (30 - (random.randint(0,5)))
elif(token.surface in ["節約","家計","積立"]):
iconActive[IdUser.id_str] -= (50 - (random.randint(0,5)))

####################################################
# MAIN
####################################################

# 基本の画像を入力
imBase = Image.open("activeGraphBase.png")
# 出力Txtデータ
outTxt = open(outTxtName,'w',encoding='utf-8')

startId = api.get_user(screenName).id
IdUser = api.get_user(startId)
Id = IdUser.id
print("Root :" + IdUser.screen_name)
writeStr = "Root : " + IdUser.screen_name + "\n"
outTxt.write(writeStr)
iconDirPath = os.path.join(os.getcwd(), "icon", "get_for_analyze")
shutil.rmtree(iconDirPath)
os.makedirs(iconDirPath)

#本人の情報を登録
appendInfo(IdUser)

try:
# フォローされている人の一覧を取得する
followers_ids = tweepy.Cursor(api.followers_ids, id = Id, cursor = -1).items()
for follower_id in followers_ids:
IdUser = api.get_user(follower_id)

print("ID :" + IdUser.screen_name)
writeStr = "ID :" + IdUser.screen_name + "\n"
outTxt.write(writeStr)
# フォロワーの情報を登録
appendInfo(IdUser)
except tweepy.error.TweepError:
writeStr = IdUser.screen_name + ": Information can't be got. Maybe this ID is a private account" + "\n"
outTxt.write(writeStr)
print(IdUser.screen_name,": Information can't be got. Maybe this ID is a private account")

try:
# フォローされている人の一覧を取得する
friends_ids = tweepy.Cursor(api.friends_ids, id = Id, cursor = -1).items()
for friend_id in friends_ids:
IdUser = api.get_user(friend_id)

print("ID :" + IdUser.screen_name)
writeStr = "ID :" + IdUser.screen_name + "\n"
outTxt.write(writeStr)
# フォローの情報を取得
appendInfo(IdUser)
except tweepy.error.TweepError:
writeStr = IdUser.screen_name + ": Information can't be got. Maybe this ID is a private account" + "\n"
outTxt.write(writeStr)
print(IdUser.screen_name,": Information can't be got. Maybe this ID is a private account")


for key,value in sorted(iconActive.items(), key=lambda x: x[1], reverse=True):
# writeはデバックデータ
writeStr=str(key) + ":" +str(value) + ":" + str(iconTweetNum[key])
outTxt.write(writeStr)
outTxt.write("--------------\n")

# Icon画像を取得
imCurr = Image.open(iconImage[key])
# X軸は最大最少でクリップ
x = ((value+120)*4)
if (x < 0):
x = 0
if (x > 950):
x = 950

# Y軸は平均値を元に正規化して表示
tweetNumAvg = tweetNumSum / tweetNumIndex
y = 950 - int(950 * (iconTweetNum[key] - minTweetNum)/(tweetNumAvg * 2 - minTweetNum))
if (y < 0):
y = 0
if (y > 950):
y = 950
# ICONをペーストする
imBase.paste(imCurr,(x,y))

outTxt.close()
#最終的に出来上がり画像を保存
imBase.save(outImageName)

画像周りの処理は、「退屈なことはPythonにやらせよう」の17章、時間周りは15章を参考にしています。




スポンサーリンク
posted by くまなべ at 07:54 | Comment(0) | TrackBack(0) | Python
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/180452934
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック