def like_media(self, media_id, media_code, username): """ Function is used to like user post using instagram public api (AJAX requests) :param media_id: id of the post :param media_code: code of the post :param username: username (of the followings) :return: """ url_like = 'https://www.instagram.com/web/likes/{}/like/'.format( media_id) try: post_like = self.session.post(url_like) if post_like.status_code == 200: insta_logger.info('User {} post code #{} was liked'.format( username, media_code)) return 1 else: insta_logger.error('Failed to like user {} post code #{},' 'status code: {}'.format( username, media_code, post_like.status_code)) except Exception as e: insta_logger.error( 'Exception raised while liking user {} post code #{}' 'Exception: {}'.format(username, media_code, e))
def load_ignore_list(): """ Get names of users from file to be excluded from liking list :return: list of users to be ignored, empty list if file structure is modified or not found """ dir_name = os.path.dirname(os.path.abspath(__file__)) if os.path.exists(os.path.join(dir_name, 'ignore_list.txt')): with open('ignore_list.txt', 'r') as ignore_list_file: full_text = ignore_list_file.read() if full_text.find('Ignore list:') != -1: start_index = full_text.index('Ignore list:') + len( 'Ignore list:') list_raw = full_text[start_index:].split(',') insta_logger.info('Ignore list extracted') print('Ignore list extracted') return [account.strip() for account in list_raw] print('"Ignore list.txt" was edited incorrectly. ' 'Can\'t create ignore list.' ' Please see description.') insta_logger.error('Ignore list file incorrectly edited') return [] print('No ignore list found') insta_logger.error('No ignore list found') return []
def login(self, attempts=3): """ Login to instagram account with 'requests' :param attempts: number of attempts to login, default is 3, shutting down if failed to login :return: 1 if login successful """ print('-Trying to login to account to use public api') for attempt in range(attempts): try: print('-Login attempt {} of {}'.format(attempt + 1, attempts)) self.session.get(self.login_url) csrf_token = self.session.cookies['csrftoken'] self.session.headers.update({'X-CSRFToken': csrf_token}) time.sleep(3) login_to_acc = self.session.post(self.login_url, data={ 'username': self.user_login, 'password': self.user_password }, allow_redirects=True) time.sleep(3) if login_to_acc.status_code == 200: main_page = self.session.get(self.main_url) if main_page.text.find(self.user_login) != -1: print('--Successful login') insta_logger.info('Requests: Successful login') self.session.headers.update( {'X-CSRFToken': login_to_acc.cookies['csrftoken']}) self.logged_in = True return 1 print('--Login failed, wrong credentials') insta_logger.error('Requests: Login failed (status code: ' '{}), credentials: login: {} password:'******' {}, attempt: {}'.format( login_to_acc.status_code, self.user_login, self.user_password, attempt)) else: print('--Login failed, status code: {}'.format( login_to_acc.status_code)) insta_logger.error('Requests: login failed (status code: ' '{}), credentials: login: {} password: '******'{}, attempt: {}'.format( login_to_acc.status_code, self.user_login, self.user_password, attempt)) except Exception as e: insta_logger.error( 'Requests: login error, Exception: {}, attempt {}'.format( e, attempt + 1)) print('--Failed. Exception raised.') insta_logger.critical('Requests: unable to login.' 'Shutting down') insta_logger.info('--------------STOP---------------') print('Unable to login. Refer to log file. ' 'Shutting down') sys.exit()
def login(self, timeout=10, attempts=3): """ Login to account using Selenium and PhantomJS headless browser :param timeout: Number of seconds before timing out :param attempts: number of attempts to login, default is 3, shutting down if failed to login :return: """ for attempt in range(attempts): print('-Login with Selenium, attempt {} of {}'.format( attempt + 1, attempts)) try: self.driver.get(self.login_page) WebDriverWait( self.driver, timeout=timeout).until( EC.presence_of_element_located(( By.XPATH, '/html/body/span/section/main/div/article/div/' 'div[1]/div/form/div[1]/input'))) login = self.driver.find_element( By.XPATH, '/html/body/span/section/main/div/article/' 'div/div[1]/div/form/div[1]/input') login.send_keys(self.user_login) password = self.driver.find_element( By.XPATH, '/html/body/span/section/main/div/article/div/' 'div[1]/div/form/div[2]/input') password.send_keys(self.user_password) btn = self.driver.find_element( By.XPATH, '/html/body/span/section/main/div/article' '/div/div[1]/div/form/span/button') btn.click() time.sleep(5) if self.driver.current_url == self.main_url: print('--Login successful') insta_logger.info('Selenium: login successful') return 1 print('--Unable to login with given credentials, attempt {}' ''.format(attempt + 1)) insta_logger.error('Selenium: login fail, wrong credentials,' ' attempt #{}'.format(attempt + 1)) except Exception as e: insta_logger.error( 'Selenium: login error, Exception: {}, attempt {}'.format( e, attempt + 1)) print('--Failed. Exception raised.') insta_logger.critical('Selenium: unable to login.' 'Shutting down') insta_logger.info('--------------STOP---------------') print('Unable to login. Refer to log file. ' 'Shutting down') sys.exit()
def logout(self): """ Logout from account with 'requests' :return: """ try: logout_page = self.session.get(self.logout_url) time.sleep(3) if logout_page.status_code == 200: insta_logger.info('Successful logout') else: insta_logger.error('Failed logout, status code: {}'.format( logout_page.status_code)) except Exception as e: insta_logger.error('Failed logout, exception: {}'.format(e))
def write_likes(total_likes): """ Write number of likes made by bot and current time stamp to json file: {"total_likes": "total likes", "timestamp": "timestamp"} :param total_likes: number of likes :return: """ dir_name = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(dir_name, 'likes_count.json'), 'w') as likes_file: try: likes = json.load(likes_file) except io.UnsupportedOperation: likes = {'total_likes': total_likes, 'timestamp': time()} likes['total_likes'] = total_likes likes['timestamp'] = time() json.dump(likes, likes_file) insta_logger.info('Likes made by bot is writen to file, ' 'total posts liked: {}'.format(total_likes))
def check_likes_limit(self, write_timestamp=True): """ Checks if likes made by bot not exceeds limit (1000 likes) If likes made today equal limit - exit program. :return: """ if not self.ignore_limit: if self.total_likes >= 1000: insta_logger.critical('Bot liked 1000 posts in recent ' '24 hours,' 'stopping bot to avoid ban') if write_timestamp: write_likes(self.total_likes) insta_logger.info('--------------STOP---------------') print('Bot liked 1000 posts in recent 24 hours, ' 'stopping bot to avoid ban') if self.public_api.logged_in: self.public_api.logout() sys.exit()
def liking_all_posts(self): """ Liking followings posts using 'InstApi' 'like_media' method :return: """ total_posts = self.calc_num_of_posts(self.posts_to_like) if total_posts == 0: insta_logger.critical('No posts to like, shutting down') insta_logger.info('--------------STOP---------------') print('No posts to like. Shutting down.') self.public_api.logout() sys.exit() print('-Liking followings posts') print('--Total posts to like: {}'.format(total_posts)) start_time = current_time() completion = 0 liked = 0 for user, posts in self.posts_to_like.items(): for post in posts: self.check_likes_limit() like_post = self.public_api.like_media(media_id=post[1], media_code=post[0], username=user) if like_post: liked += 1 self.total_likes += 1 else: self.like_errors += 1 self.like_error_posts.setdefault(user, set()).add(post) if self.like_errors == self.like_error_limit: self.like_errors = 0 print('\n') print('\n oops, something is wrong,' 'login again after 5 minutes\n' 'please wait') self.public_api.logout() time.sleep(60 * 5) insta_logger.info('Relogin attempt') self.public_api.login() print('Resume liking') completion += 1 progress_bar(completion=completion, total=total_posts, start_time=start_time) time.sleep(1 * random.random()) session_failed = self.calc_num_of_posts(self.like_error_posts) print('--Successfully liked in session: {}'.format(liked)) insta_logger.info('Successfully liked in session: {}'.format(liked)) print('--Failed to like in session: {}'.format(session_failed)) insta_logger.info('Failed to like in ' 'session: {}'.format(session_failed)) write_likes(self.total_likes)
def excluding_liked_posts(self): """ Exclude data from the set of posts that have already been liked using 'InstApi' 'check_like' method :return: """ print('-Excluding posts that have already been liked') start_time = current_time() total_posts = self.calc_num_of_posts(self.posts_to_like) completion = 0 for user, posts in self.posts_to_like.items(): liked_posts = set() for post in posts: liked = self.public_api.check_like(media_code=post[0], username=user) if liked: liked_posts.add(post) completion += 1 progress_bar(completion=completion, total=total_posts, start_time=start_time) insta_logger.info('User {} total liked posts excluded: {}'.format( user, len(liked_posts))) posts.difference_update(liked_posts)
def populate_post_list(self): """ Populate posts to like from list of followings using 'InstApi' 'get_recent_media_feed' method :return: """ print('-Extracting users media') start_time = current_time() total_links = len(self.followings) completion = 0 for user_url in self.followings: media_data = self.public_api.get_recent_media_feed(user_url) if media_data: self.posts_to_like.update(media_data) completion += 1 progress_bar(completion=completion, total=total_links, start_time=start_time) if not self.posts_to_like: insta_logger.critical('No posts media extracted, shutting down') insta_logger.info('--------------STOP---------------') print('No posts to like. Shutting down.') self.public_api.logout() sys.exit()
def reliking_failed_posts(self): """ Liking posts, that 'bot' failed to like during first session :return: """ total_posts_to_relike = self.calc_num_of_posts(self.like_error_posts) if total_posts_to_relike != 0: print('-Logout and sleep for 2 minutes before "reliking" session') insta_logger.info('Logout and sleep for 2 minutes before' ' "reliking" session') self.public_api.logout() time.sleep(2 * 60) print('-Trying to relogin to relike "failed posts"') insta_logger.info('Relogin to like "failed posts"') self.public_api.login() start_time = current_time() completion = 0 liked = 0 failed_to_like = 0 for user, posts in self.like_error_posts.items(): for post in posts: self.check_likes_limit() like_post = self.public_api.like_media(media_id=post[1], media_code=post[0], username=user) if like_post: liked += 1 self.total_likes += 1 else: failed_to_like += 1 completion += 1 progress_bar(completion=completion, total=total_posts_to_relike, start_time=start_time) time.sleep(1 * random.random()) print('--Successfully liked in relike session: {}'.format(liked)) insta_logger.info( 'Successfully liked in relike session: {}'.format(liked)) print('--Failed to like in relike session: {}'.format( failed_to_like)) insta_logger.info( 'Failed to like in relike session: {}'.format(failed_to_like)) write_likes(self.total_likes)
def get_recent_media_feed(self, url): """ Function gather data about user recent posts: posts ids and posts codes To get json response with user media data query param '?__a=1' must be added to end of url. Response return max value of 12 posts, to get more posts query param &max_id='page_num' must be added. :param url: instagram user url :return: dictionary with user as key and set of tuples with posts media code and media id as value """ main_url = urljoin(url, '?__a=1') step = 0 end_cursor = None username = urlparse(url)[2].strip('/') media_data = {username: set()} try: while step != self.pagination + 1: if end_cursor: query = '&max_id={}'.format(end_cursor) feed_url = '{}{}'.format(main_url, query) else: feed_url = main_url feed_page_raw = self.session.get(feed_url) json_data = feed_page_raw.json() if username in self.ignore_list: insta_logger.info( 'User {} is in ignore list'.format(username)) return try: feed_data = json_data['user']['media']['nodes'] if feed_data: for media in feed_data: media_data[username].add( (media['code'], media['id'])) if len(media_data[username] ) >= self.posts_to_check: insta_logger.info( 'User {} media data extracted,' ' total media {}'.format( username, len(media_data[username]))) return media_data step += 1 if json_data['user']['media']['page_info'][ 'has_next_page']: end_cursor = json_data['user']['media'][ 'page_info']['end_cursor'] time.sleep(1 * random.random()) else: break else: insta_logger.info( 'User {} has no media data, ignored'.format( username)) return except KeyError: insta_logger.error( 'Key error while getting media feed, user {}'.format( username)) return insta_logger.info( 'User {} media data extracted, total media {}'.format( username, len(media_data[username]))) return media_data except Exception as e: insta_logger.error('Exception raised while getting feed data,' 'Exeption: {}'.format(e))
def crawl_folowing_links(self, timeout=15, attempts=3): """ Crawl links from web page using Selenium. Paginate hidden links with scroll down script. :param timeout: Number of seconds before timing out :param attempts: number of attempts to get followings links, default is 3, shutting down if failed to get links :return: 1 if links extracted from web page successfully """ for attempt in range(attempts): print('-Trying to get followings, attempt {} of {}'.format( attempt + 1, attempts)) try: self.driver.get(self.user_page) time.sleep(3) WebDriverWait( self.driver, timeout=timeout).until( EC.presence_of_element_located(( By.CLASS_NAME, '_s53mj'))) total_following_web_elem = self.driver.find_element( By.CSS_SELECTOR, 'a[href*="following"] > span') total_following = int(total_following_web_elem.text) folowing_button = self.driver.find_element(By.CSS_SELECTOR, 'a[href*=' '"following"]') print('--Total following to extract: {}'.format( total_following)) folowing_button.click() time.sleep(3) current_total = len( self.driver.find_elements(By.CLASS_NAME, '_cx1ua')) while current_total != total_following: self.driver.execute_script('window.scrollTo(0, document' '.body.scrollHeight);') time.sleep(2) current_total = len( self.driver.find_elements(By.CLASS_NAME, '_cx1ua')) sys.stdout.write('\r--Total followings' ' extracted: {}'.format(current_total)) sys.stdout.flush() sys.stdout.write('\n') links_web_elem = self.driver.find_elements(By.CLASS_NAME, '_cx1ua') links = [link.find_element_by_tag_name( 'a').get_attribute('href') for link in links_web_elem] if len(links) == total_following: insta_logger.info( 'Selenium: Followings links extracted successfully') print('--Followings extracted successfully') self.followings_list = links return 1 insta_logger.info( 'Selenium: failed to extract ' 'Followings links, attempt #{}'.format(attempt + 1)) print('--Failed') except Exception as e: insta_logger.error('Selenium: crawl_following exception ' 'raised. Exception: {}, attempt #{}'.format( e, attempt + 1)) print('--Failed') insta_logger.critical( 'Selenium: unable to get followings. Shutting down') insta_logger.info( '--------------STOP---------------') print( '\nUnable to get followings. Refer to log file. Shutting' ' down') sys.exit()
def main(): bot = None try: # Parsing args from command-line args = parse_credentials() login = args.login password = args.password posts_to_check = args.number_of_posts ignore_limit = args.ignore_limit insta_logger.info('--------------START---------------') draw_line_separator() draw_logo() draw_line_separator() print('instabot is working:') draw_worker_text('populating ignore list') ignore_list = load_ignore_list() bot = LikingBot(user_login=login, user_password=password, ignore_list=ignore_list, followings=None, posts_to_check=posts_to_check, ignore_limit=ignore_limit) # Check if likes limit not exceeds bot.check_likes_limit(write_timestamp=False) draw_line_separator() draw_worker_text('getting list of followings') print('#This could take a while, be patient#') # Selenium part: # using PhantomJS headless web browser to login and # gather links of the accounts that user follows web_driver = CrawlFollowing(user_login=login, user_password=password) web_driver.login() web_driver.crawl_folowing_links() followings_list = web_driver.followings_list web_driver.close() # Public api part using requests library: bot.followings = followings_list draw_line_separator() draw_worker_text('Liking posts') # Login with 'requests' to use instagram public api bot.public_api.login() bot.populate_post_list() # Populating users media data # Excluding posts that have already been liked bot.excluding_liked_posts() bot.liking_all_posts() # Liking all remaining posts if bot.calc_num_of_posts(bot.like_error_posts) != 0: bot.reliking_failed_posts() draw_worker_text('Work is done, bot is tired and shutting down.') draw_worker_text('For more info refer to today log file') insta_logger.info('End of the program. Shutting down') insta_logger.info('--------------STOP---------------') bot.public_api.logout() sys.exit() except KeyboardInterrupt: print('\nBot was interrupted by user. Shutting down') insta_logger.info('Program is interrupted by user. Shutting down') if bot and bot.public_api.logged_in: bot.public_api.logout() insta_logger.info('--------------STOP---------------') sys.exit()