├── LICENSE ├── README.md └── instagram_downloader.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fernando 4 | https://github.com/fernandod1/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Instagram downloader 2 | Instagram user's photos and videos downloader. Download all media files from any public username. Working 2021. 3 | 4 | LIMITS: 5 | 6 | Script does not requires a token or username/pass to use Instagram API, this causes some daily limits: 7 | 8 | - Script has been tested in single execution and downloads around 2200 images/videos. 9 | - Instagram API limits daily queries, so if script reachs limit, I recommend you to execute script again in 12 hours so that daily limit is expired. 10 | - I have added a resume mode so that you can execute again script and it will continue from last image downladed. 11 | 12 | Requirement: Python v3 13 | 14 | CONFIGURE: 15 | 16 | Set in line 14 public instagram username you want to download photos from (Example: ladygaga): 17 | INSTAGRAM_USERNAME = "SET_INSTAGRAM_USERNAME" 18 | 19 | USAGE COMMAND: 20 | 21 | python instagram_downloader.py 22 | 23 | DISCLAIMER: 24 | 25 | "Instagram-downloader" repository is in no way affiliated with, authorized, maintained or endorsed by Instagram or any of its affiliates or subsidiaries. This is an independent and unofficial project. Use at your own risk and respect copyrights of media files. 26 | 27 | COLLABORATIONS: 28 | 29 | Collaborations to improve this script are always webcome. 30 | 31 | -------------------------------------------------------------------------------- /instagram_downloader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2021 Fernando 4 | # Url: https://github.com/fernandod1/ 5 | # License: MIT 6 | 7 | import sys 8 | import requests 9 | import time 10 | import urllib.request 11 | import os 12 | import json 13 | 14 | INSTAGRAM_USERNAME = "ladygaga" 15 | 16 | # ------------------------------Do not modify under this line--------------------------------------- # 17 | 18 | class Instagram_Downloader: 19 | def __init__(self, username): 20 | self.username = username 21 | self.user_id = "" 22 | self.jsondata = "" 23 | self.apilabel = "graphql" 24 | self.hash_timeline = "" 25 | 26 | def create_download_directory(self): 27 | try: 28 | os.mkdir(self.username) 29 | print("Directory ", self.username, " created.") 30 | except FileExistsError: 31 | print("Directory ", self.username, " already exists.") 32 | 33 | def get_total_photos(self): 34 | return int((self.jsondata.json()[self.apilabel]["user"]["edge_owner_to_timeline_media"]["count"])) 35 | 36 | def set_user_id(self): 37 | self.user_id = int(self.jsondata.json()[self.apilabel]["user"]["id"]) 38 | 39 | def get_end_cursor_timeline_media(self): 40 | return self.jsondata.json()[self.apilabel]["user"]["edge_owner_to_timeline_media"]["page_info"]["end_cursor"] 41 | 42 | def write_resume_end_cursor_timeline_media(self): 43 | f = open("resume_"+self.username+".txt", "w") # create file with last page downladed to resume if needed 44 | f.write(str(self.get_end_cursor_timeline_media())) 45 | f.close() 46 | 47 | def read_resume_end_cursor_timeline_media(self): 48 | if os.path.isfile("resume_"+self.username+".txt"): 49 | with open("resume_"+self.username+".txt") as f: 50 | self.hash_timeline = f.readline().strip() 51 | print ("****************\nResume mode ON\n****************") 52 | return True 53 | else: 54 | print ("Not resume pending") 55 | return False 56 | 57 | def remove_resume_file(self): 58 | if os.path.exists("resume_"+self.username+".txt"): 59 | os.remove("resume_"+self.username+".txt") 60 | 61 | def set_apilabel(self,label): 62 | self.apilabel = label 63 | 64 | def has_next_page(self): 65 | return self.jsondata.json()[self.apilabel]["user"]["edge_owner_to_timeline_media"]["page_info"]["has_next_page"] 66 | 67 | def get_jsondata_instagram(self): 68 | headers = { 69 | "Host": "www.instagram.com", 70 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11" 71 | } 72 | if self.apilabel=="graphql": 73 | self.jsondata = requests.get("https://www.instagram.com/" + self.username + "/feed/?__a=1", headers=headers) 74 | self.hash_timeline = self.get_end_cursor_timeline_media() 75 | self.set_user_id() 76 | else: 77 | self.jsondata = requests.get("https://www.instagram.com/graphql/query/?query_id=17888483320059182&variables=%7B%22id%22%3A%22" + str(self.user_id) + "%22%2C%22first%22%3A12%2C%22after%22%3A%22" + str(self.hash_timeline) + "%22%7D", headers=headers) 78 | if user.has_next_page(): 79 | self.hash_timeline = self.get_end_cursor_timeline_media() 80 | if (self.jsondata.status_code==200): 81 | print("----------------\nJson url loaded:\n----------------\nhttps://www.instagram.com/graphql/query/?query_id=17888483320059182&variables=%7B%22id%22%3A%22" + str(self.user_id) + "%22%2C%22first%22%3A12%2C%22after%22%3A%22" + str(self.hash_timeline) + "%22%7D") 82 | else: 83 | print("ERROR: Incorrect json data url recieved.") 84 | return self.jsondata 85 | 86 | def download_photos(self): 87 | i = 0 88 | for match in self.jsondata.json()[self.apilabel]["user"]["edge_owner_to_timeline_media"]["edges"]: 89 | time.sleep(0.5) 90 | print("Downloading IMAGE:\n" +match["node"]["display_url"]) 91 | filename = self.username + "/" + self.jsondata.json()[self.apilabel]["user"]["edge_owner_to_timeline_media"]["edges"][i]["node"]["shortcode"] + ".jpg" 92 | try: 93 | if not os.path.exists(filename ): 94 | urllib.request.urlretrieve(match["node"]["display_url"], filename) 95 | else: 96 | print("Notice: "+ filename+ " image already downloaded, skipped.") 97 | except urllib.error.HTTPError as e: 98 | print(str(e.code) + " Can't download image") 99 | i = i + 1 100 | 101 | def download_videos(self): 102 | i = 0 103 | if 'edge_felix_video_timeline' in self.jsondata.json()[self.apilabel]["user"]: 104 | for match in self.jsondata.json()[self.apilabel]["user"]["edge_felix_video_timeline"]["edges"]: 105 | time.sleep(0.5) 106 | print("Downloading VIDEO:\n" +match["node"]["video_url"]) 107 | filename = self.username + "/" + self.jsondata.json()[self.apilabel]["user"]["edge_felix_video_timeline"]["edges"][i]["node"]["shortcode"] + ".mp4" 108 | try: 109 | if not os.path.exists(filename ): 110 | urllib.request.urlretrieve(match["node"]["video_url"], filename) 111 | else: 112 | print("Notice: "+ filename+ " video already downloaded, skipped.") 113 | except urllib.error.HTTPError as e: 114 | print(str(e.code) + " Can't download video") 115 | i = i + 1 116 | 117 | 118 | # --------------------------------- Main program -------------------------------------# 119 | 120 | try: 121 | user = Instagram_Downloader(INSTAGRAM_USERNAME) 122 | user.create_download_directory() 123 | user.get_jsondata_instagram() 124 | user.download_photos() 125 | user.download_videos() 126 | user.set_apilabel("data") 127 | user.read_resume_end_cursor_timeline_media() 128 | while True: 129 | time.sleep(5) # pause to avoid ban 130 | user.get_jsondata_instagram() 131 | user.download_photos() 132 | user.download_videos() 133 | user.write_resume_end_cursor_timeline_media() 134 | if user.has_next_page() == False: 135 | user.remove_resume_file() 136 | print("Done. All images/videos downloaded for "+INSTAGRAM_USERNAME+" account.") 137 | break 138 | except: 139 | print("Notice: script premature finished due to daily limit of number of requests to Instagram API.") 140 | print("Just execute script AGAIN in few hours to continue RESUME of pending images/videos to download.") 141 | --------------------------------------------------------------------------------