Instagram Profile Scraper - $0.70 per 1,000 Profiles
Pricing
from $0.70 / 1,000 profile scrapeds
Instagram Profile Scraper - $0.70 per 1,000 Profiles
Making an Instagram account private hides the posts and nothing else. The bio, the picture and all three counts stay public. Either kind is one row: exact follower, following and post counts, verified flag, business category, e-mail, phone, bio links. $0.70/1,000 profiles.
Pricing
from $0.70 / 1,000 profile scrapeds
Rating
0.0
(0)
Developer
Dami's Studio
Maintained by CommunityActor stats
0
Bookmarked
18
Total users
5
Monthly active users
20 hours
Issues response
2 days ago
Last modified
Categories
Share
Instagram Profile Scraper
Making an Instagram account private hides the posts and nothing else. The bio, the picture and all three counts stay public, and this reads both kinds. A list of handles goes in and one row per profile comes out: the bio, the exact follower, following and post counts, the verified and private flags, the business category, the public business e-mail and phone when the account publishes them, every link in the bio, and the profile picture.
It reads the same public pages and endpoints Instagram serves to a logged-out visitor. No browser is started, and no account of yours is used.
nasa,@nasa,instagram.com/nasaandhttps://www.instagram.com/nasa/all mean the same thing. Mix them in one list.- Up to 1,000 profiles per run, read several at a time, deduplicated before anything is billed.
- Exact counts rather than the rounded ones the page shows. 20,087,807 followers, not "20M".
- A handle that no longer exists comes back as a free diagnostic row. It doesn't charge you and it doesn't stop the run.
- Empty input returns one labelled free sample row, so you can see the shape before spending anything.
Price
$0.70 per 1,000 profiles. On top of that, $0.001 per run as the platform start fee, billed per gigabyte of run memory, so exactly $0.001 on the default 1 GB.
No volume tiers, no minimum spend, no subscription.
| Profiles | Total |
|---|---|
| 100 | $0.071 |
| 1,000 | $0.701 |
| 10,000 | $7.001 |
| 100,000 | $70.001 |
What gets charged
- One
profile-scrapedevent per profile row written to the dataset. Nothing else is metered per row. - Free: the sample row an empty run returns, and every diagnostic row (a dead handle, a refused request, a run that ran out of time). All of them carry
"charged": false. - Duplicate handles in the same list are collapsed before the run starts, so you pay once per profile.
- A run that finds nothing costs the start fee and nothing else.
- Rows never leave the dataset without a charge and are never charged without a row. The billed event is a named one, so nothing is quietly attached to
apify-default-dataset-item.
Input
{"usernames": ["nasa","@gopro","https://www.instagram.com/natgeo/"],"includeLatestPosts": false,"maxItems": 50}
| Field | What it does |
|---|---|
usernames | One entry per profile. Handle, @handle or full URL. Up to 1,000 per run, duplicates removed. |
includeLatestPosts | Adds a latestPosts array to every row with the profile's most recent posts: shortcode, link, caption, likes, comments, thumbnail, timestamp. Costs no extra requests and no extra charge. It does make each row a lot bigger. Off by default. |
maxItems | Stop after this many profiles from the list. Leave it out to read everything. Handy while testing, since you pay per profile. |
concurrency | How many profiles are read at once. Default 6, maximum 12. Higher finishes a long list sooner. Lower gives the run more room to fill in every field. |
sessionCookies | Leave empty. See When Instagram refuses a handle. |
proxyUrls | Leave empty. Only for callers who want the traffic to leave through proxy servers they already pay for, as http://user:pass@host:port. |
Run it with empty input and you get one labelled sample row, free, so you can see the output shape before you spend anything.
Output
One row per profile. This is a real row from a real run:
{"ok": true,"charged": true,"recordType": "profile","inputUrl": "https://www.instagram.com/gopro/","id": "28902942","username": "gopro","url": "https://www.instagram.com/gopro/","fullName": "GoPro","biography": "We help the world to capture and share itself in immersive and exciting ways.","externalUrl": "https://gopro.com/","externalUrls": [{ "title": "", "url": "https://gopro.com/", "linkType": "external" }],"followersCount": 20087807,"followsCount": 240,"postsCount": 9551,"verified": true,"private": false,"isBusinessAccount": true,"isProfessionalAccount": true,"categoryName": "Product/service","businessCategoryName": null,"businessEmail": null,"businessPhoneNumber": null,"businessAddress": null,"profilePicUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/449069728_1006900254437011_2641997517452774766_n.jpg","profilePicUrlHD": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/449069728_1006900254437011_2641997517452774766_n.jpg","highlightReelCount": 7,"igtvVideoCount": 0,"hasChannel": false,"joinedRecently": false,"pronouns": [],"relatedProfiles": [],"emailsFound": [],"phonesFound": [],"bioHashtags": [],"bioMentions": [],"latestPosts": [],"fbid": "17841400823952971","dataSource": "profile-api-lean+profile-page","scrapedAt": "2026-08-15T17:41:02.914Z"}
Field notes
usernameis the handle as Instagram spells it, lower-cased. Safe as a key within a run.idis the numeric account id. It survives a rename, so it's the better key if you track accounts over time.followersCountis the exact number, not the abbreviated one on the page.followsCountis how many accounts the profile follows. Instagram publishes this on one surface only, and that surface refuses a share of requests, so on a few profiles it comes backnullinstead of wrong.postsCountis the total at read time.externalUrlis the first link in the bio.externalUrlscarries all of them when the account uses the multi-link layout.businessEmailandbusinessPhoneNumberonly appear when the account publishes them.emailsFoundandphonesFoundadditionally pick up anything written into the bio text.categoryNameis the category the account chose, like "Product/service". Personal accounts have none.profilePicUrlHDis the largest picture Instagram offers. It's a time-limited CDN link, so download the image if you need to keep it.relatedProfilesholds the "suggested for you" accounts, when Instagram offers any.dataSourcesays which public surfaces the row was assembled from. Useful when you want to know why a field is null.scrapedAtis the UTC read time. Counts move constantly, so a row is a snapshot.
Real rows carry "charged": true. Sample rows carry "_sample": true, diagnostic rows carry "_diagnostic": true and an errorCode. Neither is billed.
How it works
Instagram serves several public surfaces to a logged-out visitor: the profile data endpoint the web app calls, the profile page itself, and the public embed card. The run reads whichever ones answer for a given handle.
Those surfaces disagree with each other. Some accounts are refused by one and answered by another. One of them reports a following count that doesn't match the profile page. Another leaves the post count at zero. Each field is taken only from a source known to be right for it, so a field nobody could supply comes back null rather than wrong.
Requests leave through a rotating pool of addresses, so a per-address rate limit is answered by moving instead of waiting. Profiles are read several at a time, each on its own address, which is what keeps a 1,000-handle run to minutes. Nothing is rendered and no headless browser starts, which is most of the reason this costs what it costs.
When Instagram refuses a handle
Sometimes every public surface refuses the same handle inside the same run, usually because the addresses that run happened to draw were all throttled at that moment. You get an uncharged BLOCKED diagnostic row for that handle and the run carries on. Re-running usually picks it up.
If you're pushing large lists and that happens more than you'd like, paste your own Instagram cookie into sessionCookies. The run then retries refused handles with your account, on your own rate limit. It's optional, and most runs never need it. In Chrome: F12 → Application → Cookies → https://www.instagram.com → copy the sessionid value. Pasting the whole cookie string works too.
Use an account you own and are willing to automate. A session Instagram invalidates is dropped for the rest of the run, and the run finishes with what it collected rather than failing. The closing log line carries sessionState: keyless when no cookie was supplied, ok while yours is healthy, degraded after one was dropped, exhausted when none are left.
What people use it for
- Influencer shortlisting. Drop in a few hundred handles and sort by follower count or category before anyone opens Instagram.
- Lead enrichment. Business accounts publish a category, an e-mail and a phone, and the bio often carries a second address. One row per handle, straight into a CRM.
- Weekly tracking. Re-run the same list and diff
followersCountandpostsCountto see who's growing and who's gone quiet. - Market mapping. Read every account in a niche, keep the bios and bio links, cluster them however you like.
- List hygiene.
verified,privateand theNOT_FOUNDdiagnostics tell you which handles are still real.
Reading the output
Three kinds of row come out, and they're easy to tell apart.
- Real rows carry
"charged": trueand"recordType": "profile". One billed event each. - The sample row carries
"_sample": trueand"charged": false. There's exactly one, and only when the input had no handles. - Diagnostic rows carry
"_diagnostic": true,"charged": false, theusernamethey belong to, and anerrorCodeyou can switch on.NOT_FOUNDmeans no public account at that handle.BLOCKEDmeans Instagram refused every public route for it this run.TIME_BUDGETmeans the run ran out of time before reaching it.
Filter on charged == true and you have exactly the rows you paid for. That count always equals the number of billed events.
Private accounts
A private account still returns a full row. Instagram publishes the bio, the counts, the verification flag and the picture for private profiles, and private comes back true. What isn't public is the content: latestPosts is empty for a private account no matter what you ask for. Nothing here reaches behind a private profile.
Limits
- Public data only. Private posts, stories, DMs and follower lists are out of scope, and no input reaches them. Supplying
sessionCookiesdoes not reach any of them either. - Counts are a snapshot. Two runs minutes apart will disagree slightly on a large account, and neither is wrong.
followsCountcomes backnullon a small share of profiles, because the one public surface that publishes it throttles hard. It's never filled in with an approximation.- Instagram currently returns a schema error for a slice of business accounts on its richest public endpoint. Those rows get assembled from the other surfaces instead, which is why
businessCategoryNamecan be null on an account that clearly has a category.categoryNameusually still carries it. latestPostsonly appears when you ask for it, and only covers the most recent handful of posts. For a full post history, use a post-level scraper.- Follower and following lists are not returned. Only the counts.
- Picture and thumbnail links are time-limited CDN URLs. They work right after the run and expire later.
- A renamed handle resolves to whoever owns that handle now. Track
idif that matters. - Hard ceiling of 1,000 profiles per run. Split bigger lists.
- Instagram changes its public surfaces without notice. When it does, a run returns uncharged diagnostic rows rather than wrong data, and the Actor gets repaired.
Questions
Do I need an Instagram account or a cookie?
No. Profiles are read logged out, and that answers almost every handle. sessionCookies is there for the rare handle that gets refused, and it's optional.
What if a username doesn't exist?
One uncharged diagnostic row with errorCode: "NOT_FOUND", and the run carries on through the rest of your list.
Why is followsCount sometimes null?
Instagram publishes it on one public surface only, and that surface throttles hard. The run retries on fresh addresses. When it still refuses, the field stays null instead of taking the wrong number another surface reports. Re-running usually fills it.
Can it get e-mails and phone numbers?
It returns the public business e-mail and phone when the account chose to publish them, plus anything written into the bio text. Personal accounts usually publish neither, and no private contact detail is ever exposed.
Will the run fail if Instagram blocks something?
No. A refused profile becomes an uncharged diagnostic row and the run still finishes as succeeded. A failed run would still bill the start fee, which would mean paying to be told something went wrong.
Do I need a proxy?
No. The run brings its own egress and it's already inside the price. proxyUrls exists only for callers who want traffic to leave through servers they already own.
Can I run it on a schedule?
Yes. Nothing is held between runs, so repeating the same input is safe. Diff on id and followersCount to see what moved.