フォローとフォロワーの関係の可視化(Python + Graphviz)

最近、TwitterのAPIを叩いているのですが、せっかくなのでフォロー・フォロワーの繋がりを可視化してみたいなーと思いました。
と言うわけで、graphvizを用いて可視化しようとしたのですが、少しのノードでもグラフがbusyになってしまうので、自分のフォローとフォロワーの間だけにしてみました。といっても結構ごちゃごちゃしたグラフになりましたが。

自分のフォロー・フォロワーの関係図

作成したグラフは下記のようになります(巨大画像ファイルのため注意)。→の方向でフォローしている方向になります。
20170723_twitter_0-thumbnail2 フォローとフォロワーの関係の可視化(Python + Graphviz)

Graphvizにくわせるdot

Graphvizをpythonから直接叩ければよかったのですが、windowsでは環境設定が面倒なため、とりあえず下記のようなdotファイルを作成してGraphvizに食わせてグラフを作成しました。
digraph sample{
node[fontname="meiryo"];
"kumana_be"[image="C:\python\intro\twitter\icon\get\883820222527885312-kumana_be.jpg"];
"chihimasah"[image="C:\python\intro\twitter\icon\get\840769338671996928-chihimasah.jpg"];
"churio777"[image="C:\python\intro\twitter\icon\get\847223574586769408-churio777.jpg"];
<<中略>>
"manesetsu_crowd" -> "zenzenkabu";
"manesetsu_crowd" -> "minasek";
"manesetsu_crowd" -> "ryo_tatibana";
}
続いてソースコードになります。

ソースコード

元々は特定IDから深さを指定してその値だけフォロー・フォロワーを探索してグラフを作成する予定だったのですが、出来上がりのグラフが大変なことになったので、そのコードをベースに特定IDの人のフォロー・フォロワーのみの関係をグラフにするようにしています。
# coding='utf-8'
###########################################################################
# 特定アカウントのフォロワーとフォローの関係をDOTで出力する
# アカウント情報が必要になるのでforegoで.envを読みだす
# Usage : .\forego.exe run python.exe .\tweepy_search_myself.py <Twitter ID> 2 <Dotファイル名>
# Example : .\forego.exe run python.exe .\tweepy_search_myself.p kumana_be 2 relation.dot

from PIL import Image
import numpy as np
import os
import sys
import tweepy
import urllib.request
from requests_oauthlib import OAuth1Session
from datetime import datetime
import pdb

# 環境変数から認証情報を取得する。
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']

# 初期化
maxId = 999999999999999999 # 検索結果用ID
maxIdOld = 999999999999999999 # 検索結果用ID
searchPage = 0
checkedUserList = [] # 一度確認したID
relationUserList = [] # 今回確認したいID(大元のIDのフォローとフォロワー)
iconImageList = [] # IDのアイコンリスト for dot
linkList = [] # edgeのリスト for dot
screenName = sys.argv[1] # Twitter ID
maxDepth = int(sys.argv[2]) # フォロー,フォロワー探索の深さ。今回は2とする
outFileName = sys.argv[3] # Dotファイル出力

# 認証情報を使う
# 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)

def appendIcon(IdUser):
# iconの画像も使用するのでダウンロードしてくる
iconUrl = IdUser.profile_image_url
iconPath = os.path.join(os.getcwd(), "icon", "get", IdUser.id_str + "-" + IdUser.screen_name + ".jpg")
urllib.request.urlretrieve(iconUrl, iconPath)
# 下記の形式でiconをnodeに表示させる
iconImageStr = "\"" + IdUser.screen_name + "\"[image=\"" + iconPath + "\"];\n"
# 既に登録されていなければ登録する
if not iconImageStr in iconImageList:
iconImageList.append(iconImageStr)

# フォロー側の探索
def checkRelationFriend(prevScreen,currScreen,depth):
IdUser = api.get_user(currScreen)
Id = IdUser.id

print("Current :"+IdUser.screen_name)
# フォローするIDが選択されているとき
if prevScreen != "":
# Dotのedgeの形式でフォロー,フォロワーの関係性を明示
linkStr = "\"" + prevScreen + "\" -> \"" + IdUser.screen_name + "\";\n"
# 既に登録されていなければ登録する
if not linkStr in linkList:
linkList.append(linkStr)

# Iconの登録
appendIcon(IdUser)

# 今回の深さは2固定になる
if depth < maxDepth:
# すでにチェックが入っていない。もしくは、一番初めのID以外の場合
if not Id in checkedUserList or Id == int(startId):
checkedUserList.append(Id)
try:
# フォローしている人の一覧を取得する
friends_ids = tweepy.Cursor(api.friends_ids, id = Id, cursor = -1).items()
for friend_id in friends_ids:
# フォロー、フォロワーであれば探索
if friend_id in relationUserList:
checkRelation(IdUser.screen_name, friend_id, "", (depth + 1))
except tweepy.error.TweepError:
print(IdUser.screen_name,": Information can't be got. Maybe this ID is a private account")


def checkRelationFollower(postScreen,currScreen,depth):
IdUser = api.get_user(currScreen)
Id = IdUser.id

print("Current :"+IdUser.screen_name)
# フォローされている人が存在する場合
if postScreen != "":
# Dotのedgeの形式でフォロー,フォロワーの関係性を明示
linkStr = "\"" + IdUser.screen_name + "\" -> \"" + postScreen + "\";\n"
# 既に登録されていなければ登録する
if not linkStr in linkList:
linkList.append(linkStr)

#iconの登録
appendIcon(IdUser)

# ただし今回の深さは2固定になる
if depth < maxDepth:
# すでにチェックが入っていない。もしくは、一番初めのID以外の場合
if not Id in checkedUserList or Id == int(startId):
checkedUserList.append(Id)
try:
# フォローされている人の一覧を取得する
followers_ids = tweepy.Cursor(api.followers_ids, id = Id, cursor = -1).items()
for follower_id in followers_ids:
# フォロー、フォロワーであれば探索
if follower_id in relationUserList:
checkRelation("",follower_id,IdUser.screen_name, (depth + 1))
except tweepy.error.TweepError:
print(IdUser.screen_name,": Information can't be got. Maybe this ID is a private account")

def checkRelation(prevScreen,currScreen,postScreen,depth):
checkRelationFriend(prevScreen,currScreen,depth)
# 今回はフォロワー側の探索はせずにIconの登録のみ実施する
# checkRelationFollower(postScreen,currScreen,depth)
appendIcon(api.get_user(currScreen))

print(str(datetime.now()))

# 初めに大元となるIDを登録
startId = api.get_user(screenName).id
relationUserList.append(startId)
IdUser = api.get_user(startId)
appendIcon(IdUser)

# 大元のIDのフォローとフォロワーのリストを取得
# このリストに含まれるIDだけ作成するグラフに含まれる
friends_ids = tweepy.Cursor(api.friends_ids, id = startId, cursor = -1).items()
for friend_id in friends_ids:
friendUser = api.get_user(friend_id)
relationUserList.append(friendUser.id)

followers_ids = tweepy.Cursor(api.followers_ids, id = startId, cursor = -1).items()
for followers_id in followers_ids:
followersUser = api.get_user(followers_id)
relationUserList.append(followersUser.id)


# 今回はcheckRelationのFOllower側がコメントアウトされているので
# checkRelation("", screenName, "",0)
checkRelationFriend("",screenName,0)
# pdb.set_trace() # デバッグ用。 Friend側終了後にステップ実行したい場合に
checkRelationFollower("",screenName,0)

# dotファイルの出力
outFile = open(outFileName,'w',encoding='utf-8')
outFile.write("digraph sample{\n")
outFile.write("node[fontname=\"meiryo\"];\n")
print("icon")
print(iconImageList)
for item in iconImageList:
writeStr = str(item)
outFile.write(writeStr)
print("link")
print(linkList)for item in linkList:
writeStr = str(item)
outFile.write(writeStr)
outFile.write("}\n")
outFile.close()

print(str(datetime.now()))

雑感

結構少しのフォロー・フォロワーでもグラフは煩雑になるのだなぁというのが正直な感想です。あと、APIのフォロー・フォロワーの取得制限が15分間に15回なので、waitが入るため結構な処理時間になっています。フォロー46人、フォロワー7人の自分でも1時間以上dotの作成にかかっています。

この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)