Tracking9 min read
Export Instagram Followers to CSV with Python
Export followers of a public Instagram account to CSV with Python: full script, cursor pagination, resume after a stop, and 429/503 handling.
On this page
- How do you export Instagram followers to a CSV file with Python?
- What you need
- The full script
- Run it: start with a few pages
- The CSV columns
- How the pagination works
- Rate limits and errors
- Private accounts
- Following list too
- Open it in Excel or Google Sheets
- Compare two exports to see who unfollowed
- No-code option
- A note on use
- FAQ
How do you export Instagram followers to a CSV file with Python?
Call the /v1/user/followers endpoint in a loop, pass the next_cursor from each page as cursor in the next call, and write every user to a CSV with csv.DictWriter. The script below does this with one dependency (requests), saves its place after each page so it can resume, and waits and retries when the API answers 429 or 503. It works for public accounts only.
StoriesFly is our API, so we use it here. It needs no Instagram account, no login and no cookies, only an API key.
What you need
- Python 3.10 or newer (the script uses
str | None). - The
requestslibrary:pip install requests. - An API key. Sign in with Telegram at /developers/dashboard and create a key. It looks like
sf_live_.... A new account gets 100 free requests, once per Telegram account, which is enough to try everything in this article.
Put the key in an environment variable so it does not end up in your code:
export STORIESFLY_KEY=sf_live_your_key_hereOn Windows PowerShell use $env:STORIESFLY_KEY = "sf_live_your_key_here".
The API reference for this endpoint is at /developers/docs/user-followers.
The full script
Save this as export_followers.py:
"""Export the followers of an Instagram account to a CSV file.
Usage: STORIESFLY_KEY=sf_live_... python export_followers.py nasa --max-pages 5
Each page is one request. The script saves the cursor after every page, so if
it stops (network, empty balance) the next run continues where it left off.
"""
import argparse
import csv
import json
import os
import time
from pathlib import Path
import requests
API = "https://storiesfly.com/api/v1"
KEY = os.environ["STORIESFLY_KEY"]
FIELDS = ["id", "username", "full_name", "verified", "is_private", "profile_pic_url"]
def fetch_page(username: str, cursor: str | None) -> dict:
params = {"username": username}
if cursor:
params["cursor"] = cursor
while True:
r = requests.get(
f"{API}/user/followers",
params=params,
headers={"X-Api-Key": KEY},
timeout=60,
)
if r.status_code == 429: # too fast: wait as told, costs nothing
time.sleep(int(r.headers.get("Retry-After", "1")))
continue
if r.status_code == 503: # failed on their side, refunded: try again
time.sleep(5)
continue
if r.status_code != 200:
err = r.json()
raise SystemExit(f"{r.status_code} {err['code']}: {err['error']}")
return r.json()
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("username")
ap.add_argument("--max-pages", type=int, default=0, help="0 = no limit")
args = ap.parse_args()
username = args.username.lstrip("@")
out = Path(f"{username}_followers.csv")
state = Path(f"{username}_followers.state.json")
cursor = None
if state.exists():
cursor = json.loads(state.read_text())["cursor"]
print("Resuming from the saved cursor")
new_file = not out.exists()
pages = rows = 0
with open(out, "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS, extrasaction="ignore")
if new_file:
writer.writeheader()
while True:
page = fetch_page(username, cursor)
writer.writerows(page["users"])
f.flush()
pages += 1
rows += len(page["users"])
print(f"page {pages}: +{len(page['users'])} (total {rows})")
if not page["has_more"]:
state.unlink(missing_ok=True)
print("Done: the list has ended")
break
cursor = page["next_cursor"]
state.write_text(json.dumps({"cursor": cursor}))
if args.max_pages and pages >= args.max_pages:
print("Stopped at --max-pages; run again to continue")
break
print(f"{rows} rows written to {out}, {pages} requests spent")
if __name__ == "__main__":
main()Run it: start with a few pages
Large accounts have a lot of followers, and every page costs one request. Do a cheap first run with --max-pages:
python export_followers.py nasa --max-pages 3The output looks like this (sample values):
page 1: +50 (total 50)
page 2: +50 (total 100)
page 3: +50 (total 150)
Stopped at --max-pages; run again to continue
150 rows written to nasa_followers.csv, 3 requests spentOpen nasa_followers.csv and check the columns. When it looks right, run the same command without --max-pages to export the whole list. Because the script remembers the cursor, the full run continues after the pages you already have.
How much does it cost?
Each page is one request, and the page size follows what Instagram returns, so it varies. The reliable way to estimate: run 3 to 5 pages, divide the rows by the pages, and divide the account's follower count by that number. That is roughly the number of requests the full export will take. Multiply that by the price per request on /developers/pricing.
The CSV columns
| Column | Meaning |
|---|---|
id | Numeric Instagram user ID. It does not change when the username changes |
username | Current username |
full_name | Display name |
verified | Whether the account has the verified badge |
is_private | Whether the follower's own account is private |
profile_pic_url | Profile picture link |
The profile_pic_url values are Instagram CDN links and expire after a while. If you need the pictures, download them right away. For comparing lists over time, use id, not username. For more details, see our guide on download Reels in HD.
How the pagination works
The followers endpoint returns a page of users plus two fields: has_more and next_cursor. To get the next page you send the same request with cursor=<next_cursor>. The loop in main() does exactly that and stops when has_more is false.
After each page the script writes the new cursor to nasa_followers.state.json. If the run stops for any reason (your laptop sleeps, the network drops, the balance runs out), start the same command again. It reads the cursor from the state file and continues. The CSV is opened in append mode and the header is written only once, so there are no duplicates. When the list ends, the state file is deleted.
If you want a fresh export of the same account, delete both nasa_followers.csv and nasa_followers.state.json first, or rename the old CSV (see the comparison section below).
Rate limits and errors
The script handles two cases on its own:
- 429
rate_limited. You sent requests too fast. The limit is 1 request per second before the first top-up and 3 per second at the Start level. The script sleeps for the number of seconds in theRetry-Afterheader and repeats the call. A 429 is not charged. - 503
upstream_unavailable. The call failed on our side. The request is refunded, so the script waits 5 seconds and tries again.
Anything else stops the script with the status code, the error code and the message. Errors are JSON like {"error": "...", "code": "..."}. The ones you may meet:
| Status | Code | Charged? |
|---|---|---|
| 400 | bad_request | No |
| 401 | unauthorized | No |
| 402 | credits_exhausted | No |
| 403 | private_account | Yes |
| 404 | not_found | Yes |
| 500 | internal_error | Refunded |
Every answer carries headers such as X-Units-Charged and X-Balance-Remaining, so you can print them if you want to watch your balance during a long export.
Private accounts
You cannot export followers of a private account. The API answers 403 private_account and returns no data. This is by design, not a bug in the script.
Following list too
To export who the account follows, use the same script with a different endpoint. Change this line in fetch_page:
f"{API}/user/followers",to:
f"{API}/user/following",You will also want to change the output file names so the two exports do not mix: replace _followers with _following in the two Path(...) lines in main(). The columns are the same. See /developers/docs/user-following.
Open it in Excel or Google Sheets
The file is UTF-8 CSV.
- Google Sheets: File, Import, Upload, choose the file. Sheets reads UTF-8 correctly.
- Excel: use Data, From Text/CSV, and make sure the file origin is set to UTF-8. If you double-click the file, Excel may read names with accents or non-Latin letters incorrectly. The import dialog avoids that.
Long numeric IDs can show in scientific notation in Excel. Set the id column to text during import to keep all digits.
Compare two exports to see who unfollowed
Run the export now, rename the file, and run it again a week later. For example, rename the first one to nasa_followers_monday.csv, then run the script again (it creates a new nasa_followers.csv). Then compare the id columns with sets:
import csv
def ids(path):
with open(path, newline="", encoding="utf-8") as f:
return {row["id"] for row in csv.DictReader(f)}
old, new = ids("nasa_followers_monday.csv"), ids("nasa_followers.csv")
print("Unfollowed:", len(old - new))
print("New followers:", len(new - old))old - new are IDs that were in the first file and are not in the second, new - old the other way around. Look them up in the CSV by id to get the usernames. Note that an account that was deleted or banned also disappears from the list.
No-code option
If you do not write code, StoriesFly has a follower export for regular users at /instagram-followers-export. The API in this article is for when you want to run it yourself, schedule it, or build it into your own tool.
A note on use
This works with public data. Follow Instagram's terms and the law in your country, and do not use exported lists for spam or harassment.
FAQ
Can I export followers of a private Instagram account?
No. For a private account the API returns 403 private_account and no list. Only public accounts can be exported.
How many requests does a full follower export take?
One request per page. Page size varies, so run 3 to 5 pages with --max-pages, divide rows by pages, and divide the follower count by that to estimate the total.
What happens if the script stops halfway?
Run the same command again. The cursor is saved to a state file after every page, and the script continues from there and appends to the same CSV.
Do I need an Instagram account or cookies?
No. The script only sends your StoriesFly API key in the X-Api-Key header. There is no Instagram login in the code.
Are failed requests charged?
Rate limit (429), bad request (400), wrong key (401) and empty balance (402) responses are free. A 503 or 500 is refunded. Data, not_found and private_account answers are charged.
Can I get the list of accounts someone follows too?
Yes. Change /user/followers to /user/following in the script, and change the file names. The columns are the same.
