├── requirements.txt ├── instabot ├── bot │ ├── bot.py │ └── __init__.py ├── api │ ├── __init__.py │ ├── request.py │ └── api.py ├── getter │ ├── __init__.py │ └── getter.py ├── sender │ ├── __init__.py │ └── sender.py ├── user │ ├── __init__.py │ ├── user_controller_db.py │ ├── user_controller.py │ ├── user_db.py │ └── user.py ├── __init__.py ├── log.conf ├── db_helper.py └── config.py ├── examples ├── crons │ ├── likers │ │ ├── geotags.txt │ │ ├── hashtags.txt │ │ └── hashtag_geotag_liker.py │ └── followings │ │ ├── hashtags.txt │ │ └── follow_by_hashtag.py ├── scrapers │ ├── config.py │ ├── users_by_hashtag.py │ ├── users_by_geotag.py │ ├── users_by_followers.py │ └── scraper.py └── notebooks │ ├── sender_example.ipynb │ └── getter_example.ipynb ├── .travis.yml ├── add_user.py ├── README.md ├── test.py ├── .gitignore └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | tqdm 3 | -------------------------------------------------------------------------------- /instabot/bot/bot.py: -------------------------------------------------------------------------------- 1 | class Bot(object): 2 | pass 3 | -------------------------------------------------------------------------------- /examples/crons/likers/geotags.txt: -------------------------------------------------------------------------------- 1 | МФТИ 2 | дубна 3 | Москва 4 | -------------------------------------------------------------------------------- /instabot/api/__init__.py: -------------------------------------------------------------------------------- 1 | from . import request 2 | from . import api 3 | -------------------------------------------------------------------------------- /instabot/bot/__init__.py: -------------------------------------------------------------------------------- 1 | from .bot import Bot 2 | 3 | assert Bot 4 | -------------------------------------------------------------------------------- /instabot/getter/__init__.py: -------------------------------------------------------------------------------- 1 | from .getter import Getter 2 | 3 | assert Getter # silence pyflakes 4 | -------------------------------------------------------------------------------- /instabot/sender/__init__.py: -------------------------------------------------------------------------------- 1 | from .sender import Sender 2 | 3 | assert Sender # silence pyflakes 4 | -------------------------------------------------------------------------------- /examples/scrapers/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config file for scrapers examples 3 | """ 4 | 5 | outputpath = "~/instabot/" 6 | -------------------------------------------------------------------------------- /examples/crons/followings/hashtags.txt: -------------------------------------------------------------------------------- 1 | mipt 2 | мфти 3 | физтех 4 | bitcoin 5 | f4f 6 | ЛШ_PI 7 | бухайтанцуй 8 | buhaitancui 9 | -------------------------------------------------------------------------------- /examples/crons/likers/hashtags.txt: -------------------------------------------------------------------------------- 1 | mipt 2 | мфти 3 | btc 4 | coin 5 | Ar 6 | Vr 7 | Blockchain 8 | Crypto 9 | bitcoin 10 | ico 11 | 3D 12 | pythonnight 13 | -------------------------------------------------------------------------------- /instabot/user/__init__.py: -------------------------------------------------------------------------------- 1 | from .user import User 2 | from .user_controller import UserController 3 | 4 | assert User # silence pyflakes 5 | assert UserController # silence pyflakes 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.5" 5 | 6 | install: 7 | - pip install -r requirements.txt 8 | 9 | script: 10 | - pyflakes instabot 11 | - pep8 --ignore=E223,E501 instabot 12 | 13 | branches: 14 | only: 15 | - master 16 | - develop -------------------------------------------------------------------------------- /instabot/__init__.py: -------------------------------------------------------------------------------- 1 | from .user import User, UserController 2 | from .api import api 3 | from .getter import Getter 4 | from .sender import Sender 5 | from .bot import Bot 6 | 7 | assert User 8 | assert UserController 9 | assert Getter 10 | assert Sender 11 | assert Bot 12 | -------------------------------------------------------------------------------- /add_user.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from instabot import User 3 | 4 | parser = argparse.ArgumentParser(add_help=True) 5 | parser.add_argument('user', type=str, help="username") 6 | parser.add_argument('password', type=str, help="password") 7 | args = parser.parse_args() 8 | 9 | _ = User(args.user, args.password) -------------------------------------------------------------------------------- /instabot/log.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [handlers] 5 | keys=handler1, handler2 6 | 7 | [formatters] 8 | keys=formatter1 9 | 10 | [logger_root] 11 | qualname=main 12 | level=DEBUG 13 | handlers=handler1,handler2 14 | 15 | [handler_handler1] 16 | class=StreamHandler 17 | level=INFO 18 | formatter=formatter1 19 | args=(sys.stdout,) 20 | 21 | [handler_handler2] 22 | class=FileHandler 23 | level=DEBUG 24 | formatter=formatter1 25 | args=('instabot.log','a') 26 | 27 | [formatter_formatter1] 28 | format=%(levelname)-8s [%(asctime)s] %(message)s 29 | datefmt=%Y-%m-%d %H:%M:%S 30 | class=logging.Formatter -------------------------------------------------------------------------------- /examples/crons/followings/follow_by_hashtag.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from random import random 4 | from tqdm import tqdm 5 | 6 | sys.path.append('../../../') 7 | from instabot import User, Sender 8 | 9 | main_username = "ohld" # this user should be logged to instapro earlier 10 | send = Sender(main_username) 11 | 12 | with open("hashtags.txt", "r", encoding='utf-8') as f: 13 | hashtags = [x.strip() for x in f.readlines()] 14 | 15 | for hashtag in tqdm(hashtags, desc="hashtags"): 16 | send.follow_hashtag_medias(hashtag, total=10, delay=15) 17 | 18 | time.sleep(random() * 10 * 60) # 10 minutes between locations and hashtags 19 | -------------------------------------------------------------------------------- /examples/scrapers/users_by_hashtag.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import time 4 | 5 | sys.path.append('../../') 6 | from instabot import User, Getter 7 | 8 | import config # config file from the same folder 9 | import scraper 10 | 11 | parser = argparse.ArgumentParser(add_help=True) 12 | parser.add_argument('hashtag', type=str, nargs='+', 13 | help='hashtag which authors you want to scrape') 14 | args = parser.parse_args() 15 | 16 | get = Getter() 17 | print ("USERS AVAILABLE: %d" % get.controller.queue.qsize()) 18 | 19 | path = config.outputpath + "hashtag_%s.tsv" 20 | 21 | for hashtag in args.hashtag: 22 | iterator = get.hashtag_medias(hashtag) 23 | scraper.save_users_from_media(get, iterator, path % hashtag, batchsleep=12) 24 | time.sleep(120) 25 | -------------------------------------------------------------------------------- /examples/scrapers/users_by_geotag.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import time 4 | 5 | sys.path.append('../../') 6 | from instabot import User, Getter 7 | 8 | import config # config file from the same folder 9 | import scraper 10 | 11 | parser = argparse.ArgumentParser(add_help=True) 12 | parser.add_argument('geotag', type=str, nargs='+', 13 | help='geotag which authors you want to scrape') 14 | args = parser.parse_args() 15 | 16 | get = Getter() 17 | print ("USERS AVAILABLE: %d" % get.controller.queue.qsize()) 18 | 19 | path = config.outputpath + "geotag_%s.tsv" 20 | 21 | for geotag in args.geotag: 22 | location_id = get.geo_id(geotag) 23 | 24 | iterator = get.geo_medias(location_id) 25 | scraper.save_users_from_media(get, iterator, path % geotag, batchsleep=10) 26 | time.sleep(120) 27 | -------------------------------------------------------------------------------- /examples/scrapers/users_by_followers.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import time 4 | 5 | sys.path.append('../../') 6 | from instabot import User, Getter 7 | 8 | import config # config file from the same folder 9 | import scraper 10 | 11 | parser = argparse.ArgumentParser(add_help=True) 12 | parser.add_argument('user', type=str, nargs='+', 13 | help='user which followers you want to scrape') 14 | args = parser.parse_args() 15 | 16 | get = Getter() 17 | print ("USERS AVAILABLE: %d" % get.controller.queue.qsize()) 18 | 19 | path = config.outputpath + "followers_%s.tsv" 20 | 21 | for username in args.user: 22 | user_id = get.user_info(username)["pk"] 23 | iterator = get.user_followers(user_id) 24 | scraper.save_users_from_user(get, iterator, path % username, batchsleep=10) 25 | time.sleep(120) 26 | -------------------------------------------------------------------------------- /examples/crons/likers/hashtag_geotag_liker.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from random import random 4 | from tqdm import tqdm 5 | 6 | sys.path.append('../../../') 7 | from instabot import User, Sender 8 | 9 | main_username = "ohld" # this user should be logged to instapro earlier 10 | send = Sender(main_username) 11 | 12 | with open("hashtags.txt", "r", encoding='utf-8') as f: 13 | hashtags = [x.strip() for x in f.readlines()] 14 | 15 | with open("geotags.txt", "r", encoding='utf-8') as f: 16 | locations = [x.strip() for x in f.readlines()] 17 | 18 | time.sleep(random() * 1 * 60) # 1 minute sleep 19 | 20 | for location in tqdm(locations, desc="locations"): 21 | send.like_geo_medias(location, total=10, delay=20) 22 | 23 | time.sleep(random() * 10 * 60) # 10 minutes between locations and hashtags 24 | 25 | for hashtag in tqdm(hashtags, desc="hashtags"): 26 | send.like_hashtag_medias(hashtag, total=10, delay=20) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # instapro 2 | Professional instagram tool for developers. 3 | 4 | Hi everyone. I tried to make the perfect instagram tool for developers and now that the alpha version of this script has been released, I hope I will find interested developers to help me with it. 5 | I appreciate any help. 6 | 7 | If you are not fluent with python it would be hard enough to use this scripts because there are no ready-to-go examples or clear documentation. 8 | 9 | You can find the examples, on how to use it, in the examples folder. I recommend to look in the _notebooks/_ folder. 10 | 11 | ## Contribution 12 | 13 | If you want to contribute to this project you can 14 | * Star this and our other repositories. 15 | * Just use it! While you use it, you will find bugs and ideas to implement. 16 | * If you have any correction - just create a pull request! 17 | * If you have your own vision of the project and you want to become lead developer, write me about it [me](t.me/okhlopkov). 18 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from instabot import User, api, Sender, Getter 2 | import logging.config 3 | import unittest 4 | from instabot.user.user_controller import UserController 5 | 6 | 7 | class TestUser(unittest.TestCase): 8 | 9 | def test_getter(self): 10 | get = Getter() 11 | print ("USERS AVAILABLE: %d" % get.controller.queue.qsize()) 12 | resp = list(get.user_followers("4456846295")) 13 | self.assertTrue(len(resp) > 0) 14 | resp = list(get.user_following("4456846295", total=5)) 15 | self.assertEqual(len(resp), 5) 16 | resp = list(get.user_feed("4456846295", total=10)) 17 | self.assertEqual(len(resp), 10) 18 | 19 | resp = get.user_info("4456846295") 20 | self.assertEqual(resp["pk"], 4456846295) 21 | resp = get.user_info("ohld") 22 | self.assertEqual(resp["pk"], 352300017) 23 | 24 | resp = list(get.liked_media(total=0)) 25 | self.assertEqual(len(resp), 0) 26 | 27 | def test_sender(self): 28 | send = Sender("instabotproject") 29 | self.assertTrue(send.can_follow("ohld")) 30 | self.assertFalse(send.follow_followers("ohld", total=1)) 31 | 32 | def add_users(): 33 | pass 34 | 35 | if __name__ == '__main__': 36 | logging.config.fileConfig('instabot/log.conf') 37 | log = logging.getLogger('main') 38 | # add_users() 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /instabot/user/user_controller_db.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from instabot.db_helper import DBHelper 3 | 4 | is_py2 = sys.version[0] == '2' 5 | if is_py2: 6 | from Queue import Queue 7 | else: 8 | from queue import Queue 9 | 10 | from .. import config 11 | 12 | users_folder_path = config.PROJECT_FOLDER_PATH + config.USERS_FOLDER_NAME 13 | 14 | 15 | class UserController(object): 16 | instance = None 17 | 18 | def __new__(cls): 19 | if cls.instance is None: 20 | cls.instance = super(UserController, cls).__new__(cls) 21 | return cls.instance 22 | 23 | def __init__(self): 24 | self.queue = Queue() 25 | self.main_user = None 26 | 27 | self.db = DBHelper() 28 | 29 | self.load_all_users() 30 | 31 | @property 32 | def current(self): 33 | if not self.queue.empty(): 34 | temp_user = self.queue.get() 35 | self.queue.put(temp_user) 36 | return temp_user 37 | 38 | @property 39 | def main(self): 40 | # todo проверка, что main_user задан 41 | return self.main_user 42 | 43 | @main.setter 44 | def main(self, user): 45 | self.main_user = user 46 | 47 | @main.deleter 48 | def main(self): 49 | del self.main_user 50 | 51 | def load_all_users(self): 52 | for user in self.db.get_all_users(): 53 | self.queue.put(user) 54 | 55 | def load_user(self, name): 56 | return self.db.get_user(name) 57 | 58 | def save_user(self, user): 59 | self.db.insert_user(user) 60 | 61 | def delete_user(self, user): 62 | self.db.delete_user(user.name) 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File with login and password 2 | secret.txt 3 | 4 | # Files with downloaded data 5 | *.csv 6 | *.tsv 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *,cover 53 | .hypothesis/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # IPython Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # Bot checkpoints 99 | bot_cp_* 100 | 101 | *.checkpoint 102 | 103 | # Saved users 104 | users/ 105 | accounts/ 106 | 107 | # IDE temp 108 | .idea/ 109 | examples/crons/.DS_Store 110 | -------------------------------------------------------------------------------- /instabot/api/request.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | import hashlib 4 | import hmac 5 | import logging 6 | from .. import config 7 | 8 | 9 | class Request(object): 10 | @classmethod 11 | def send(cls, session, endpoint, post=None): 12 | log = logging.getLogger('main') 13 | 14 | try: 15 | if post is not None: # POST 16 | response = session.post( 17 | config.API_URL + endpoint, data=cls.generate_signature(post)) 18 | else: # GET 19 | response = session.get(config.API_URL + endpoint) 20 | if response.status_code == 200: 21 | log.debug("Request OK! Response: " + response.text) 22 | return json.loads(response.text) 23 | else: 24 | try: 25 | s = json.loads(response.text) 26 | if s['message'] == 'checkpoint_required': 27 | warn = s['message'] 28 | if 'checkpoint_url' in s: 29 | warn += " " + s['checkpoint_url'] 30 | log.warning(warn) 31 | return None 32 | except: 33 | error = str(response.text) 34 | log.error("Request return " + 35 | str(response.status_code) + " error: " + error) 36 | if response.status_code == 429: 37 | sleep_minutes = 5 38 | log.warning( 39 | "That means 'too many requests'. I'll go to sleep for %d minutes." % sleep_minutes) 40 | time.sleep(sleep_minutes * 60) 41 | return None 42 | except Exception as e: 43 | log.error("Request error: " + str(e)) 44 | return None 45 | 46 | @staticmethod 47 | def generate_signature(data): 48 | return 'ig_sig_key_version=' + config.SIG_KEY_VERSION + '&signed_body=' + hmac.new( 49 | config.IG_SIG_KEY.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest() + '.' + data 50 | -------------------------------------------------------------------------------- /instabot/api/api.py: -------------------------------------------------------------------------------- 1 | from instabot.api.request import Request 2 | 3 | 4 | def is_id(smth): 5 | ''' checks if input string is a number ''' 6 | if str(smth).isdigit(): 7 | return True 8 | return False 9 | 10 | 11 | def get_user_info(user, user_id): 12 | if is_id(user_id): 13 | return Request.send(user.session, 14 | 'users/' + str(user_id) + '/info/') 15 | else: # username was passed 16 | return Request.send(user.session, 17 | 'users/' + str(user_id) + '/usernameinfo/') 18 | 19 | 20 | def get_user_feed(user, user_id, maxid='', minTimestamp=None): 21 | return Request.send(user.session, 22 | 'feed/user/' + str(user_id) + '/?max_id=' + str(maxid) + '&min_timestamp=' + str(minTimestamp) + 23 | '&rank_token=' + str(user.rank_token) + '&ranked_content=true') 24 | 25 | 26 | def get_user_followers(user, user_id, maxid=''): 27 | return Request.send(user.session, 28 | 'friendships/' + str(user_id) + '/followers/?max_id=' + str(maxid) + '&rank_token=' + user.rank_token) 29 | 30 | 31 | def get_user_following(user, user_id, maxid=''): 32 | return Request.send(user.session, 33 | 'friendships/' + str(user_id) + '/following/?max_id=' + str(maxid) + '&rank_token=' + str(user.rank_token)) 34 | 35 | 36 | def get_liked_media(user, maxid=''): 37 | return Request.send(user.session, 38 | 'feed/liked/?max_id=' + str(maxid)) 39 | 40 | 41 | def search_location(user, query): 42 | return Request.send(user.session, 43 | 'fbsearch/places/?rank_token=' + str(user.rank_token) + '&query=' + str(query)) 44 | 45 | 46 | def get_geo_feed(user, location_id, maxid=''): 47 | return Request.send(user.session, 48 | 'feed/location/' + str(location_id) + '/?max_id=' + str(maxid) + '&rank_token=' + user.rank_token + '&ranked_content=true&') 49 | 50 | 51 | def get_hashtag_feed(user, hashtag, maxid=''): 52 | return Request.send(user.session, 53 | 'feed/tag/' + str(hashtag) + '/?max_id=' + str(maxid) + '&rank_token=' + user.rank_token + '&ranked_content=true&') 54 | -------------------------------------------------------------------------------- /instabot/user/user_controller.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | import warnings 4 | import sys 5 | 6 | is_py2 = sys.version[0] == '2' 7 | if is_py2: 8 | from Queue import Queue 9 | else: 10 | from queue import Queue 11 | 12 | from .. import config 13 | 14 | users_folder_path = config.PROJECT_FOLDER_PATH + config.USERS_FOLDER_NAME 15 | 16 | 17 | class UserController(object): 18 | instance = None 19 | 20 | def __new__(cls): 21 | if cls.instance is None: 22 | cls.instance = super(UserController, cls).__new__(cls) 23 | return cls.instance 24 | 25 | def __init__(self): 26 | self.queue = Queue() 27 | self.main_user = None 28 | 29 | self.load_all_users() 30 | 31 | def load_all_users(self): 32 | for user_path in os.listdir(users_folder_path): 33 | if user_path.endswith('.user'): 34 | username = user_path[:-5] 35 | user = self.load_user(username) 36 | if user is not None: 37 | self.queue.put(user) 38 | 39 | @property 40 | def user(self): 41 | if not self.queue.empty(): 42 | temp_user = self.queue.get() 43 | self.queue.put(temp_user) 44 | return temp_user 45 | else: 46 | print("empty accounts list") 47 | exit(0) 48 | 49 | @property 50 | def main(self): 51 | if self.main_user is not None: 52 | return self.main_user 53 | warnings.warn("No main user was setted. Load it with " 54 | "controller.main = controller.load_user(username). " 55 | "If you use Sender class, the Controller class is in " 56 | "send.controller.") 57 | 58 | @main.setter 59 | def main(self, user): 60 | self.main_user = user 61 | 62 | @main.deleter 63 | def main(self): 64 | del self.main_user 65 | 66 | def load_user(self, name): 67 | input_path = users_folder_path + "%s.user" % name 68 | if not os.path.exists(input_path): 69 | warnings.warn("%s not found." % name) 70 | return None 71 | 72 | with open(input_path, 'rb') as finput: 73 | try: 74 | user = pickle.load(finput) 75 | if not user.logged_in: 76 | user.login() 77 | if not user.logged_in: 78 | return None 79 | return user 80 | except: 81 | warnings.warn("%s is corrupted." % name) 82 | os.remove(input_path) 83 | return None 84 | -------------------------------------------------------------------------------- /instabot/db_helper.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import pickle 3 | from . import config 4 | from .user.user_db import User 5 | 6 | users_folder_path = config.PROJECT_FOLDER_PATH + config.USERS_FOLDER_NAME 7 | 8 | 9 | class DBHelper(object): 10 | def __init__(self): 11 | self.conn = sqlite3.connect(users_folder_path + 'instabot.db') 12 | self.cursor = self.conn.cursor() 13 | create_table_query = '''CREATE TABLE IF NOT EXISTS accounts ( 14 | name text NOT NULL UNIQUE, 15 | password text NOT NULL, 16 | user_id bigint NOT NULL, 17 | device_uuid text NOT NULL, 18 | guid text NOT NULL, 19 | device_id text NOT NULL, 20 | session text NOT NULL, 21 | counters text, 22 | limits text, 23 | delays text, 24 | filters text, 25 | PRIMARY KEY (user_id) 26 | );''' 27 | self.cursor.execute(create_table_query) 28 | 29 | def insert_user(self, user): 30 | try: 31 | user_data = (user.name, user.password, int(user.id), user.device_uuid, user.guid, user.device_id, 32 | pickle.dumps(user.session), None, None, None, None) 33 | self.cursor.execute( 34 | 'INSERT INTO accounts ' 35 | 'VALUES (?,?,?,?,?,?,?,?,?,?,?);', user_data) 36 | except sqlite3.DatabaseError as err: 37 | print("Error: ", err) 38 | else: 39 | self.conn.commit() 40 | 41 | def delete_user(self, username): 42 | try: 43 | self.cursor.execute( 44 | 'DELETE FROM accounts ' 45 | 'WHERE name=(?);', (username,)) 46 | except sqlite3.DatabaseError as err: 47 | print("Error: ", err) 48 | else: 49 | self.conn.commit() 50 | 51 | def get_user(self, name): 52 | self.cursor.execute('SELECT * FROM accounts ' 53 | 'WHERE name=(?);', (name,)) 54 | d = self.cursor.fetchone() 55 | return User(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10]) 56 | 57 | def get_all_users(self): 58 | users = [] 59 | for d in self.cursor.execute('SELECT * from accounts;'): 60 | users.append(User(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10])) 61 | return users -------------------------------------------------------------------------------- /instabot/user/user_db.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import os 4 | import pickle 5 | import uuid 6 | import requests 7 | import logging 8 | from .. import config 9 | from ..api.request import Request 10 | from ..db_helper import DBHelper 11 | 12 | users_folder_path = config.PROJECT_FOLDER_PATH + config.USERS_FOLDER_NAME 13 | 14 | 15 | class Dotdict(dict): 16 | __getattr__ = dict.get 17 | __setattr__ = dict.__setitem__ 18 | __delattr__ = dict.__delitem__ 19 | __repr__ = dict.__repr__ 20 | 21 | def __str__(self): 22 | s = "" 23 | for key, value in self.items(): 24 | s += "%s: %s\n" % (str(key), str(value)) 25 | return s 26 | 27 | def __getstate__(self): 28 | return self.__dict__ 29 | 30 | def __setstate__(self, d): 31 | self.__dict__.update(d) 32 | 33 | 34 | class User(object): 35 | def __init__(self, username, password, id=None, device_uuid=None, guid=None, device_id=None, session=None, n1=None, n2=None, n3=None, n4=None): 36 | self.name = username 37 | self.password = password 38 | if id is None: 39 | self.device_uuid = str(uuid.uuid4()) 40 | self.guid = str(uuid.uuid4()) 41 | self.device_id = 'android-' + \ 42 | hashlib.md5(username.encode('utf-8')).hexdigest()[:16] 43 | self.session = requests.Session() 44 | self.id = None 45 | self.counters = Dotdict({}) 46 | self.limits = Dotdict({}) 47 | self.delays = Dotdict({}) 48 | self.filters = Dotdict({}) 49 | 50 | self.login() 51 | #self.save() 52 | else: 53 | self.device_uuid = device_uuid 54 | self.guid = guid 55 | self.device_id = device_id 56 | self.session = pickle.loads(session) 57 | self.id = id 58 | 59 | self.counters = Dotdict({}) 60 | self.limits = Dotdict({}) 61 | self.delays = Dotdict({}) 62 | self.filters = Dotdict({}) 63 | 64 | def login(self): 65 | self.session.headers.update({ 66 | 'Connection': 'close', 67 | 'Accept': '*/*', 68 | 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 69 | 'Cookie2': '$Version=1', 70 | 'Accept-Language': 'en-US', 71 | 'User-Agent': config.USER_AGENT}) 72 | 73 | data = { 74 | 'phone_id': self.device_uuid, 75 | 'username': self.name, 76 | 'guid': self.guid, 77 | 'device_id': self.device_id, 78 | 'password': self.password, 79 | 'login_attempt_count': '0'} 80 | 81 | message = Request.send( 82 | self.session, 'accounts/login/', json.dumps(data)) 83 | if message is None: 84 | logging.getLogger('main').info(self.name + ' login failed') 85 | return None 86 | self.id = str(message["logged_in_user"]["pk"]) 87 | self.rank_token = "%s_%s" % ( 88 | self.id, self.guid) 89 | logging.getLogger('main').info(self.name + ' successful authorization') 90 | 91 | def dump(self): 92 | items = self.__dict__.copy() 93 | # del items["counters"] 94 | return json.dumps(items, indent=2) -------------------------------------------------------------------------------- /examples/scrapers/scraper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions that are used in scrapper scripts. 3 | (should be moved to instapro core?) 4 | 5 | by: okhlopkov.com 6 | """ 7 | 8 | import sys 9 | import os 10 | import time 11 | import gc 12 | import pandas as pd 13 | from tqdm import tqdm 14 | 15 | 16 | def dump_data(users, output_filename): 17 | data = pd.DataFrame(users) 18 | mode = "w" 19 | if os.path.isfile(output_filename): 20 | mode = "a" 21 | data.to_csv(output_filename, mode=mode, sep="\t", 22 | header=(mode == "w"), index=False) 23 | 24 | 25 | def filter_user_info(ud): 26 | bad_keys = ["hd_profile_pic_url_info", "hd_profile_pic_versions", "external_lynx_url", 27 | "profile_pic_id", "profile_pic_url", "pk"] 28 | for key in bad_keys: 29 | ud.pop(key, None) 30 | return ud 31 | 32 | 33 | def read_usernames(output_filename): 34 | if os.path.isfile(output_filename): 35 | return set(pd.read_table(output_filename, usecols=["username"])["username"]) 36 | return set() 37 | 38 | 39 | def save_users_from_media(get, iterator, output_filename, batchsize=100, batchsleep=12): 40 | pbar = tqdm(desc="unique users") 41 | usernames = read_usernames(output_filename) 42 | pbar.update(len(usernames)) 43 | 44 | i = 0 45 | users = [] 46 | for media in tqdm(iterator, desc="total users"): 47 | if media["user"]["username"] in usernames: 48 | continue 49 | 50 | i += 1 51 | usernames.add(media["user"]["username"]) 52 | 53 | usr = {} 54 | usr["user_id"] = media["user"]["pk"] 55 | if media["user"]["is_private"]: 56 | usr["username"] = media["user"]["username"] 57 | usr["full_name"] = media["user"]["full_name"] 58 | else: 59 | user_info = get.user_info(usr["user_id"]) 60 | usr.update(filter_user_info(user_info)) 61 | 62 | users.append(usr) 63 | pbar.update(1) 64 | if i % batchsize == 0: 65 | dump_data(users, output_filename) 66 | users = [] 67 | gc.collect() 68 | time.sleep(batchsleep) 69 | 70 | dump_data(users, output_filename) 71 | users = [] 72 | pbar.close() 73 | 74 | 75 | def save_users_from_user(get, iterator, output_filename, batchsize=100, batchsleep=12): 76 | pbar = tqdm(desc="unique users") 77 | usernames = read_usernames(output_filename) 78 | pbar.update(len(usernames)) 79 | 80 | i = 0 81 | users = [] 82 | for _user in tqdm(iterator, desc="total users"): 83 | if _user["username"] in usernames: 84 | continue 85 | 86 | i += 1 87 | usernames.add(_user["username"]) 88 | 89 | usr = {} 90 | usr["user_id"] = _user["pk"] 91 | if _user["is_private"]: 92 | usr["username"] = _user["username"] 93 | usr["full_name"] = _user["full_name"] 94 | else: 95 | user_info = get.user_info(usr["user_id"]) 96 | usr.update(filter_user_info(user_info)) 97 | 98 | users.append(usr) 99 | pbar.update(1) 100 | if i % batchsize == 0: 101 | dump_data(users, output_filename) 102 | users = [] 103 | gc.collect() 104 | time.sleep(batchsleep) 105 | 106 | dump_data(users, output_filename) 107 | users = [] 108 | pbar.close() 109 | -------------------------------------------------------------------------------- /instabot/user/user.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import os 4 | import pickle 5 | import uuid 6 | import requests 7 | import logging 8 | from .. import config 9 | from ..api.request import Request 10 | 11 | users_folder_path = config.PROJECT_FOLDER_PATH + config.USERS_FOLDER_NAME 12 | 13 | 14 | class Dotdict(dict): 15 | __getattr__ = dict.get 16 | __setattr__ = dict.__setitem__ 17 | __delattr__ = dict.__delitem__ 18 | __repr__ = dict.__repr__ 19 | 20 | def __str__(self): 21 | s = "" 22 | for key, value in self.items(): 23 | s += "%s: %s\n" % (str(key), str(value)) 24 | return s 25 | 26 | def __getstate__(self): 27 | return self.__dict__ 28 | 29 | def __setstate__(self, d): 30 | self.__dict__.update(d) 31 | 32 | 33 | class User(object): 34 | def __init__(self, username, password): 35 | self.name = username 36 | self.password = password 37 | self.device_uuid = str(uuid.uuid4()) 38 | self.guid = str(uuid.uuid4()) 39 | self.device_id = 'android-' + \ 40 | hashlib.md5(username.encode('utf-8')).hexdigest()[:16] 41 | self.session = requests.Session() 42 | self.id = None 43 | self.logged_id = False 44 | self.counters = Dotdict({}) 45 | self.limits = Dotdict({}) 46 | self.delays = Dotdict({}) 47 | self.filters = Dotdict({}) 48 | 49 | if not self.login(): 50 | return None 51 | 52 | def login(self): 53 | self.session.headers.update({ 54 | 'Connection': 'close', 55 | 'Accept': '*/*', 56 | 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 57 | 'Cookie2': '$Version=1', 58 | 'Accept-Language': 'en-US', 59 | 'User-Agent': config.USER_AGENT}) 60 | 61 | data = { 62 | 'phone_id': self.device_uuid, 63 | 'username': self.name, 64 | 'guid': self.guid, 65 | 'device_id': self.device_id, 66 | 'password': self.password, 67 | 'login_attempt_count': '0'} 68 | 69 | message = Request.send( 70 | self.session, 'accounts/login/', json.dumps(data)) 71 | if message is None: 72 | logging.getLogger('main').warning(self.name + ' login failed') 73 | self.logged_in = False 74 | self.save() 75 | return False 76 | self.id = str(message["logged_in_user"]["pk"]) 77 | self.rank_token = "%s_%s" % (self.id, self.guid) 78 | self.logged_in = True 79 | logging.getLogger('main').info(self.name + ' successful authorization') 80 | self.save() 81 | return True 82 | 83 | def save(self): 84 | if not os.path.exists(users_folder_path): 85 | if not os.path.exists(config.PROJECT_FOLDER_PATH): 86 | os.makedirs(config.PROJECT_FOLDER_PATH) 87 | os.makedirs(users_folder_path) 88 | output_path = users_folder_path + "%s.user" % self.name 89 | with open(output_path, 'wb') as foutput: 90 | pickle.dump(self, foutput) 91 | 92 | def delete(self): 93 | input_path = users_folder_path + "%s.user" % self.name 94 | if os.path.exists(input_path): 95 | os.remove(input_path) 96 | 97 | def dump(self): 98 | items = self.__dict__.copy() 99 | # del items["counters"] 100 | return json.dumps(items, indent=2) 101 | -------------------------------------------------------------------------------- /instabot/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | 5 | API_URL = 'https://i.instagram.com/api/v1/' 6 | DEVICE_SETTINTS = { 7 | 'manufacturer': 'Xiaomi', 8 | 'model': 'HM 1SW', 9 | 'android_version': 18, 10 | 'android_release': '4.3' 11 | } 12 | USER_AGENT = 'Instagram 9.2.0 Android ({android_version}/{android_release}; 320dpi; 720x1280; {manufacturer}; {model}; armani; qcom; en_US)'.format( 13 | **DEVICE_SETTINTS) 14 | IG_SIG_KEY = '012a54f51c49aa8c5c322416ab1410909add32c966bbaa0fe3dc58ac43fd7ede' 15 | EXPERIMENTS = 'ig_android_progressive_jpeg,ig_creation_growth_holdout,ig_android_report_and_hide,ig_android_new_browser,ig_android_enable_share_to_whatsapp,ig_android_direct_drawing_in_quick_cam_universe,ig_android_huawei_app_badging,ig_android_universe_video_production,ig_android_asus_app_badging,ig_android_direct_plus_button,ig_android_ads_heatmap_overlay_universe,ig_android_http_stack_experiment_2016,ig_android_infinite_scrolling,ig_fbns_blocked,ig_android_white_out_universe,ig_android_full_people_card_in_user_list,ig_android_post_auto_retry_v7_21,ig_fbns_push,ig_android_feed_pill,ig_android_profile_link_iab,ig_explore_v3_us_holdout,ig_android_histogram_reporter,ig_android_anrwatchdog,ig_android_search_client_matching,ig_android_high_res_upload_2,ig_android_new_browser_pre_kitkat,ig_android_2fac,ig_android_grid_video_icon,ig_android_white_camera_universe,ig_android_disable_chroma_subsampling,ig_android_share_spinner,ig_android_explore_people_feed_icon,ig_explore_v3_android_universe,ig_android_media_favorites,ig_android_nux_holdout,ig_android_search_null_state,ig_android_react_native_notification_setting,ig_android_ads_indicator_change_universe,ig_android_video_loading_behavior,ig_android_black_camera_tab,liger_instagram_android_univ,ig_explore_v3_internal,ig_android_direct_emoji_picker,ig_android_prefetch_explore_delay_time,ig_android_business_insights_qe,ig_android_direct_media_size,ig_android_enable_client_share,ig_android_promoted_posts,ig_android_app_badging_holdout,ig_android_ads_cta_universe,ig_android_mini_inbox_2,ig_android_feed_reshare_button_nux,ig_android_boomerang_feed_attribution,ig_android_fbinvite_qe,ig_fbns_shared,ig_android_direct_full_width_media,ig_android_hscroll_profile_chaining,ig_android_feed_unit_footer,ig_android_media_tighten_space,ig_android_private_follow_request,ig_android_inline_gallery_backoff_hours_universe,ig_android_direct_thread_ui_rewrite,ig_android_rendering_controls,ig_android_ads_full_width_cta_universe,ig_video_max_duration_qe_preuniverse,ig_android_prefetch_explore_expire_time,ig_timestamp_public_test,ig_android_profile,ig_android_dv2_consistent_http_realtime_response,ig_android_enable_share_to_messenger,ig_explore_v3,ig_ranking_following,ig_android_pending_request_search_bar,ig_android_feed_ufi_redesign,ig_android_video_pause_logging_fix,ig_android_default_folder_to_camera,ig_android_video_stitching_7_23,ig_android_profanity_filter,ig_android_business_profile_qe,ig_android_search,ig_android_boomerang_entry,ig_android_inline_gallery_universe,ig_android_ads_overlay_design_universe,ig_android_options_app_invite,ig_android_view_count_decouple_likes_universe,ig_android_periodic_analytics_upload_v2,ig_android_feed_unit_hscroll_auto_advance,ig_peek_profile_photo_universe,ig_android_ads_holdout_universe,ig_android_prefetch_explore,ig_android_direct_bubble_icon,ig_video_use_sve_universe,ig_android_inline_gallery_no_backoff_on_launch_universe,ig_android_image_cache_multi_queue,ig_android_camera_nux,ig_android_immersive_viewer,ig_android_dense_feed_unit_cards,ig_android_sqlite_dev,ig_android_exoplayer,ig_android_add_to_last_post,ig_android_direct_public_threads,ig_android_prefetch_venue_in_composer,ig_android_bigger_share_button,ig_android_dv2_realtime_private_share,ig_android_non_square_first,ig_android_video_interleaved_v2,ig_android_follow_search_bar,ig_android_last_edits,ig_android_video_download_logging,ig_android_ads_loop_count_universe,ig_android_swipeable_filters_blacklist,ig_android_boomerang_layout_white_out_universe,ig_android_ads_carousel_multi_row_universe,ig_android_mentions_invite_v2,ig_android_direct_mention_qe,ig_android_following_follower_social_context' 16 | SIG_KEY_VERSION = '4' 17 | 18 | #-- 19 | 20 | PROJECT_FOLDER_PATH = os.path.expanduser("~/instabot/") 21 | USERS_FOLDER_NAME = 'users/' 22 | 23 | #-- 24 | 25 | LOGGING_LEVEL = logging.DEBUG 26 | LOG_FILE = None 27 | -------------------------------------------------------------------------------- /examples/notebooks/sender_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Sender example\n", 8 | "\n", 9 | "The example of simple Sender class usage." 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Preparations\n", 17 | "Import instabot from sources" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": { 24 | "collapsed": false 25 | }, 26 | "outputs": [], 27 | "source": [ 28 | "import sys\n", 29 | "sys.path.append('../../')\n", 30 | "from instabot import User, Sender" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "Login users to be used in instabot. I suggest you to add as many users as you have because all get requests will be parallized between them to distribute the Instagram servers load. " 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 2, 43 | "metadata": { 44 | "collapsed": false 45 | }, 46 | "outputs": [], 47 | "source": [ 48 | "_ = User('instabotproject', 'password')\n", 49 | "_ = User(\"user_for_scrapping1\", \"password\")\n", 50 | "_ = User(\"user_for_scrapping2\", \"password\")\n", 51 | "_ = User(\"user_for_scrapping3\", \"password\")" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "Init the Sender class with the main user's username. This user will be used to put likes / follows and other activity stuff. " 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 3, 64 | "metadata": { 65 | "collapsed": true 66 | }, 67 | "outputs": [], 68 | "source": [ 69 | "send = Sender(\"instabotproject\")" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "## Activity examples\n", 77 | "You can like medias by geolocation like this" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 4, 83 | "metadata": { 84 | "collapsed": false 85 | }, 86 | "outputs": [ 87 | { 88 | "name": "stderr", 89 | "output_type": "stream", 90 | "text": [ 91 | "like medias: 23it [01:32, 5.66s/it]../../instabot/sender/sender.py:102: UserWarning: Error while liking stanislavkruglik's media.\n", 92 | " \"Error while liking %s's media.\" % media['user']['username'])\n", 93 | "like medias: 100it [07:31, 4.52s/it]\n" 94 | ] 95 | }, 96 | { 97 | "data": { 98 | "text/plain": [ 99 | "False" 100 | ] 101 | }, 102 | "execution_count": 4, 103 | "metadata": {}, 104 | "output_type": "execute_result" 105 | } 106 | ], 107 | "source": [ 108 | "geo_name = \"МФТИ\"\n", 109 | "send.like_geo_medias(geo_name, total=100, delay=5)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "To follow someone's followers code like this" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 5, 122 | "metadata": { 123 | "collapsed": false 124 | }, 125 | "outputs": [ 126 | { 127 | "name": "stderr", 128 | "output_type": "stream", 129 | "text": [ 130 | "follow users: 10it [01:57, 12.73s/it]\n" 131 | ] 132 | }, 133 | { 134 | "data": { 135 | "text/plain": [ 136 | "False" 137 | ] 138 | }, 139 | "execution_count": 5, 140 | "metadata": {}, 141 | "output_type": "execute_result" 142 | } 143 | ], 144 | "source": [ 145 | "follow_followers_of = \"ohld\"\n", 146 | "send.follow_followers(follow_followers_of, total=10, delay=10)" 147 | ] 148 | } 149 | ], 150 | "metadata": { 151 | "kernelspec": { 152 | "display_name": "Python 3", 153 | "language": "python", 154 | "name": "python3" 155 | }, 156 | "language_info": { 157 | "codemirror_mode": { 158 | "name": "ipython", 159 | "version": 3 160 | }, 161 | "file_extension": ".py", 162 | "mimetype": "text/x-python", 163 | "name": "python", 164 | "nbconvert_exporter": "python", 165 | "pygments_lexer": "ipython3", 166 | "version": "3.5.1" 167 | } 168 | }, 169 | "nbformat": 4, 170 | "nbformat_minor": 0 171 | } 172 | -------------------------------------------------------------------------------- /instabot/sender/sender.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import warnings 4 | from random import random 5 | from tqdm import tqdm 6 | 7 | from random import random 8 | 9 | from ..api.request import Request 10 | from ..getter import Getter 11 | from ..user import UserController, User 12 | 13 | 14 | class Sender(object): 15 | 16 | def __init__(self, username, password=None): 17 | self.get = Getter() 18 | self.controller = self.get.controller # for shortage 19 | if password is not None: 20 | user = User(username, password) 21 | if user is not None: 22 | self.controller.main = user 23 | else: 24 | return None 25 | else: 26 | self.controller.main = self.controller.load_user(username) 27 | self.main = self.controller.main # for shortage 28 | 29 | def sleep(self, delay): 30 | time.sleep(delay * 0.5 + random() * delay) 31 | 32 | def can_follow(self, usr=None): 33 | if usr is None: 34 | return False 35 | # check filter 36 | # check limit 37 | # check counters 38 | if isinstance(usr, dict): 39 | if not hasattr(self.main, 'following'): 40 | self.main.following = set( 41 | [usr['pk'] for usr in self.get.user_following(self.main.id)]) 42 | if usr['pk'] in self.main.following: 43 | return False 44 | if 'is_business' in usr and usr['is_business']: 45 | return False 46 | if 'is_verified' in usr and usr['is_verified']: 47 | return False 48 | 49 | return True 50 | 51 | def can_unfollow(self, usr=None): 52 | # check whitelist 53 | return True 54 | 55 | def can_like(self, md=None): 56 | if md is None: 57 | return False 58 | # check filter 59 | # check limit 60 | # check counters 61 | if isinstance(md, dict): 62 | if 'has_liked' in md and md['has_liked']: 63 | return False 64 | return True 65 | 66 | def follow_followers(self, main_target, total=None, delay=15): 67 | if not str(main_target).isdigit(): 68 | main_target = self.get.user_info(main_target)['pk'] 69 | return self.follow_users(self.get.user_followers(main_target, total=total), delay=delay) 70 | 71 | def follow_following(self, main_target, total=None, delay=15): 72 | if not str(main_target).isdigit(): 73 | main_target = self.get.user_info(main_target)['pk'] 74 | return self.follow_users(self.get.user_following(main_target, total=total), delay=delay) 75 | 76 | def follow_users(self, targets, delay=15): 77 | for target in tqdm(targets, desc='follow users'): 78 | res = self.follow(target) 79 | if res is None: 80 | warnings.warn('Error while following %s.' % target['username']) 81 | if res: 82 | self.sleep(delay) 83 | self.main.save() 84 | return False # exitcode 0 - no errors 85 | 86 | def follow(self, target): 87 | if self.can_follow(target): 88 | ret = Request.send(self.controller.main.session, 'friendships/create/' + str(target['user']['pk']) + '/', 89 | '{}') 90 | if 'follows' not in self.main.counters: 91 | self.main.counters.follows = 0 92 | self.main.counters.follows += 1 93 | return ret 94 | return False 95 | 96 | def unfollow_users(self, targets, delay=15): 97 | for target in tqdm(targets, desc='unfollow users'): 98 | res = self.unfollow(target) 99 | if res is None: 100 | warnings.warn('Error while unfollowing %s.' % target['username']) 101 | if res: 102 | self.sleep(delay) 103 | self.main.save() 104 | return False # exitcode 0 - no errors 105 | 106 | def unfollow(self, target): 107 | if self.can_unfollow(target): 108 | ret = Request.send(self.controller.main.session, 'friendships/destroy/' + str(target['pk']) + '/', '{}') 109 | if 'unfollows' not in self.main.counters: 110 | self.main.counters.unfollows = 0 111 | self.main.counters.unfollows += 1 112 | return ret 113 | return False 114 | 115 | def like(self, media): 116 | if self.can_like(media): 117 | ret = Request.send(self.controller.main.session, 118 | 'media/' + str(media['pk']) + '/like/', '{}') 119 | if 'likes' not in self.main.counters: 120 | self.main.counters.likes = 0 121 | self.main.counters.likes += 1 122 | return ret 123 | return False 124 | 125 | def like_medias(self, medias, delay=5): 126 | for media in tqdm(medias, 'like medias'): 127 | res = self.like(media) 128 | if res is None: 129 | warnings.warn( 130 | "Error while liking %s's media." % media['user']['username']) 131 | if res: 132 | self.sleep(delay) 133 | self.main.save() 134 | return False # exitcode 0 - no errors 135 | 136 | def follow_medias(self, medias, delay=15): 137 | for media in tqdm(medias, 'follow medias'): 138 | res = self.follow(media) 139 | if res is None: 140 | warnings.warn( 141 | "Error while following %s's media." % media['user']['username']) 142 | if res: 143 | self.sleep(delay) 144 | self.main.save() 145 | return False # exitcode 0 - no errors 146 | 147 | def like_geo_medias(self, location, total=None, delay=5): 148 | if not str(location).isdigit(): 149 | location = self.get.geo_id(location) 150 | return self.like_medias(self.get.geo_medias(location, total=total), delay=delay) 151 | 152 | def like_hashtag_medias(self, hashtag, total=None, delay=5): 153 | return self.like_medias(self.get.hashtag_medias(hashtag, total=total), delay=delay) 154 | 155 | def follow_hashtag_medias(self, hashtag, total=None, delay=15): 156 | return self.follow_medias(self.get.hashtag_medias(hashtag, total=total), delay=delay) 157 | -------------------------------------------------------------------------------- /instabot/getter/getter.py: -------------------------------------------------------------------------------- 1 | import time 2 | import warnings 3 | import sys 4 | from random import random 5 | 6 | is_py2 = sys.version[0] == '2' 7 | if is_py2: 8 | from Queue import Queue 9 | else: 10 | from queue import Queue 11 | from instabot.api import api 12 | from instabot.user.user_controller import UserController 13 | 14 | 15 | class Getter(object): 16 | def __init__(self): 17 | self.controller = UserController() 18 | 19 | def error_handler(func): 20 | def error_handler_wrapper(*args, **kwargs): 21 | self = args[0] 22 | while True: 23 | kwargs['user'] = self.controller.user 24 | try: 25 | return func(*args, **kwargs) 26 | except Exception as e: 27 | warnings.warn('GETTER: ' + str(e)) 28 | time.sleep(60 * random()) 29 | continue 30 | 31 | return error_handler_wrapper 32 | 33 | @error_handler 34 | def _get_user_followers(self, user_id, max_id='', user=None): 35 | if user is None: 36 | raise Exception("No User instance was passed") 37 | resp = api.get_user_followers(user, user_id, maxid=max_id) 38 | if resp is None: 39 | raise Exception("Broken User: %s" % user.name) 40 | if "next_max_id" not in resp: # or "big_list" in resp and not resp['big_list']: 41 | return (resp['users'], None) 42 | return (resp['users'], resp['next_max_id']) 43 | 44 | @error_handler 45 | def _get_user_following(self, user_id, max_id="", user=None): 46 | if user is None: 47 | raise Exception("No User instance was passed") 48 | resp = api.get_user_following(user, user_id, maxid=max_id) 49 | if resp is None: 50 | raise Exception("Broken User") 51 | if "next_max_id" not in resp: # or "big_list" in resp and not resp["big_list"]: 52 | return (resp["users"], None) 53 | return (resp["users"], resp["next_max_id"]) 54 | 55 | @error_handler 56 | def _get_user_info(self, user_id, user=None): 57 | if user is None: 58 | raise Exception("No User instance was passed") 59 | resp = api.get_user_info(user, user_id) 60 | if resp is None: 61 | raise Exception("Broken User") 62 | if "user" in resp: 63 | return resp["user"] 64 | return None 65 | 66 | @error_handler 67 | def _get_geo_id(self, location_name, user=None): 68 | if user is None: 69 | raise Exception("No User instance was passed") 70 | resp = api.search_location(user, location_name) 71 | if resp is None: 72 | raise Exception("Broken User") 73 | try: 74 | return resp["items"][0]['location']['pk'] 75 | except: 76 | return None 77 | 78 | @error_handler 79 | def _get_user_feed(self, user_id, max_id="", user=None): 80 | if user is None: 81 | raise Exception("No User instance was passed") 82 | resp = api.get_user_feed(user, user_id, maxid=max_id) 83 | if resp is None: 84 | # just user is private 85 | return ([], None) 86 | if "next_max_id" not in resp or "more_available" in resp and not resp["more_available"]: 87 | return (resp["items"], None) 88 | return (resp["items"], resp["next_max_id"]) 89 | 90 | @error_handler 91 | def _get_liked_media(self, max_id="", user=None): 92 | if user is None: 93 | raise Exception("No API instance was passed") 94 | resp = api.get_liked_media(user, max_id) 95 | if resp is None: 96 | raise Exception("Broken API") 97 | if "next_max_id" not in resp or "more_available" in resp and not resp["more_available"]: 98 | return (resp["items"], None) 99 | return (resp["items"], resp["next_max_id"]) 100 | 101 | @error_handler 102 | def _get_geo_medias(self, location_id, max_id="", user=None): 103 | if user is None: 104 | raise Exception("No API instance was passed") 105 | resp = api.get_geo_feed(user, location_id, max_id) 106 | if resp is None: 107 | raise Exception("Broken API") 108 | if "next_max_id" not in resp or "more_available" in resp and not resp["more_available"]: 109 | return (resp["items"], None) 110 | return (resp["items"], resp["next_max_id"]) 111 | 112 | @error_handler 113 | def _get_hashtag_medias(self, hashtag, max_id="", user=None): 114 | if user is None: 115 | raise Exception("No API instance was passed") 116 | resp = api.get_hashtag_feed(user, hashtag, max_id) 117 | if resp is None: 118 | raise Exception("Broken API") 119 | if "next_max_id" not in resp or "more_available" in resp and not resp["more_available"]: 120 | return (resp["items"], None) 121 | return (resp["items"], resp["next_max_id"]) 122 | 123 | @staticmethod 124 | def generator(func, arg, total=None): 125 | max_id = "" 126 | count = 0 127 | while True: 128 | time.sleep(1 * random()) 129 | if max_id is None or total is not None and total < count: 130 | break 131 | if arg is not None: 132 | resp = func(arg, max_id=max_id) 133 | else: 134 | resp = func(max_id=max_id) 135 | if resp is None: 136 | time.sleep(10 * random()) 137 | continue 138 | items, max_id = resp 139 | for item in items: 140 | count += 1 141 | if total is not None and total < count: 142 | break 143 | yield item 144 | 145 | def user_info(self, user_id): 146 | """ returns dict with user's info. You can pass as username as user_id. """ 147 | time.sleep(1 * random()) 148 | return self._get_user_info(user_id) 149 | 150 | def user_followers(self, user_id, total=None): 151 | """ generator to iterate over user's followers """ 152 | return self.generator(self._get_user_followers, user_id, total=total) 153 | 154 | def user_following(self, user_id, total=None): 155 | """ generator to iterate over user's following """ 156 | return self.generator(self._get_user_following, user_id, total=total) 157 | 158 | def user_feed(self, user_id, total=None): 159 | """ generator to iterate over user feed """ 160 | return self.generator(self._get_user_feed, user_id, total=total) 161 | 162 | # TODO: make this method iterate over main user's liked media 163 | # def liked_media(self, total=None): 164 | # """ generator to iterate over liked medias """ 165 | # return self.generator(self._get_liked_media, None, total=total) 166 | 167 | def geo_medias(self, location_id, total=None): 168 | """ generator to iterate over geo medias """ 169 | return self.generator(self._get_geo_medias, location_id, total=total) 170 | 171 | def hashtag_medias(self, hashtag, total=None): 172 | """ generator to iterate over hashtag medias """ 173 | return self.generator(self._get_hashtag_medias, hashtag, total=total) 174 | 175 | def geo_id(self, location_name): 176 | return self._get_geo_id(location_name) 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/notebooks/getter_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Getter example\n", 8 | "\n", 9 | "The example of simple Getter class usage and even simpler analysis on recieved data." 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Preparations\n", 17 | "Import instabot from sources" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": { 24 | "collapsed": true 25 | }, 26 | "outputs": [], 27 | "source": [ 28 | "import sys\n", 29 | "sys.path.append('../../')\n", 30 | "from instabot import User, Getter" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "Login users to be used in instabot. I suggest you to add as many users as you have because all get requests will be parallized between them to distribute the Instagram servers load. " 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 2, 43 | "metadata": { 44 | "collapsed": true 45 | }, 46 | "outputs": [], 47 | "source": [ 48 | "_ = User(\"user_for_scrapping1\", \"password\")\n", 49 | "_ = User(\"user_for_scrapping2\", \"password\")\n", 50 | "_ = User(\"user_for_scrapping3\", \"password\")" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "Init the Getter class without any parameters. It will use all of the available and successfully logged in users to parallize the get requests to Instagram's servers." 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 3, 63 | "metadata": { 64 | "collapsed": false 65 | }, 66 | "outputs": [], 67 | "source": [ 68 | "get = Getter()" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "## Usage cases\n", 76 | "### Users who posted with geotag\n", 77 | "Almost all Getter methods return generators to iterate over medias or users. But some of them such as __get.geo_id__ or __get.user_info__ return single value: the number or the dictionary (json-like)." 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 4, 83 | "metadata": { 84 | "collapsed": false 85 | }, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "The id of МФТИ is 1433831.\n" 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "location_name = \"МФТИ\"\n", 97 | "location_id = get.geo_id(location_name)\n", 98 | "print (\"The id of %s is %d.\" % (location_name, location_id))" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "For example you want to know who posts with specific geotag. You can iterate over medias and take the author's username. \n", 106 | "\n", 107 | "Get iterator over geo medias" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 5, 113 | "metadata": { 114 | "collapsed": true 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "geo_medias = get.geo_medias(location_id, total=10)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 6, 124 | "metadata": { 125 | "collapsed": false 126 | }, 127 | "outputs": [ 128 | { 129 | "name": "stdout", 130 | "output_type": "stream", 131 | "text": [ 132 | "Users who post with МФТИ geotag:\n", 133 | "alissonramos539\n", 134 | "kir_ilya\n", 135 | "blackgannet\n", 136 | "secchiarolli\n", 137 | "tinyshortslongarms\n", 138 | "igorturin1981\n", 139 | "rybkinaliza\n", 140 | "g_v_001\n", 141 | "mistergahan\n", 142 | "a_talyzina\n" 143 | ] 144 | } 145 | ], 146 | "source": [ 147 | "print (\"Users who post with %s geotag:\" % location_name)\n", 148 | "for media in geo_medias:\n", 149 | " print (media[\"user\"][\"username\"])" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "All the values that are in response media's json:" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 7, 162 | "metadata": { 163 | "collapsed": false, 164 | "scrolled": false 165 | }, 166 | "outputs": [ 167 | { 168 | "data": { 169 | "text/plain": [ 170 | "dict_keys(['client_cache_key', 'has_liked', 'photo_of_you', 'comments', 'preview_comments', 'comment_count', 'media_type', 'max_num_visible_preview_comments', 'lat', 'lng', 'code', 'user', 'comment_likes_enabled', 'image_versions2', 'like_count', 'taken_at', 'has_more_comments', 'filter_type', 'device_timestamp', 'original_height', 'caption_is_edited', 'location', 'original_width', 'id', 'caption', 'organic_tracking_token', 'pk', 'usertags'])" 171 | ] 172 | }, 173 | "execution_count": 7, 174 | "metadata": {}, 175 | "output_type": "execute_result" 176 | } 177 | ], 178 | "source": [ 179 | "media.keys()" 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "### User's mean likes\n", 187 | "Another use case: count the mean and std of recieved likes of specific user." 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": 8, 193 | "metadata": { 194 | "collapsed": false 195 | }, 196 | "outputs": [ 197 | { 198 | "name": "stdout", 199 | "output_type": "stream", 200 | "text": [ 201 | "The id of 'ohld' is 352300017.\n" 202 | ] 203 | } 204 | ], 205 | "source": [ 206 | "username = \"ohld\"\n", 207 | "user_info = get.user_info(username)\n", 208 | "user_id = user_info[\"pk\"]\n", 209 | "print (\"The id of '%s' is %d.\" % (username, user_id))" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 9, 215 | "metadata": { 216 | "collapsed": false 217 | }, 218 | "outputs": [ 219 | { 220 | "name": "stdout", 221 | "output_type": "stream", 222 | "text": [ 223 | "Amount of likes recieved by ohld\n", 224 | "[330, 168, 208, 1829, 1516, 532, 343, 307, 347, 390, 523, 391, 750, 1046, 2760, 276, 157, 192, 193, 274]\n", 225 | "Mean: 626.60. Total: 12532\n" 226 | ] 227 | } 228 | ], 229 | "source": [ 230 | "mean = lambda l: 0 if l == [] else sum(l) * 1. / len(l)\n", 231 | "\n", 232 | "like_counts = [media[\"like_count\"] for media in get.user_feed(user_id, total=20)]\n", 233 | "print (\"Amount of likes recieved by %s\" % username)\n", 234 | "print (like_counts)\n", 235 | "print (\"Mean: %.2f. Total: %d\" % (mean(like_counts), sum(like_counts)))" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "metadata": {}, 241 | "source": [ 242 | "### Mean likes of every follower\n", 243 | "So let's test the Getter module with __hard task__: calculate mean likes of every follower and make some analysis." 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 10, 249 | "metadata": { 250 | "collapsed": false 251 | }, 252 | "outputs": [ 253 | { 254 | "name": "stdout", 255 | "output_type": "stream", 256 | "text": [ 257 | "\n" 258 | ] 259 | } 260 | ], 261 | "source": [ 262 | "from tqdm import tqdm_notebook # to see the progress of scrapping\n", 263 | "\n", 264 | "mean_likes = {}\n", 265 | "for user in tqdm_notebook(get.user_followers(user_id), total=user_info[\"follower_count\"]):\n", 266 | " like_counts = [media['like_count'] for media in get.user_feed(user['pk'], total=5)]\n", 267 | " mean_likes[user[\"username\"]] = mean(like_counts)" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": 11, 273 | "metadata": { 274 | "collapsed": false 275 | }, 276 | "outputs": [ 277 | { 278 | "data": { 279 | "image/png": "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\nAElFTkSuQmCC\n", 280 | "text/plain": [ 281 | "" 282 | ] 283 | }, 284 | "metadata": {}, 285 | "output_type": "display_data" 286 | } 287 | ], 288 | "source": [ 289 | "import matplotlib.pyplot as plt\n", 290 | "%matplotlib inline\n", 291 | "\n", 292 | "plt.figure(figsize=(15, 5))\n", 293 | "plt.hist([i for i in list(mean_likes.values()) if i > 0], bins=500)\n", 294 | "plt.title(\"Mean likes of %s followers\" % username)\n", 295 | "plt.xlabel(\"Mean likes\")\n", 296 | "plt.ylabel(\"Frequency\")\n", 297 | "plt.show()" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": 12, 303 | "metadata": { 304 | "collapsed": false 305 | }, 306 | "outputs": [ 307 | { 308 | "data": { 309 | "image/png": "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\nxvqbDG/F/AjwT830/1rHsb2b4Wd6CcNKlu9dY78rt3MwcSxfd6313s/wFtRLV8zfnvBV1dsZPrd3\nbnOefAQ4cUys92L4nwlfYdjD/CVg7ODxkqR2ZDOGHWr+9/Vy4N+q6qQkpzEsFPCF5i0vb34RSpIk\nSZKmZLOKtryQ4f++3mvZsldX1as3af+SJEmStO20fktnkmMY3kZz5sqmtvctSZIkSdvZZjzD9xqG\nlblW3jv6/CRXJjlztQFqJUmSJEkb02rCl+SpwFJVXcmde/TOAI6tqr0Mx3Dy1k5JkiRJmrJWi7Yk\neSXw48DXgcMYlvh+a1X95LL37AEuqKpHrrJ++xVlJEmSJGmGVdW6H4fblCqdAEkeD7ykqdI5qKp9\nzfIXMRzU9zmrrFObFZ+2toWFBRYWFroOQ1uE54sm5bmig+H5okl5ruhgJNlQwrdZVTpX+u0kexkO\nwnoN8LyO4pAkSZKk3tq0hK+q3k8ziOvyWzolSZIkSe3YjCqdUuvm5+e7DkFbiOeLJuW5ooPh+aJJ\nea5oM23aM3zr4TN8kiRJkrazjT7DZw+fJEmSJPWUCZ8kSZIk9ZQJnyRJkiT1lAmfJEmSJPWUCZ8k\nSZIk9ZQJnyRJkiT1lAmfJEmSJPWUCZ8kSZIk9ZQJnyRJkiT1lAmfJEmSJPWUCZ8kSZIk9ZQJnyRJ\nkiT1lAlfzw0GcyQZ+RoM5roOUZIkSVJLUlVdxzBSkprl+LaCJMC4zzD4GUuSJEmzKQlVlfWubw+f\nJEmSJPWUCZ8kSZIk9ZQJnyRJkiT1lAmfJEmSJPWUCZ8kSZIk9ZQJnyRJkiT11KYkfEl2JPlwknc2\n80cluSjJJ5JcmOTIzYhDkiRJkraTzerheyFw1bL5U4GLq+phwCXAyzYpDkmSJEnaNlpP+JIcAzwF\nOHPZ4pOBs5rps4Cntx2HJEmSJG03m9HD9xrgl4Fatmx3VS0BVNU+4OhNiEOSJEmStpVWE74kTwWW\nqupKIGPeWmPaJEmSJEnrcEjL2z8eOCnJU4DDgHsmORvYl2R3VS0lGQBfGLWBhYWF26fn5+eZn59v\nN2JJkiRJ6sji4iKLi4tT216qNqdzLcnjgZdU1UlJfhv4clWdnuSlwFFVdeoq69RmxddXSRjfgRr8\njCVJkqTZlISqGne35FhdjcP3W8AJST4BfF8zL0mSJEmaok3r4VsPe/g2zh4+SZIkaevaqj18kiRJ\nkqSWmfBJkiRJUk+Z8HVsMJgjycjXYDDXdYiSJEmStiif4etY28/Y+QyfJEmStHX5DJ8kSZIkaVUm\nfJIkSZLUUyZ8kiRJktRTJnySJEmS1FMmfJIkSZLUUyZ8kiRJktRTJnySJEmS1FMmfJIkSZLUUyZ8\nkiRJktRTJnySJEmS1FMmfJIkSZLUUyZ8kiRJktRTJnySJEmS1FMmfJIkSZLUUyZ8kiRJktRTJnyS\nJEmS1FMmfJIkSZLUUyZ8294ukox9DQZzXQcpSZIkaR1aTfiS7EryoSRXJPl4klc2y09L8tkkH25e\nJ7YZh8a5Faixr6Wla7sLT5IkSdK6para3UFyeFXdnGQn8LfAS4DvB75aVa9eY91qO76uJWGYWI18\nBxv5DCbZ/vj2jccgSZIkaX2SUFVZ7/qt39JZVTc3k7ua/V3fzK87aEmSJEnS2lpP+JLsSHIFsA9Y\nrKqrmqbnJ7kyyZlJjmw7DkmSJEnabjajh29/VX0XcAzwuCSPB84Ajq2qvQwTwbG3dkqSJEmSDt4h\nm7WjqroxybuAR1fV+5c1vR64YNR6CwsLt0/Pz88zPz/fVogzalfzHN7qdu/ew75912xeOOswGMyN\nLfwy68ew1eOXJEnS1rG4uMji4uLUttdq0ZYk9wO+VlU3JDkMuBB4BfDxqtrXvOdFwHFV9ZxV1rdo\ny5pFVcYXVJmFoi1tF6Zp21aPX5IkSVvXRou2tN3D903AWRn+xbwDOLuq3pvkTUn2AvuBa4DntRyH\nJEmSJG07rQ/LsBH28IE9fN3b6vFLkiRp65r5YRkkSZIkSd0w4ZMkSZKknjLhkyRJkqSeMuGTJEmS\npJ4y4ZMkSZKknjLhkyRJkqSeMuGTJEmSpJ4y4ZMkSZKknjLh0wR2kWTkazCYa3Xvg8Fcp/uXJEmS\ntqpUVdcxjJSkZjm+aUgCjDvGtdvHfUYb3/7mxNDm+hvV9f4lSZK0fSWhqrLe9e3hkyRJkqSeMuGT\nJEmSpJ4y4ZMkSZKknjLhkyRJkqSeOqTrAKQDVUAlSZIkTZcJn2bAraxdSVSSJEnSwfKWTkmSJEnq\nKRM+SZIkSeopEz5JkiRJ6ikTPkmSJEnqKRM+SZIkSeopq3RueQ5pIEmSJGl1rfbwJdmV5ENJrkjy\n8SSvbJYfleSiJJ9IcmGSI9uMo98ODGkw6iVJkiRpu2o14auqW4EnVNV3AY8EnpjkeOBU4OKqehhw\nCfCyNuOQJEmSpO2o9Wf4qurmZnJXs7/rgZOBs5rlZwFPbzsOSZIkSdpuWk/4kuxIcgWwD1isqquA\n3VW1BFBV+4Cj245DkiRJkrab1ou2VNV+4LuS3Au4MMk8d324zIfNJEmSJGnKNq1KZ1XdmOSvgEcD\nS0l2V9VSkgHwhVHrLSws3D49Pz/P/Px826FKkiRJUicWFxdZXFyc2vZS1V7nWpL7AV+rqhuSHAZc\nCLwCeBLwlao6PclLgaOq6tRV1q8245sFwyEVxh1j1+2TbWPc97QZx9jyedzp/iVJkrR9JaGq1j0O\nW9s9fN8EnJXhX8w7gLOr6r3NM33nJXkucC3wzJbjkCRJkqRtp9Uevo2yhw/s4dv4/jfKHj5JkiR1\nZaM9fK1X6ZQkSZIkdcOET5IkSZJ6yoRPkiRJknrKhE+SJEmSesqET5IkSZJ6yoRPkiRJknrKhE+S\nJEmSesqET5IkSZJ6yoRPmnGDwRxJRr4Gg7muQ5QkSdKMSlV1HcNISWqW45uGJMC4Y+y6fbJtjPue\nNuMY2zxPJom/z/uXJElSd5JQVVnv+vbwSZIkSVJPmfBJkiRJUk+Z8EmSJElST5nwSZIkSVJPmfBp\nCnaNrSLZ9f6tYilJkqTtyiqdHetLlc5Zb9/IedR1lcyu9y9JkqTuWKVTkiRJkrQqEz5JkiRJ6qmJ\nEr4k39F2IJIkSZKk6Zq0h++MJJcl+fkkR7YakSRJkiRpKiZK+KrqscCPAQ8CLk9yTpITWo1MkiRJ\nkrQhB1WlM8lO4OnAHwA3MiyP+PKqemsrwVmlk+4rXM5CDBttvztw68jW3bv3sG/fNaO3bpVOSZIk\ndWRTqnQmeWSS1wBXA08EnlZV39pMv2bMesckuSTJx5N8NMkvNstPS/LZJB9uXieu9wCktd3KMGFa\n/bW0dG2HsUmSJEntmaiHL8n7gTOB86vqlhVtP1FVZ49YbwAMqurKJPcALgdOBn4U+GpVvXqN/drD\n13nv2CzE0O04fV33sHW9f0mSJHVnoz18h0z4vqcCt1TVN5qd7gDuXlU3j0r2AKpqH7Cvmb4pydXA\nAw/Evt6gJUmSJElrm7RK58XAYcvmD2+WTSzJHLAX+FCz6PlJrkxyppU/JUmSJGn6Jk347l5VNx2Y\naaYPn3Qnze2c5wMvbNY9Azi2qvYy7AEce2unJEmSJOngTXpL538keVRVfRggyXcDt6yxDs17D2GY\n7J1dVe8AqKovLnvL64ELRq2/sLBw+/T8/Dzz8/MThjwbBoM5i4L03q7mObvVrVUFVJIkSTpgcXGR\nxcXFqW1v0qItxwHnAp9n+OzdAPjRqrp8gnXfBHypql68bNmgeb6PJC8Cjquq56yy7pYv2jL7RVks\n2jKNoi1tFlWxaIskSdL2tdGiLROPw5fkbsDDmtlPVNXXJljneOBS4KPcUQf/5cBzGD7Ptx+4Bnhe\nVS2tsr4JX+ftsxCDCZ8JnyRJ0va0mQnf9wJzLLsNtKretN4dT7hPE77O22chBhM+Ez5JkqTtaVOG\nZUhyNvCfgCuBbzSLC2g14ZMkSZIkrd+kRVseDTxiy3e3SZIkSdI2MumwDB9jWKhFKwwGcyQZ+ZIk\nSZKkrkzaw3c/4KoklwG3HlhYVSe1EtUWMhxyYa3nuyRJkiRp802a8C20GYQkSZIkafomSviq6v1J\n9gAPraqLkxwO7Gw3NEmSJEnSRkz0DF+SnwPOB/64WfRA4O1tBSVJkiRJ2rhJi7b8AnA8cCNAVX0K\nOLqtoCRJkiRJGzdpwndrVd12YCbJIaw9WrckSZIkqUOTJnzvT/Jy4LAkJwBvAS5oLyxJkiRJ0kZl\nkrHUk+wAfgZ4EsNxBi4Ezmx7IPYkMz/W+3CsvbWGZdjK7bMQQ/vt486zaXzHGzmPJ9n/rP+cSJIk\naX2SUFXrHuttooSvKyZ8s9A+CzGY8JnwSZIkbU8bTfgmGpYhyadZ5S/Oqjp2vTuWJEmSJLVr0oHX\nH71s+u7AM4D7TD8cSZIkSdK0rPuWziSXV9V3Tzmelfvwls7O22chBm/p9JZOSZKk7Wmzbul81LLZ\nHQx7/CbtHZQkSZIkdWDSpO33lk1/HbgGeObUo5EkSZIkTc1ECV9VPaHtQCRJkiRJ0zXpLZ0vHtde\nVa+eTjiSJEmSpGk5mCqdxwHvbOafBlwGfKqNoCRJkiRJGzdRlc4klwJPraqvNvP3BN5VVY9rNTir\ndM5A+yzEYJVOq3RKkiRtTxut0rljwvftBm5bNn9bs6z3BoM5kox8qQ92tfwdj9/+YDA309tf62dg\n4/FLkiSpLZP28P0qw6qcb2sWPR04r6peucZ6xwBvYpgc7gdeX1V/kOQo4M+BPTQVP6vqhlXW77yH\nr/89ePbwzUK7PYySJElazUZ7+CYeeL0Zi++xzeylVXXFBOsMgEFVXZnkHsDlwMnAKcCXq+q3k7wU\nOKqqTl1lfRO+zttnIYb+t5vwSZIkaTWbdUsnwOHAjVX1+8Bnk3zzWitU1b6qurKZvgm4GjiGYdJ3\nVvO2sxj2GEqSJEmSpmiihC/JacBLgZc1i+4G/OnB7CjJHLAX+CCwu6qWYJgUAkcfzLYkSZIkSWub\ntIfvh4CTgP8AqKrPA/ecdCfN7ZznAy9sevpW3v/l/WCSJEmSNGWTjsN3W1VVkgJIcsSkO0hyCMNk\n7+yqekezeCnJ7qpaap7z+8Ko9RcWFm6fnp+fZ35+ftJdS5IkSdKWsri4yOLi4tS2N2mVzl8CHgqc\nALwKeC5wTlX94QTrvgn4UlW9eNmy04GvVNXpFm2Z9fZZiKH/7RZtkSRJ0mo2s0rnCcCTGP51eWFV\nvWeCdY4HLgU+yvAvxgJeDlwGnAc8CLiW4bAM/77K+iZ8nbfPQgz9bzfhkyRJ0mpaT/iS7AQurqon\nrHcn62XCNwvtsxBD/9tN+CRJkrSa1odlqKpvAPuTHLnenUiSJEmSNt+kRVtuAj6a5D00lToBquoF\nrUQlSZIkSdqwSRO+tzYvST0zGMyxtHRt12G0aq1j3L17D/v2XbN5AUmSJG2Ssc/wJXlwVX1mE+NZ\nuX+f4eu8fRZi6H97l8/wdf2M4GbwOURJkrRVtf0M39uX7egv1rsTSZIkSdLmWyvhW55JHttmIJIk\nSZKk6Vor4asR05IkSZKkGbdW0ZbvTHIjw56+w5ppmvmqqnu1Gp0kSZIkad3GJnxVtXOzApHUll1N\n0RJJkiRtN5MOyyBpy7qVtatwSpIkqY/WeoZPkiRJkrRFmfBJkiRJUk+Z8EmSJElST5nwSZIkSVJP\nmfBJnRtW0Rz16oPBYG7sMQ4Gc12HKEmS1Eupmt3x1JNU1/EN/+Beq8Jhn9tnIQbbZ719rZ/TSX6O\n2vxZ73r/kiRJ65WEqlp3L4A9fJIkSZLUUyZ8kiRJktRTJnySJEmS1FMmfJIkSZLUUyZ8kiRJktRT\nJnySJEmS1FOtJnxJ3pBkKclHli07Lclnk3y4eZ3YZgySJEmStF213cP3RuAHVln+6qp6VPN6d8sx\nSJIkSdK21GrCV1UfAK5fpWndAwdKkiRJkibT1TN8z09yZZIzkxzZUQySJEmS1GtdJHxnAMdW1V5g\nH/DqDmKQJEmSpN47ZLN3WFVfXDb7euCCce9fWFi4fXp+fp75+flW4pIkSZKkri0uLrK4uDi17aWq\npraxVXeQzAEXVNV3NPODqtrXTL8IOK6qnjNi3Wo7vrUkAcbF0Pf2WYjB9llvX+vndJKfozZ/1rve\nvyRJ0noloarWXQOl1R6+JOcA88B9k3wGOA14QpK9wH7gGuB5bcYgSZIkSdtV6z18G2EP3yy0z0IM\nts96uz18kiRJ7dhoD19XVTolSZIkSS0z4ZMkSZKkntr0Kp2S+mZXc8ukJEmSZo0Jn6QNupXJngOV\nJEnSZvOWTkmSJEnqKRM+SZIkSeopEz5JkiRJ6ikTPkmSJEnqqW2f8A0GcyQZ+ZLUvbV+TnfuPMKf\nY0mSpFWkaq3qet1JUm3HN/xjcNw+tnv7LMRg+9Zun2wb437WN+PndJavhZIkaftKQlWt+3+wt30P\nnyRJkiT1lQmfJEmSJPWUCZ8kSZIk9ZQJnyRJkiT1lAmfJEmSJPWUCZ8kSZIk9ZQJnyRJkiT1lAmf\nJEmSJPWUCZ8kSZIk9ZQJnyRJkiT1lAmfJEmSJPVU7xO+wWCOJCNfkjTr1rqO7dx5xNj2wWCu60OQ\nJEkdSVW1t/HkDcAPAktV9chm2VHAnwN7gGuAZ1bVDSPWr43GN0zqxm3D9vHtsxCD7Vu7fbJtjPtZ\n34yf4zavhRs1jeOf5eOTJEmjJaGq1t1T1XYP3xuBH1ix7FTg4qp6GHAJ8LKWY5AkSZKkbanVhK+q\nPgBcv2LxycBZzfRZwNPbjEGSJEmStqsunuE7uqqWAKpqH3B0BzFIkiRJUu8d0nUArP3wz0hvecvb\n+cM/fMPIdmuySJIkSdrOukj4lpLsrqqlJAPgC+PevLCwcPv0/Pw88/Pzt8+fd947+Ju/ORb4/lXX\nPeSQ/zOFcCX1366xVXt3797Dvn3XbF44kiRp21pcXGRxcXFq22u1SidAkjnggqr6jmb+dOArVXV6\nkpcCR1XVqSPWHVul8xnPOIXzz38ccMqq7Yce+kJuu+0PmO0KhrPePgsx2L612yfbRtdVOme5yqVV\nOiVJ2r5mukpnknOAvwO+JclnkpwC/BZwQpJPAN/XzEuSJEmSpqzVWzqr6jkjmla/B1OSJEmSNDVd\nVOmUJEmSJG0CEz5JkiRJ6ikTPkmSJEnqKRM+SZIkSeopEz5JkiRJ6ikTPkmSJEnqKRM+SZIkSeop\nEz5JkiRJ6ikTPkla0y6SjHzt3HnE2PbBYG7s1geDubHrb3VrHd80PkNJkrS6Q7oOQJJm361AjWzd\n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\n", 310 | "text/plain": [ 311 | "" 312 | ] 313 | }, 314 | "metadata": {}, 315 | "output_type": "display_data" 316 | } 317 | ], 318 | "source": [ 319 | "filtered_likes = [item for item in mean_likes.values() if 0 < item < 300]\n", 320 | "plt.figure(figsize=(15, 5))\n", 321 | "plt.hist(filtered_likes, bins=100)\n", 322 | "plt.title(\"Mean likes of %s followers\" % username)\n", 323 | "plt.xlabel(\"Mean likes\")\n", 324 | "plt.ylabel(\"Frequency\")\n", 325 | "plt.show()" 326 | ] 327 | }, 328 | { 329 | "cell_type": "markdown", 330 | "metadata": {}, 331 | "source": [ 332 | "Let's take a look at the greatest mean likes owner" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 13, 338 | "metadata": { 339 | "collapsed": false 340 | }, 341 | "outputs": [ 342 | { 343 | "name": "stdout", 344 | "output_type": "stream", 345 | "text": [ 346 | "lord.ach has the highest value of mean likes in ohld followers.\n" 347 | ] 348 | } 349 | ], 350 | "source": [ 351 | "print (\"%s has the highest value of mean likes in %s followers.\" % (max(mean_likes, key=mean_likes.get), username))" 352 | ] 353 | } 354 | ], 355 | "metadata": { 356 | "kernelspec": { 357 | "display_name": "Python 3", 358 | "language": "python", 359 | "name": "python3" 360 | }, 361 | "language_info": { 362 | "codemirror_mode": { 363 | "name": "ipython", 364 | "version": 3 365 | }, 366 | "file_extension": ".py", 367 | "mimetype": "text/x-python", 368 | "name": "python", 369 | "nbconvert_exporter": "python", 370 | "pygments_lexer": "ipython3", 371 | "version": "3.5.1" 372 | }, 373 | "widgets": { 374 | "state": { 375 | "f923e33a107041b3ac6800be46aea286": { 376 | "views": [ 377 | { 378 | "cell_index": 18 379 | } 380 | ] 381 | } 382 | }, 383 | "version": "1.2.0" 384 | } 385 | }, 386 | "nbformat": 4, 387 | "nbformat_minor": 0 388 | } 389 | --------------------------------------------------------------------------------