| 
 | 1 | +"""  | 
 | 2 | +Copyright (c) 2018 Randal S. Olson  | 
 | 3 | +
  | 
 | 4 | +Permission is hereby granted, free of charge, to any person obtaining a copy of this software  | 
 | 5 | +and associated documentation files (the "Software"), to deal in the Software without restriction,  | 
 | 6 | +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,  | 
 | 7 | +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,  | 
 | 8 | +subject to the following conditions:  | 
 | 9 | +
  | 
 | 10 | +The above copyright notice and this permission notice shall be included in all copies or substantial  | 
 | 11 | +portions of the Software.  | 
 | 12 | +
  | 
 | 13 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT  | 
 | 14 | +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  | 
 | 15 | +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,  | 
 | 16 | +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE  | 
 | 17 | +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  | 
 | 18 | +"""  | 
 | 19 | + | 
 | 20 | +from __future__ import print_function  | 
 | 21 | +import time  | 
 | 22 | +from datetime import datetime  | 
 | 23 | +import os  | 
 | 24 | + | 
 | 25 | +from twitter import Twitter, OAuth, TwitterHTTPError  | 
 | 26 | +from tqdm import tqdm  | 
 | 27 | +import pandas as pd  | 
 | 28 | +import matplotlib.pyplot as plt  | 
 | 29 | + | 
 | 30 | +USER_TO_ANALYZE = ''  | 
 | 31 | +OAUTH_TOKEN = ''  | 
 | 32 | +OAUTH_SECRET = ''  | 
 | 33 | +CONSUMER_KEY = ''  | 
 | 34 | +CONSUMER_SECRET = ''  | 
 | 35 | + | 
 | 36 | +twitter_connection = Twitter(auth=OAuth(OAUTH_TOKEN, OAUTH_SECRET,  | 
 | 37 | + CONSUMER_KEY, CONSUMER_SECRET))  | 
 | 38 | + | 
 | 39 | +pbar = tqdm()  | 
 | 40 | +pbar.write('Collecting list of Twitter followers for @{}'.format(USER_TO_ANALYZE))  | 
 | 41 | + | 
 | 42 | +rl_status = twitter_connection.application.rate_limit_status()  | 
 | 43 | +if rl_status['resources']['followers']['/followers/ids']['remaining'] <= 0:  | 
 | 44 | + sleep_until = rl_status['resources']['followers']['/followers/ids']['reset']  | 
 | 45 | + sleep_for = int(sleep_until - time.time()) + 10 # wait a little extra time just in case  | 
 | 46 | + if sleep_for > 0:  | 
 | 47 | + pbar.write('Sleeping for {} seconds...'.format(sleep_for))  | 
 | 48 | + time.sleep(sleep_for)  | 
 | 49 | + pbar.write('Awake!')  | 
 | 50 | + | 
 | 51 | +followers_status = twitter_connection.followers.ids(screen_name=USER_TO_ANALYZE)  | 
 | 52 | +followers = followers_status['ids']  | 
 | 53 | +next_cursor = followers_status['next_cursor']  | 
 | 54 | +pbar.update(len(followers))  | 
 | 55 | + | 
 | 56 | +while next_cursor != 0:  | 
 | 57 | + rl_status = twitter_connection.application.rate_limit_status()  | 
 | 58 | + if rl_status['resources']['followers']['/followers/ids']['remaining'] <= 0:  | 
 | 59 | + sleep_until = rl_status['resources']['followers']['/followers/ids']['reset']  | 
 | 60 | + sleep_for = int(sleep_until - time.time()) + 10 # wait a little extra time just in case  | 
 | 61 | + if sleep_for > 0:  | 
 | 62 | + pbar.write('Sleeping for {} seconds...'.format(sleep_for))  | 
 | 63 | + time.sleep(sleep_for)  | 
 | 64 | + pbar.write('Awake!')  | 
 | 65 | + | 
 | 66 | + followers_status = twitter_connection.followers.ids(screen_name=USER_TO_ANALYZE, cursor=next_cursor)  | 
 | 67 | + # Prevent duplicate Twitter user IDs  | 
 | 68 | + more_followers = [follower for follower in followers_status['ids'] if follower not in followers]  | 
 | 69 | + followers += more_followers  | 
 | 70 | + next_cursor = followers_status['next_cursor']  | 
 | 71 | + | 
 | 72 | + pbar.update(len(more_followers))  | 
 | 73 | + | 
 | 74 | +pbar.close()  | 
 | 75 | + | 
 | 76 | +pbar = tqdm(total=len(followers))  | 
 | 77 | +pbar.write('Collecting join dates of Twitter followers for @{}'.format(USER_TO_ANALYZE))  | 
 | 78 | +followers_created = list()  | 
 | 79 | + | 
 | 80 | +rl_status = twitter_connection.application.rate_limit_status()  | 
 | 81 | +remaining_calls = rl_status['resources']['users']['/users/lookup']['remaining']  | 
 | 82 | + | 
 | 83 | +for base_index in range(0, len(followers), 100):  | 
 | 84 | + if remaining_calls == 50:  | 
 | 85 | + # Update the remaining calls count just in case  | 
 | 86 | + rl_status = twitter_connection.application.rate_limit_status()  | 
 | 87 | + remaining_calls = rl_status['resources']['users']['/users/lookup']['remaining']  | 
 | 88 | + | 
 | 89 | + if remaining_calls <= 0:  | 
 | 90 | + sleep_until = rl_status['resources']['users']['/users/lookup']['reset']  | 
 | 91 | + sleep_for = int(sleep_until - time.time()) + 10 # wait a little extra time just in case  | 
 | 92 | + if sleep_for > 0:  | 
 | 93 | + pbar.write('Sleeping for {} seconds...'.format(sleep_for))  | 
 | 94 | + time.sleep(sleep_for)  | 
 | 95 | + pbar.write('Awake!')  | 
 | 96 | + rl_status = twitter_connection.application.rate_limit_status()  | 
 | 97 | + remaining_calls = rl_status['resources']['users']['/users/lookup']['remaining']  | 
 | 98 | + | 
 | 99 | + remaining_calls -= 1  | 
 | 100 | + | 
 | 101 | + # 100 users per request  | 
 | 102 | + user_info = twitter_connection.users.lookup(user_id=list(followers[base_index:base_index + 100]))  | 
 | 103 | + followers_created += [x['created_at'] for x in user_info]  | 
 | 104 | + | 
 | 105 | + pbar.update(len(followers[base_index:base_index + 100]))  | 
 | 106 | + | 
 | 107 | +pbar.close()  | 
 | 108 | +print('Creating Follower Factory visualization for @{}'.format(USER_TO_ANALYZE))  | 
 | 109 | + | 
 | 110 | +days_since_2006 = [(x.year - 2006) * 365 + x.dayofyear for x in pd.to_datetime(followers_created)]  | 
 | 111 | + | 
 | 112 | +mpl_style_url = 'https://gist.githubusercontent.com/rhiever/d0a7332fe0beebfdc3d5/raw/1b807615235ff6f4c919b5b70b01a609619e1e9c/tableau10.mplstyle'  | 
 | 113 | +with plt.style.context(mpl_style_url):  | 
 | 114 | + plt.figure(figsize=(9, 12))  | 
 | 115 | + plt.scatter(x=range(len(days_since_2006)), y=days_since_2006[::-1], s=2, alpha=0.1 * (80000. / len(days_since_2006)))  | 
 | 116 | + plt.yticks(range(0, 365 * (datetime.today().year + 1 - 2006), 365), range(2006, datetime.today().year + 1))  | 
 | 117 | + plt.xlabel('Follower count for @{}'.format(USER_TO_ANALYZE))  | 
 | 118 | + plt.ylabel('Date follower joined Twitter')  | 
 | 119 | + plt.savefig('{}-follower-factory.png'.format(USER_TO_ANALYZE))  | 
 | 120 | + | 
 | 121 | +print('Follower Factory visualization saved to {}'.format(os.getcwd()))  | 
0 commit comments