【TwitterAPI】Twitterフォロワーのベン図をmatplotlibで描画する

# はじめに

最近、「イケメンシリーズ」なる乙女ソシャゲを知ったんですが登場するキャラ、マジでイケメンなんですよね。
イケメン戦国では、三方ヶ原の戦いでうんちを漏らした事で有名な徳川家康が俺様系イケメンです。
そうした偉人の"穢れ"の部分がどう描写されているのか、個人的には気になりますね。

イケメンシリーズは現状、以下の3タイトルがリリースされているそうです。
・イケメン戦国
・イケメンヴァンパイア
・イケメン源氏伝

これらのタイトルのTwitter公式運営垢のフォロワーをユーザーと仮定するならば、イケメンシリーズ間でどれほど
共通ユーザーが存在するのかをベン図で描画するのが今回の記事の筋です。

# フォロワーリストを取得する

## TwitterAPIについて
TwitterAPIを利用するにあたっての申請等の手順は、他の投稿者の方が懇々と説明してくださっているかと思いますので省略します。

TwitterAPIでは各エンドポイント別にレート制限が設けられています。
http://westplain.sakuraweb.com/translate/twitter/Documentation/REST-APIs/Public-API/API-Rate-Limits.cgi
【Twitter 開発者 ドキュメント日本語訳　API レート制限】

今回取り扱う特定のユーザーからフォロワーの一覧を返す[/followers/id]のレート制限は「15回/15分」となっております。
https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids

一度のリクエストで取得できるフォロワー数は5000人まで、それが15分につき15回許可されているわけですから、
一日に取得できるフォロワー数は5000×15×4×24で7,200,000人までとなります。

トランプ大統領のフォロワー数が現時点で約64,000,000人いるわけですが、全てのフォロワーを取得するとなると約9日近く掛かる事になります。ジャスティン・ビーバーは約106,000,000人のフォロワーを抱えている為、2週間近く掛かる事になります。その間にフォロワーが絶えず変動する訳ですので、信用性の無いデータとなると言えます。

別の例では、ソシャゲで一番ユーザーが多いと考えられるFGOの公式垢のフォロワーは約1,500,000人で、サイゲの超大作は約840,000人、プリコネRは約387,000人となっております。

つまりソシャゲの運営垢に限定するならば、一日の内に全てのフォロワーを取得する事が可能という事になります。


また、現在利用しているレート制限の状態は[/application/rate_limit_status]にリクエストを投げる事で取得できます。
このメソッド自体にも「180回/15分」のレート制限がありますので、留意してください。
https://developer.twitter.com/en/docs/developer-utilities/rate-limit-status/api-reference/get-application-rate_limit_status

今回のサンプルコードではこのメソッドを利用してレート制限内で処理を回してますが、tweepyなるサードパーティーライブラリを使えばその辺をよしなに処理してくれるらしいので、面倒なら頼るのがよろしいかと思います。

ちなみにレート制限を尊重しないとBANされるそうです。
## サンプルソースコード
```サンプルコードその1.py
from requests_oauthlib import oauthS
import time

BASEURL='https://api.twitter.com/1.1/'

class UneiAccountFollowers(object):
    def __init__(self,resorce,endpoint,userid):
      self.resorce,self.endpoint=resorce,endpoint
      self.oath = oauthS(
        'ご自身のAPIKey',
        'ご自身のAPISecretKey',
        'ご自身のAccessToken',
        'ご自身のAccessTokenSecret',
        )
      self.__params=userid

    @property
    def params(self):
      params = {
                "user_id": self.__params,
                "count":5000,
                "stringify_ids":"true"
              }
              
      return params

    def api_get(self,param):
      def rate_limit_status_get(resorce):
        responce = self.oath.get(BASEURL+'/application/rate_limit_status.json', params = {"resources":resorce})
        return json.loads(responce.text)['resources'][resorce]

      folower_list=[]

      rate_limit=rate_limit_status_get(self.resorce)[self.endpoint]

      while True:
        if rate_limit['remaining'] >0:
          responce = self.oath.get(BASEURL +self.endpoint+'.json', params = param)
          res=json.loads(responce.text)
          rate_limit['remaining']-=1

          folower_list.extend(res['ids'])
          if len(res['ids']) < 5000: return folower_list

          if 'cursor' in param:
            param['cursor']=res['next_cursor_str']
          else:
            param={**param,**{'cursor':res['next_cursor_str']}}
        else:
          time.sleep(abs(rate_limit['reset']-datetime.now().timestamp()))
          rate_limit=rate_limit_status_get(self.resorce)[self.endpoint]
        continue

    @classmethod
    def all_exec(cls):
        new=cls(
          'followers'
          ,'/followers/ids'
          ,'任意のTwitterID'
          )

        print(new.api_get(new.params))

UneiAccountFollowers.all_exec()
```
このサンプルコードでは例外処理等を一切していないですが、冗長となる為必要な部分だけ書いてます。
取得したフォロワーリストはDBに書き込むなりCSV出力するなりすればよいかと思います。

上記のソースを参考にする場合は、十分に注意を払って取り扱う事をお勧めします。BANされる危険性がありますので。

また、何度か検証した結果、返ってくるフォロワーIDが重複しているケースが極稀にある為、

```
list(set(folower_list))
```
上記のように、重複削除してからデータ処理するのが無難かと思います。

# 分析する
## ベン図とは
https://mathwords.net/venn
具体例で学ぶ数学 -ベン図とは



↓こんなやつです。中学の数学とかでよく出てきたアレです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302014/f5dcda56-37ed-9899-fd02-6e9212769bb3.png)


## matplotlib-vennを使ってベン図を描画する
```サンプルコードその2.py
from matplotlib import pyplot as plt
from matplotlib_venn import venn3
import csv

with open("./IkemenVampire.csv", "r" ) as csv_file: 
    tmp1=[]
    cf=csv.reader(csv_file, doublequote=True, lineterminator="\r\n", quotechar='"', skipinitialspace=True)
    for row in cf:
        tmp1.append(row[0])
    ikemen1 = set(tmp1)
    
with open("./ikemen_genjiden.csv", "r") as csv_file: 
    tmp2=[]
    cf=csv.reader(csv_file, doublequote=True, lineterminator="\r\n", quotechar='"', skipinitialspace=True)
    for row in cf:
        tmp2.append(row[0])
    ikemen2 = set(tmp2)
    
with open("./Cyikemensengoku.csv", "r") as csv_file: 
    tmp3=[]
    cf=csv.reader(csv_file, doublequote=True, lineterminator="\r\n", quotechar='"', skipinitialspace=True)
    for row in cf:
        tmp3.append(row[0])
    ikemen3 = set(tmp3)
    
plt.title("Twitter Ikemen Graph")

v=venn3(subsets = [ikemen1,ikemen2,ikemen3], set_labels = ('A', 'B', 'C'))

v.get_label_by_id('A').set_text('')
v.get_label_by_id('B').set_text('')
v.get_label_by_id('C').set_text('')

plt.annotate('@Cyikemensengoku', xy = v.get_label_by_id('001').get_position(), xytext = (0,-70), size = 'medium',
            ha = 'center', textcoords = 'offset points', bbox = dict( fc = 'yellow', alpha = 0.7),
            arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3',color = 'gray'))

plt.annotate('@IkemenVampire', xy = v.get_label_by_id('10').get_position(), xytext = (-30,-70), size = 'medium',
            ha = 'center', textcoords = 'offset points', bbox = dict( fc = 'yellow', alpha = 0.7),
            arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3', color = 'gray'))

plt.annotate('@ikemen_genjiden', xy = v.get_label_by_id('01').get_position(), xytext = (30,-70), size = 'medium',
            ha = 'center', textcoords = 'offset points', bbox = dict( fc = 'yellow', alpha = 0.7),
            arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3',color = 'gray'))

plt.show()
```
今回の場合はcsv出力したものを読み込んでますが、参考にする場合はご自身の環境に合わせたデータ処理をしてください。

出てきた結果がこちら。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302014/cd1a8ae4-b0c7-ef96-29d1-c24f86be7cc0.png)

9110人は全てのイケメンシリーズを嗜んでいる事になります。

以上です。