Skip to content

Commit 247089b

Browse files
committed
Upload Follower Factory script
1 parent 7778afe commit 247089b

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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

Comments
 (0)