Instagram Profile Scraper - Followers Count & Engagement Rate
Pricing
from $0.67 / 1,000 profiles
Instagram Profile Scraper - Followers Count & Engagement Rate
Instagram profile scraper: one row per handle or link with the bio and bio links, exact followers count, following and posts, verified and private flags, category and engagement over recent posts. E-mail, phone and address from the contact buttons are a paid extra. $0.70 per 1,000 profiles.
Pricing
from $0.67 / 1,000 profiles
Rating
5.0
(2)
Developer
Dami's Studio
Maintained by CommunityActor stats
3
Bookmarked
1.7K
Total users
157
Monthly active users
16 hours
Issues response
3 days ago
Last modified
Categories
Share
Instagram profile scraper for handles and profile links, one row per account: the bio, exact follower, following and post counts, the verified and private flags, the business category and the bio's links. Tick the contact box and each profile is also checked for the e-mail, phone and address on its contact buttons. Private accounts come back with their bio, counts and flags, never their posts.
One thing to know before you build on it. The engagement figures are worked out from the recent
posts Instagram shows a logged-out visitor, which is 6 to 12 of them at most. That makes them a
picture of current form, not a lifetime average, and on some accounts those posts do not come back
at all. Every row carries postsAnalysed so you can see which you got.
| Input | Handles, @handles or instagram.com/... profile links |
| Output | One row per profile: bio and bio links, exact counts, verified and private flags, category, engagement over recent posts, and the e-mail, phone and address from the contact buttons as a paid extra |
| Ceiling | 1,000 profiles per run |
| Speed | Roughly 4 to 9 profiles a minute, so 100 handles take 11 to 25 minutes |
| Account needed | None |
| Price | $0.70 per 1,000 profiles on the free plan, down to $0.67 on Business. The free plan's $5 a month covers about 7,000 |
| Using an AI agent? | |
|---|---|
| Actor | dami_studio/instagram-profile-scraper |
| Set one field | usernames, labelled "Instagram usernames or profile URLs" in the Console |
| Cap the bill | maxTotalChargeUsd on the run, for example 0.25: it never charges past that |
| MCP, one link | https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/instagram-profile-scraper |
| How often | Weekly, to track follower counts |
🔍 What Instagram Profile Scraper does
You hand it a list of accounts, it hands back a table. nasa, @nasa, instagram.com/nasa and
https://www.instagram.com/nasa/ all mean the same thing, so you can mix whatever forms your list
already has. Duplicates are dropped before anything is charged.
Counts come back exact, not rounded the way the app prints them: 104,282,031 followers, not "104M".
A field that no public page reports for an account comes back null rather than guessed.
Every row also carries two worked-out blocks at no extra cost, engagement and signals, so you can
sort a list of accounts without doing the arithmetic yourself.
📋 What data you get from each Instagram profile
| What you get | Field |
|---|---|
| Handle, display name and the numeric account id | username, fullName, id |
| Bio, and every link in it | biography, externalUrl, externalUrls |
| Exact follower, following and post counts | followersCount, followsCount, postsCount |
| Verified, private, business and creator flags | verified, private, isBusinessAccount, isProfessionalAccount |
| Category | categoryName, businessCategoryName |
| Business e-mail, phone and address from the contact buttons, as a paid extra, and whether they were checked | businessEmail, businessPhoneNumber, businessAddress, contactDetailsChecked |
| Every e-mail and phone number found, published or written into the bio | emailsFound, phonesFound |
| Hashtags and @mentions in the bio | bioHashtags, bioMentions |
| Profile picture, normal and large | profilePicUrl, profilePicUrlHD |
| Accounts Instagram lists as related | relatedProfiles |
| Highlight and video counts | highlightReelCount, igtvVideoCount |
| Average and median likes and comments, engagement rate, posts per week, last post date, top hashtags and mentions, best recent post | engagement |
| Follower tier, follower-to-following ratio, contact route, link count, bio length | signals |
| Account type: personal, creator or business | accountType |
| Median views of the recent videos, those views against followers, days since the last video | videoStats |
| Country, join date (as text and as a timestamp) and former-username count, as a paid extra | accountAbout |
| The recent posts themselves, when you switch them on | latestPosts |
| The handle a related profile was found under, when you ask for related profiles | relatedOf |
▶️ How to scrape Instagram profiles
- Open Instagram Profile Scraper and click Try for free.
- Paste your handles into Instagram usernames or profile URLs, one per line.
- Set Maximum profiles while you are testing, since you pay per profile.
- Click Start.
- Download the dataset as JSON, CSV or Excel, or pull it from the Apify API.
Want to see the row shape first? Clear the list and start it empty. You get one labelled sample row, and it is not charged.
💰 How much does it cost to scrape Instagram profiles?
$0.70 per 1,000 profiles. That is the free plan's price; paid plans pay a little less: $0.69 on Starter, $0.68 on Scale and $0.67 on Business. On the free plan, the $5 Apify gives you each month covers about 7,000 profiles.
Two boxes add paid extras, charged for every profile checked, whether or not the account has that data:
| Extra | On top, per 1,000 profiles checked |
|---|---|
| Country, join date and former usernames | $4.95 on Starter, $4.90 on Scale, $4.85 on Business. Paid Apify plans only |
| E-mail, phone and address from the contact buttons | $1.10 on every plan. On the free plan it checks one profile per run |
Leave both boxes alone and you pay the profile price only.
| What the run did with a profile | Charged |
|---|---|
| Delivered it | as a profile |
| Read it, and your filters checked it and left it out | as a profile, because it was read |
| Left it out because a value your filters need did not come back, such as a private account's posts | not as a profile |
| Checked it for contact details, with that box ticked | contact details too, even when your filters then left it out |
| Read it as a related profile you asked for | as a profile, filters included |
| The same handle twice in your list | once |
| A handle that does not exist, or one that was not served | no |
The sample row and the diagnostic rows cost nothing. To cap what a run can spend, set a maximum cost
in its run options (maxTotalChargeUsd in the API). The run stops once it reaches it.
📥 What you give it
{"usernames": ["nasa", "@gopro", "https://www.instagram.com/natgeo/"],"maxItems": 50}
| Field | Default | What it is |
|---|---|---|
usernames | none | One entry per profile, in any of the forms above. Up to 1,000 per run. |
includeAccountAbout | false | Adds country, join date and how many handles the account has used before. Priced separately, and it needs a paid Apify plan. |
includeContactDetails | false | Adds the e-mail, phone and address on the account's contact buttons. $1.10 per 1,000 profiles checked, whether or not one is shown. One profile per run on the free Apify plan. |
includeLatestPosts | off | Adds the recent posts themselves to each row, with the same field names as our post scraper. They also get their own tab in the output. |
maxRelatedProfiles | none, off | Up to 50. After your list, also read that many of the accounts Instagram lists as related to it, skipping any already in your list. One step only, never follower lists. Each is charged as a profile. |
maxItems | none | Stop after this many profiles, counting from the top. Leave it empty to read the whole list. |
minFollowers, maxFollowers | none, off | Keep only profiles with at least, or at most, this many followers. |
accountType | none, off | Keep only personal, creator or business accounts, or creatorOrBusiness. |
minEngagementRate | none, off | Keep only profiles whose engagement.engagementRate is at least this. 1 means 1%. |
postedWithinDays | none, off | Keep only profiles whose newest recent post is at most this many days old. |
bioOrNameContains | none, off | Keep only profiles whose bio or name contains one of these words, in any case. |
categoryContains | none, off | Keep only profiles whose category contains one of these words, such as product. |
concurrency | empty, runs as 6 | How many profiles are read at once, 1 to 12. Higher finishes a long list sooner, and 6 keeps the rows most complete. |
sessionCookies | none | Optional. Your own Instagram cookie, used only to retry a handle that could not be read logged out. |
proxyUrls | none | Leave it empty unless you have your own servers you want the traffic to leave through. |
📤 What you get back

A real row from a run on 2 October 2026, with the picture links and the bio cut short and the long
lists trimmed (relatedProfiles had 49 entries).
{"ok": true,"charged": true,"recordType": "profile","inputUrl": "https://www.instagram.com/nasa/","id": "528817151","username": "nasa","url": "https://www.instagram.com/nasa/","fullName": "NASA","biography": "Making the seemingly impossible, possible. ...","externalUrl": "https://www.nasa.gov/","externalUrls": [{"title": "NASA.gov Homepage", "url": "https://www.nasa.gov", "linkType": "external"},{"title": "MAX POWER", "url": "https://www.nasa.gov/maxpower", "linkType": "external"}],"followersCount": 104282031,"followsCount": 89,"postsCount": 4940,"verified": true,"private": false,"isBusinessAccount": true,"isProfessionalAccount": true,"categoryName": "Government Agencies","businessCategoryName": "Government Agencies","businessEmail": null,"businessPhoneNumber": null,"businessAddress": null,"profilePicUrl": "https://scontent-iad6-1.cdninstagram.com/v/t51.2885-19/...","profilePicUrlHD": "https://scontent-iad6-1.cdninstagram.com/v/t51.2885-19/...","highlightReelCount": 6,"igtvVideoCount": 171,"hasChannel": false,"joinedRecently": false,"pronouns": [],"relatedProfiles": [{"id": "36407544", "username": "sciencechannel", "fullName": "Science Channel", "isVerified": true, "isPrivate": false, "profilePicUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/..."}],"emailsFound": [],"phonesFound": [],"bioHashtags": [],"bioMentions": [],"latestPosts": [],"fbid": "17841401474538262","dataSource": "profile-api","limitedData": false,"scrapedAt": "2026-10-02T23:37:45.893Z","accountAbout": {"available": false, "country": null, "dateJoined": null, "accountAgeDays": null, "accountAgeYears": null, "formerUsernames": [], "formerUsernameCount": null, "verifiedOnAboutPanel": null},"engagement": {"postsAnalysed": 12,"avgLikes": 278080.25,"medianLikes": 183589,"avgComments": 1012.92,"medianComments": 870.5,"engagementRate": 0.2676,"engagementPerPost": 279093.17,"videoSharePct": 8.3,"avgCaptionLength": 446.9,"postsPerWeek": 3.5,"lastPostAt": "2026-10-02T20:52:45.000Z","daysSinceLastPost": 0,"activeLast30Days": true,"topHashtags": [{"tag": "nasa", "count": 5}, {"tag": "galaxy", "count": 2}, {"tag": "space", "count": 2}],"topMentions": [{"tag": "astrokomrade", "count": 3}, {"tag": "iss", "count": 3}, {"tag": "astro_frankrubio", "count": 2}],"topPost": {"url": "https://www.instagram.com/p/DdWyDLcGiqo/", "shortCode": "DdWyDLcGiqo", "likesCount": 744422, "commentsCount": 1777, "timestamp": "2026-09-16T17:05:45.000Z"}},"signals": {"followerTier": "celebrity", "followerToFollowingRatio": 1171708.21, "followsPerPost": 0.02, "hasContactInfo": false, "contactEmailCount": 0, "contactPhoneCount": 0, "externalLinkCount": 5, "bioLength": 44, "hasBioLink": true}}
| Field | How to read it |
|---|---|
id | The numeric account id. It survives a rename, so track it rather than the handle. |
followsCount | null on a small share of rows rather than a guess. A re-run usually fills it. |
externalUrls | Every link in the bio when the account uses several. externalUrl is just the first. |
profilePicUrlHD | The largest picture on offer. Signed and time limited, so download what you want to keep. |
engagement.engagementRate | A percentage: average likes plus comments per recent post, against followers. 0.2676 means 0.27%. |
engagement.postsAnalysed | How many recent posts the figures rest on. 0 means none came back, and the rest of the block is null rather than zero. |
signals.hasContactInfo | true when at least one e-mail or phone number was found. A bio link alone does not count. |
accountType | business, creator or personal, from the account's own settings. null when Instagram did not say, as on limitedData rows. |
videoStats | Over the videos among the recent posts: recentVideos, videosWithViews, medianVideoViews, viewsToFollowersPct (the median against followers, in percent) and daysSinceLastVideo. View counts come with some answers and not others, so a row can have videos and null views. Everything is null when there are no recent videos. |
accountAbout.dateJoinedAt | Only on rows that came back with the paid About panel: the join date as a timestamp, the first of that month, so "August 2013" reads 2013-08-01T00:00:00.000Z. |
relatedOf | Only on rows read as related profiles: the handle from your list they were found under. |
contactDetailsChecked | true when the contact buttons were read for this row. false with the three contact fields empty means nobody looked, which is not the same as the account showing none. |
limitedData | true on a row with the name, followers, post count, picture and recent posts only: no bio, following count, category, links or contact fields. Run that handle again later for the rest. |
dataSource | Which public pages answered for this row. Useful when you want to know why a field is null. |
latestPosts | Only with includeLatestPosts on: up to 12 recent posts, each with url, caption, likesCount, commentsCount, timestamp, hashtags, mentions, taggedUsers, coauthorProfiles, locationName, musicTitle, isPinned, likesAndViewCountsDisabled and isCommentsDisabled. A flag Instagram did not send is null, not false. |
🧾 Reading the output
Three kinds of row land in your dataset, and recordType tells them apart.
| Row | How to spot it | Billed |
|---|---|---|
| A real profile | recordType: "profile", charged: true | yes |
| The sample row | recordType: "sample", _sample: true | no |
| A diagnostic | recordType: "diagnostic", _diagnostic: true, an errorCode | no |
Filter on recordType == "profile" and you have your profiles. charged: true marks a real row;
read it as that, not as a receipt.
| Code | What it means |
|---|---|
NOT_FOUND | No public account at that handle any more. |
NOT_SERVED | This profile was not served this run. Re-run it later. Runs before 13 October 2026 wrote BLOCKED here. |
TIME_BUDGET | The run reached its time limit before the end of your list. The profiles it read are in the dataset; raise the timeout or split the list. The run still ends as succeeded, with an amber status message, even when the time limit stopped it before any profile came back. |
STOPPED_EARLY | A long run of handles in a row could not be read, so the run stopped instead of working through the rest. unreadUsernames lists the handles it never reached. |
BAD_INPUT | Nothing in your list could be read as a handle or a profile link, or a filter value could not be used. Nothing was read. |
FILTERED_OUT | One row at the end listing who your filters left out: leftOutUsernames were read and charged as profiles, uncheckedUsernames were not. With the contact box ticked, both were charged for contact details if they were checked for them, and details says how many. |
ABOUT_PLAN_REQUIRED | You ticked the About box on a free Apify plan. Everything else still arrives. |
ABOUT_PARTIAL, ABOUT_RATE_LIMITED, ABOUT_UNAVAILABLE | The About lookup stopped part way through the run. The profile rows are unaffected. |
FREE_PLAN_LIMIT | You ticked the contact box on the free Apify plan, so only one profile was checked. profilesWithoutContactDetails says how many came back without them. |
CONTACTS_PARTIAL, CONTACTS_UNECONOMIC, CONTACTS_UNCONFIGURED | Contact details stopped part way, for example at the cost limit you set, or are paused on the actor. The profiles still arrive, and contacts are charged only on the profiles checked. |
PROXY_INPUT_ADJUSTED | A connection setting in your input could not be used and was replaced. The run carried on. |
CHARGE_ERROR | Some charges could not be recorded, so some rows may have arrived unbilled. |
UNEXPECTED_ERROR | Something broke inside the run. It is not charged. If nothing had come back yet, the run ends as failed. |
A diagnostic row is never billed.
💡 What people use it for
- Shortlisting creators. Drop in a few hundred handles, sort by follower count or category, and open Instagram only for the ones worth it.
- Weekly tracking. Schedule the list and diff
followersCountandpostsCountto see who is growing and who has gone quiet; Make, Zapier or n8n can move each week's rows into your sheet. For up to 15 accounts a run, Instagram Competitor Monitor does the diff for you. - Cleaning a list.
verified,privateand theNOT_FOUNDrows tell you which handles are still real accounts.
From a hashtag to a shortlist, in three runs:
- Run Instagram Hashtag Scraper on a tag
in your niche and copy the
ownerUsernamecolumn. - Paste those handles here and sort on
followersCount. - Put the
engagement.topPost.urlof each account you kept into Instagram Comments Scraper and read how its audience actually talks.
🚧 What it does not do
- No follower or following lists. The counts only.
- No posts unless you ask.
latestPostsis empty unless you turn it on, and even then it covers the recent handful rather than a full history. - Nothing behind a private account. A private profile returns its bio, counts and flags. The
content stays private, with
latestPostsempty and the engagement blocknull. - Some rows come back with fewer fields. Those rows carry
limitedData: true, are charged like any other row, and a later re-run usually returns the full profile. - Country does not show on every account. Instagram puts that line on larger accounts. Smaller
ones still return the join date with
countrynull. - Former usernames come back as a count, not as names.
- Contact details only where the account shows them. Personal accounts rarely do, and each checked profile is charged anyway.
- Entries that are not handles are dropped quietly, so check your list if a row is missing.
- 1,000 profiles per run, and up to four hours by default. If you set a shorter time limit in the run options, a long list can outlast it, so split the list.
- It is not an official Instagram tool. Dami's Studio is independent and is not affiliated with or endorsed by Instagram or Meta, or by any other company named on this page.
🧭 Which Instagram scraper do you need?
| If you want | Use |
|---|---|
| One row per account from a list of handles | This one |
| A profile plus the posts on its grid | Instagram Scraper |
| What changed on up to 15 accounts since your last run | Instagram Competitor Monitor |
| Handles to start from, found under a hashtag | Instagram Hashtag Scraper |
| Comments under a post or reel | Instagram Comments Scraper |
| The same kind of row for TikTok accounts | TikTok Profile Scraper |
| Threads profiles and posts | Threads Scraper |
❓ Questions people ask
Do I need an Instagram account to scrape profiles?
No. Profiles are read logged out.
Can it get e-mails and phone numbers from Instagram profiles?
Yes, with the contact box ticked: the e-mail, phone and address on each profile's contact buttons. Without it, only what is written into the bio.
Why do the numbers differ from what I see in the app?
The app rounds big counts and the row does not. Counts also keep moving, so two runs an hour apart
disagree slightly on a big account and neither is wrong: compare against the row's scrapedAt. If
a field is missing, open the profile in a private browser window. What a logged-out visitor sees
there is what this actor can read.
Can I call it from code or connect it to an AI assistant?
The API tab has code for Python, JavaScript and
the CLI. MCP clients like Claude and ChatGPT use
https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/instagram-profile-scraper; coding assistants can load
our skill file. All
three bill your Apify account at the same price.
# pip install apify-clientfrom apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("dami_studio/instagram-profile-scraper").call(run_input={"usernames": ["nasa"],},max_total_charge_usd=0.25, # the most this run may spend)for row in client.dataset(run["defaultDatasetId"]).iterate_items():if row.get("ok") is False: # free note rows, not resultscontinueprint(row.get("username"), row.get("followersCount"))
Is scraping Instagram legal?
This reads what accounts show on their profiles, never private accounts or messages. Results can still contain personal data, which GDPR and similar laws cover, so have a reason for collecting it. Before any cold contact, check the anti-spam rules. Apify's write-up on the legality of web scraping is a good starting point, and we are not lawyers.
🆘 If something breaks
Open the Issues tab on the actor page. Include the handles you used and the run ID, and the log is enough for us to see what happened.