A friend recently proposed the idea of creating a script that would inform someone if they were followed/un-followed on instagram – I guess this.
The result was a daily update email:
I decided to use an sqlite database to store a retrieved list of followers and report the differences between the two lists. For example here is a sample run to test it:
run_test(capture_followers = ['bob', 'tony', 'amy']) run_test(capture_followers = ['bob', 'amy', 'rachel']) run_test(capture_followers = ['bob', 'tony', 'jo', 'bigd'])
The output is:
ANALYSING User: bob has started to follow you User: tony has started to follow you User: amy has started to follow you ANALYSING User: tony has decided to unfollow you User: rachel has started to follow you ANALYSING User: amy has decided to unfollow you User: rachel has decided to unfollow you User: tony has started to follow you User: jo has started to follow you User: bigd has started to follow you
All I needed to do then was to set-up a task using task-scheduler to execute my Python code every day:
Thanks to help from stackoverflow I rewrote my get_followers function to return a generator rather than a list of values which:
also has the advantage of separating data acquisition and handling logic
The best explanation that I found on this was this post:
def get_primes(number): while True: if is_prime(number): yield number number += 1 def solve_number_10(): # She *is* working on Project Euler #10, I knew it! total = 2 for next_prime in get_primes(3): if next_prime < 2000000: total += next_prime else: print(total) return
Which presents a visualisation of the flow of operation if one wants to deal with large/infinite sequences:
It’s helpful to visualize how the first few elements are created when we call get_primes in solve_number_10’s for loop. When the for loop requests the first value from get_primes, we enter get_primes as we would in a normal function.
We enter the while loop on line 3
The if condition holds (3 is prime)
We yield the value 3 and control to solve_number_10.
Then, back in solve_number_10:
The value 3 is passed back to the for loop
The for loop assigns next_prime to this value
next_prime is added to total
The for loop requests the next element from get_primes
This time, though, instead of entering get_primes back at the top, we resume at line 5, where we left off.
Most importantly, number still has the same value it did when we called yield (i.e. 3). Remember, yield both passes a value to whoever called next(), and saves the “state” of the generator function. Clearly, then, number is incremented to 4, we hit the top of the while loop, and keep incrementing number until we hit the next prime number (5). Again we yield the value of number to the for loop in solve_number_10. This cycle continues until the for loop stops (at the first prime greater than 2,000,000).
import sqlite3 import requests import json import time import smtplib from email.mime.text import MIMEText class Indexer(object): """Initialise an Indexer Class which will handle all the communication with the sqlite 3 database. It will replace the old list of followers in a table with the current list and return the changes""" def __init__(self, db, user): self.con = sqlite3.connect(db) self.tblname = 'followers_%s' % user def __del__(self): self.con.close() def db_commmit(self): self.con.commit() def is_indexed(self, follower_name): """ Check if a user exists already in a table """ follower = self.con.execute("select rowid from %s where user='%s'" % (self.tblname, follower_name)).fetchone() return follower != None def create_tables(self): """ Create table provided it doesn't already exist """ if not self.check_table_exists(): self.con.execute("create table %s(user)" % self.tblname) self.db_commmit() def add_to_index(self, follower): """ Add user to database (if he doesn't exist there already) """ if self.is_indexed(follower): return self.con.execute("insert into %s(user) values ('%s')" % (self.tblname, follower)) self.db_commmit() return True def remove_from_index(self, follower): """ Remove user from table """ self.con.execute("delete from %s where user='%s'" % (self.tblname, follower)) self.db_commmit() def retrieve_saved(self): """ Retrieve stored followers and return as a list """ followers = self.con.execute("select user from %s" % self.tblname).fetchall() return [i for i in followers] def check_table_exists(self): """ Check if a table exists already in the database and if so return: True """ try: self.con.execute("select count(*) from %s" % self.tblname).fetchone() return True except sqlite3.OperationalError: return False class followers(object): """ Initialise an object to hold the followers captured from instagram API """ def __init__(self): self.followers =  def add_follower(self, follower): self.followers.append(follower) def run_analysis(capture_followers, user): """ Initialise the database using Indexer() and then retrieve the snapshot in the database and compare against what has been currently captured from instagram to find users who have un-followed (and remove them). Then check which new users have appeared and add those in. Return a list of those lost and gained. """ print("ANALYSING") indx = Indexer("C:/Users/ikarmanov/PycharmProjects/Insta_Followers/Instagram_Followers.db", user) indx.create_tables() # Loss loss =  previous_followers = indx.retrieve_saved() for pf in previous_followers: if pf not in capture_followers: indx.remove_from_index(pf) loss.append(pf) # Gain gain =  for cf in capture_followers: if indx.add_to_index(cf): gain.append(cf) # Results return [loss, gain] def get_user_id(username, access_token): """ Get top matching userid for a given username from instagram """ response = requests.get( 'https://api.instagram.com/v1/users/search?q=%s&access_token=%s' % (username,access_token) ).text response = json.loads(response) # Return first-user (best match) return response['data']['id'] def get_followers(user_id, access_token, cursor=''): """ Create a generator which will be accessed later to get the usernames of the followers""" while cursor is not None: # fetch data url = 'https://api.instagram.com/v1/users/%s/followed-by?access_token=%s&cursor=%s' % (my_user, access_token, cursor) print(url) response = requests.get(url) json_out = json.loads(response.text) for user in json_out['data']: yield user # fetch new token try: cursor = json_out['pagination']['next_url'] except KeyError: print("Finished with %d followers" % len(my_followers.followers)) cursor = None if __name__ == '__main__': access_token = "secret" username = "firstname.lastname@example.org" password = "secret" user = 'ilia.uk' # Searching for # Initialise my_followers = followers() my_user = get_user_id(user, access_token) # Retrieve followers followers_gen = get_followers(my_user, access_token) # Get followers for usr in followers_gen: usrname = usr['username'] my_followers.add_follower(usrname) print("Added: %s" % usrname) # Analyse difference to snapshot loss, gain = run_analysis(capture_followers=my_followers.followers, user=user) # Compare to database # Create summary message msg_l =  if len(loss): msg_l.append("LOST FOLLOWERS:\n%s" % '\n'.join(loss)) if len(gain): msg_l.append("GAINED FOLLOWERS:\n%s" % '\n'.join(gain)) if msg_l: # There is some change so prepare an email to send-out: msg_text = "\n\n".join(msg_l) print(msg_text) msg = MIMEText(msg_text) msg['Subject'] = "Instagram Followers Daily Update" # Send Email: for em in ["email@example.com"]: msg_to = em msg['From'] = "Ilia" msg['To'] = msg_to s = smtplib.SMTP('smtp.gmail.com:587') s.ehlo() s.starttls() s.login(username, password) s.sendmail("Ilia", [msg_to], msg.as_string()) print("Message Sent") s.quit() else: print("No Change Today!")