Why Instaloader?
Needed to download some Instagram photos for a project. Looked around and found Instaloader - a Python tool that can download photos, videos, and stories from Instagram.
The nice thing is you don't necessarily need to log in. Works fine for public profiles. For private profiles you'll need credentials, but then you run into rate limiting pretty quickly.
Anyway, here are my notes from figuring out how to use it without getting banned immediately.
Installation
Pretty straightforward:
pip install instaloader
That's it. No browser binaries or anything complicated.
Basic Usage
Download from Public Profile (No Login)
For public accounts, you don't need to log in:
import instaloader
L = instaloader.Instaloader()
# Download all posts from a profile
L.download_profile(target_profile, profile_pic_only=False)
This creates a folder with the profile name and downloads everything there. Pretty simple.
Download Single Post
If you just want one specific post:
import instaloader
L = instaloader.Instaloader()
# Download from a post URL
post = instaloader.Post.from_shortcode(L.context, "SHORTCODE_FROM_URL")
L.download_post(post, target="post_folder")
The shortcode is the part at the end of the URL. Like if the URL is instagram.com/p/ABC123/, then ABC123 is the shortcode.
Download with Login
For private profiles or to avoid some restrictions:
import instaloader
L = instaloader.Instaloader()
# Login (optional but recommended to avoid some restrictions)
L.login(USER, PASSWORD)
# Then download as usual
L.download_profile(target_profile)
Warning:
Storing passwords in plain text is a bad idea. Use environment variables or a config file for credentials. I'm keeping it simple here for demonstration.
Common Errors & Fixes
429 Too Many Requests
This is the big one. Instagram will block you if you request too much too fast.
Symptoms:
- Downloads suddenly stop working
- Get HTTP 429 errors
- "Redirected to login page" messages
What helps:
1. Add Delays
Instaloader has a rate limit feature. Use it:
import instaloader
import time
L = instaloader.Instaloader()
# Add delay between requests (in seconds)
L.context.quiet = True # Less output
posts = instaloader.NodeIterator(
L.context,
"9dc1f5ecaa4f41b58931f8f1a53fa24a",
lambda d: d['data']['user']['edge_owner_to_timeline_media'],
lambda n: instaloader.Post(L.context, n),
{'id': target_id},
f"/graphql/query/query_hash/9dc1f5ecaa4f41b58931f8f1a53fa24a/variables/{{\"id\":\"{target_id}\",\"first\":50}}",
lambda d: d['data']['user']['edge_owner_to_timeline_media']['page_info']
)
for post in posts:
L.download_post(post, target=target_profile)
time.sleep(10) # Wait 10 seconds between downloads
10 seconds between downloads is safer. 5 seconds might work for a while but you'll get blocked eventually.
2. Use Proxies
This is what actually solved it for me. Rotate your IP:
import instaloader
# Load proxies from a file
def load_proxies(filename):
with open(filename) as f:
return [line.strip() for line in f if line.strip()]
proxies = load_proxies("proxies.txt")
proxy_index = 0
L = instaloader.Instaloader()
# Set proxy for each request
L.context._session.proxies = {
'http': f'http://{proxies[proxy_index]}',
'https': f'https://{proxies[proxy_index]}'
}
# Rotate proxy every N downloads
download_count = 0
for post in posts:
L.download_post(post, target=target_profile)
download_count += 1
if download_count % 10 == 0:
proxy_index = (proxy_index + 1) % len(proxies)
L.context._session.proxies = {
'http': f'http://{proxies[proxy_index]}',
'https': f'https://{proxies[proxy_index]}'
}
print(f"Switched to proxy {proxy_index + 1}")
time.sleep(5)
I used residential proxies from a service. Datacenter proxies got blocked pretty quickly. Your mileage may vary.
Proxy format:
The proxies.txt file should have one proxy per line in format: ip:port or username:password@ip:port
3. Session Files
Instaloader can save and load sessions to avoid logging in every time:
import instaloader
L = instaloader.Instaloader()
# Load session if it exists
try:
L.load_session_from_file(USERNAME, "session_file")
print("Loaded session from file")
except FileNotFoundError:
# No session file, login
L.login(USERNAME, PASSWORD)
L.save_session_to_file("session_file")
print("Logged in and saved session")
# Now you can download without logging in again
L.download_profile(target_profile)
This helps avoid some rate limiting since Instagram sees it as the same session continuing.
Other Issues I Hit
Login Required Error
Sometimes Instagram will just start demanding login even for public profiles.
Workaround:
- Log in with your account
- Use session files (see above)
- Switch to a different IP/proxy
- Wait a few hours and try again
"User Not Found"
Either the username is wrong, the account is private, or you've been temporarily blocked.
Try the same profile in a browser. If it loads there but not in Instaloader, you're probably blocked.
Two-Factor Authentication
If your account has 2FA, Instaloader will ask for the code when you log in:
import instaloader
L = instaloader.Instaloader()
# This will prompt for 2FA code if needed
L.interactive_login(USERNAME)
The session is saved after this so you only need to do it once.
What Works for Me
After dealing with all the errors, here's what I do:
- Use session files - Login once, reuse the session
- Add delays - 5-10 seconds minimum between downloads
- Rotate proxies - Especially if downloading a lot
- Don't push it - If you get 429, stop and wait
- Download in batches - Don't try to get everything at once
Also worth noting: Instagram's limits change. Something that works today might not work tomorrow. They've been getting stricter about scraping.
Some Advanced Stuff
Download Stories
Stories disappear after 24 hours so you need to download them more frequently:
import instaloader
L = instaloader.Instaloader()
L.login(USERNAME, PASSWORD) # Required for stories
# Download stories from a profile
for story in L.get_stories(userids=[L.check_profile_id(target_profile)]):
for item in story:
L.download_storyitem(item, target=target_profile)
Get Metadata Without Downloading
If you just want info about posts (likes, comments, etc.) without downloading files:
import instaloader
L = instaloader.Instaloader()
profile = instaloader.Profile.from_username(L.context, target_profile)
for post in profile.get_posts():
print(f"Likes: {post.likes}")
print(f"Comments: {post.comments}")
print(f"URL: {post.url}")
print(f"Date: {post.date_local}")
print(f"Caption: {post.caption[:100]}...")
print("-" * 50)
Download Hashtag Posts
Grab all posts from a specific hashtag:
import instaloader
L = instaloader.Instaloader()
# Download posts from a hashtag
for post in instaloader.NodeIterator(
L.context,
"9dc1f5ecaa4f41b58931f8f1a53fa24a",
lambda d: d['data']['hashtag']['edge_hashtag_to_media'],
lambda n: instaloader.Post(L.context, n),
{'tag_name': 'python'},
f"/graphql/query/query_hash/...",
lambda d: d['data']['hashtag']['edge_hashtag_to_media']['page_info']
):
L.download_post(post, target="#python")
This gets rate limited very fast though. Don't expect to download thousands of posts this way.
Final Thoughts
Instaloader works well for small-scale downloading. A few hundred posts here and there. If you need to download massive amounts, you'll hit Instagram's limits pretty quickly unless you have good proxies and implement proper delays.
Also worth mentioning: scraping Instagram violates their ToS. Use at your own risk. I only use it for personal projects and don't redistribute the content.
Link to the project: github.com/instaloader/instaloader
Documentation: instaloader.github.io