Instagram Post Scraper - $0.09 per 1,000 Posts
DeprecatedPricing
from $0.09 / 1,000 post scrapeds
Instagram Post Scraper - $0.09 per 1,000 Posts
DeprecatedPaste Instagram post or reel URLs, or usernames, and get one row per post: caption, likes, comments, publish time, every carousel image and video URL, owner, hashtags, mentions, tagged users. No login, no cookies. $0.09 per 1,000 posts - the cheapest Instagram post scraper on the market.
Pricing
from $0.09 / 1,000 post scrapeds
Rating
0.0
(0)
Developer
Dami's Studio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Instagram Post Scraper
Paste Instagram post or reel URLs — or just usernames — and get one clean row per post: the full caption, like and comment counts, the publish timestamp, every image and video URL in a carousel, the owner, hashtags, mentions and tagged users.
No Instagram account. No cookies. No session token. No captcha to solve. No proxy of your own.
Price
$0.09 per 1,000 posts, plus $0.0000125 to start a run.
This is the cheapest Instagram post scraper on the market, and the rate is flat: the same $0.09 on the free plan as on any paid plan, with no volume tiers and no minimum spend, so it is what you pay from your very first run.
| Posts | Cost |
|---|---|
| 100 | $0.009 |
| 1,000 | $0.09 |
| 10,000 | $0.90 |
| 100,000 | $9.00 |
What actually bills
post-scraped— $0.00009. Charged once per genuine post row written to the dataset. That is the only per-result charge; there is no separate "filtered out" fee, no "restricted profile" fee and no dataset-item fee stacked on top.apify-actor-start— $0.0000125. Charged once per run, per GB of run memory. The Actor runs at 1 GB by default, so that is $0.0000125 a run.
Free, always:
- The sample row you get from an empty run.
- Every diagnostic row — rate limits, private accounts, deleted posts, bad input. If Instagram gives nothing back, you pay the start fee and nothing else.
- Posts dropped by your own
onlyPostsNewerThanorskipPinnedPostsfilters. They are fetched and thrown away at our expense, not billed to you.
There is no free tier and no free trial. A run that returns 40 posts costs 40 × $0.00009 = $0.0036, plus the start fee. That is the whole of it.
Input
Two ways in, and you can mix them in one run.
{"username": ["nasa", "https://www.instagram.com/natgeo/"],"resultsLimit": 12,"onlyPostsNewerThan": "7 days"}
{"postUrls": ["https://www.instagram.com/p/DcA6fTgIJ97/","https://www.instagram.com/reel/Db_sQbigDOA/","DcCUKZoAS8h"]}
| Field | What it does |
|---|---|
username | Usernames, profile URLs, or post URLs — mixed freely. The field name follows the common convention used across Instagram post Actors, so an input written for one of those normally runs here unchanged. |
postUrls | Post, reel or TV URLs. A bare shortcode such as DcCUKZoAS8h works too. directUrls and startUrls are accepted as aliases. |
resultsLimit | Posts to take from each profile. Default 20, but logged-out Instagram serves about 12 — see Limits. Ignored when you pass post URLs. |
maxItems | Hard cap on charged rows for the whole run, across every input. |
onlyPostsNewerThan | 2026-01-01, a full ISO timestamp, or a relative value like 3 days, 2 months, 1 year. Older posts are dropped and never charged. |
skipPinnedPosts | Drop the posts a profile has pinned to the top of its grid. Detectable in profile mode only. |
concurrency | Posts fetched at once. Default 6. |
requestDelayMs | Optional pause after each request. Default none. |
proxyConfiguration | Leave it alone unless you have a reason. Default is rotating Apify datacenter addresses, which is what works here and costs nothing per GB. |
Run it with no input at all and you get one labelled sample row, uncharged, showing every field — so you can see the shape before spending anything.
Output
One row per post. A real row, from a run on natgeo on 15 August 2026 (the signed query strings on
the CDN URLs are shortened here to ?<signed> for readability — the real output carries them in
full):
{"ok": true,"inputUrl": "https://www.instagram.com/natgeo/","id": "3963424910027562875","shortCode": "DcA6fTgIJ97","url": "https://www.instagram.com/p/DcA6fTgIJ97/","type": "Sidecar","productType": "carousel_container","isReel": false,"caption": "Photos by @joelsartore | Out of 33 species of jerboa, the long-eared jerboa wins the award for most oddly proportioned family member. ... #PhotoArk","hashtags": ["PhotoArk"],"mentions": ["joelsartore", "moscow_zoo_official"],"likesCount": 42094,"commentsCount": 180,"likesAndViewCountsDisabled": false,"isCommentsDisabled": false,"timestamp": "2026-08-14T19:00:01.000Z","timestampSource": "instagram","displayUrl": "https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/766106700_18686094097019133_2576347057804717679_n.jpg?<signed>","videoUrl": null,"videoDuration": null,"images": ["https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/766106700_18686094097019133_2576347057804717679_n.jpg?<signed>","https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/774970374_18686094109019133_2450257321358972437_n.jpg?<signed>"],"childPosts": [{"id": "3963424850577470287","shortCode": "DcA6ecIoDNP","type": "Image","displayUrl": "https://scontent-atl3-1.cdninstagram.com/v/...jpg?<signed>","videoUrl": null,"alt": "Photo by National Geographic on August 14, 2026. May be an image of rodent and text.","width": 1440,"height": 960},{"id": "3963424852959856137","shortCode": "DcA6eeWoIYJ","type": "Image","displayUrl": "https://scontent-atl3-1.cdninstagram.com/v/...jpg?<signed>","videoUrl": null,"alt": "Photo by National Geographic on August 14, 2026. May be an image of rodent and text.","width": 1440,"height": 960}],"childPostsCount": 2,"dimensionsWidth": 1440,"dimensionsHeight": 960,"alt": "Photo by National Geographic on August 14, 2026. May be an image of text.","ownerId": "787132","ownerUsername": "natgeo","ownerFullName": "National Geographic","ownerIsVerified": true,"ownerIsPrivate": false,"ownerProfileUrl": "https://www.instagram.com/natgeo/","coauthorProducers": [],"taggedUsers": [],"locationName": null,"locationId": null,"musicArtist": null,"musicTitle": null,"source": "post-page","scrapedAt": "2026-08-15T05:43:15.443Z"}
images is the flat list of every media URL in the post — one entry for a single image or video,
one per slide for a carousel. childPosts is the same thing with the per-slide detail: its own id,
shortcode, type, dimensions and alt text. A 20-slide NASA carousel came back with all 20.
type is Image, Video or Sidecar (carousel) — the conventional naming for Instagram post
data, so existing pipelines do not need remapping.
Field coverage, measured
From the 15 August 2026 test run over nasa and natgeo — 12 posts, 6 through each of the two
paths the Actor uses:
| Field | Present |
|---|---|
shortCode, url, type, caption, likesCount, commentsCount, timestamp | 12/12 |
ownerUsername, ownerId, ownerProfileUrl, displayUrl, images | 12/12 |
ownerFullName, ownerIsVerified | 11/12 — missing on the one coauthored post owned by a third account |
videoUrl on type: "Video" rows | 6/6 |
videoDuration | video rows fetched by post URL only |
taggedUsers, locationName, musicTitle | only when the post has them; [] or null otherwise |
Missing values are null or []. Nothing is guessed.
Which input mode should you use?
The two modes answer different questions, and the cost profile is different too.
username (profile mode) | postUrls (URL mode) | |
|---|---|---|
| Best for | "what has this account posted lately" | "give me exactly these posts" |
| Depth | about 12 most recent posts, logged out | unlimited — as many URLs as you pass |
| Requests per post | one profile request returns the whole grid | one request per post |
videoDuration | not available | returned |
skipPinnedPosts | works | not detectable |
onlyPostsNewerThan | works, and filtered posts are free | works, and filtered posts are free |
Profile mode is the cheaper and faster of the two per post, because a single request brings back the
whole recent grid. URL mode is the one to use for back-catalogue work, for a fixed list of posts you
already know, or when you need videoDuration. You can mix both in one run — pass username and
postUrls together and the results land in the same dataset.
A worked example. Monitoring 50 accounts once a day in profile mode is roughly 50 × 12 = 600
posts, or $0.054 a day plus the start fee — about $1.65 a month for a daily brand-monitoring feed.
Add onlyPostsNewerThan: "1 day" and you only pay for posts that are actually new, which for most
accounts brings that down by an order of magnitude.
Limits
Read this before you build on it.
- About 12 posts per profile, logged out. Instagram's public profile response carries roughly the
12 most recent posts and nothing more.
resultsLimit: 100will not produce 100 — it produces the 12 that exist publicly, and you are charged for 12. Deep back-catalogue paging requires a logged-in session, which this Actor deliberately does not use. If you need specific older posts, pass their URLs intopostUrls; there is no depth limit that way. - No view or play counts. Logged-out Instagram does not include them in either response, so the field is not emitted rather than filled with a guess. Any scraper showing you reel view counts is using an account.
- No comment text and no liker lists. Counts only.
- Private accounts return a diagnostic row, not data, and are not charged. Same for deleted posts and handles that do not exist.
- Media URLs are signed and expire.
displayUrl,videoUrland theimageslist point at Instagram's CDN with a signature that stops working after a few days. Download what you need soon after the run, or store the bytes rather than the link. - Rate limits are real and per address. Instagram meters its public routes per IP. The Actor
hops to a fresh address on every block rather than sleeping, and one of the two profiles in the
test run needed 15 hops before it answered. On a bad minute a profile can still come back as a
RATE_LIMITEDdiagnostic row — which is free, and re-running usually clears it. videoDurationonly on the post-URL path. The profile response does not carry the DASH manifest it is read from.- Engagement counts drift. Two runs a minute apart on the same NASA post returned 88,713 and 89,083 likes. That is Instagram, not the scraper.
FAQ
Do I need an Instagram account, cookies, or a session ID? No. Nothing to log in to, nothing to paste, no account of yours to put at risk.
What does it cost to scrape 1,000 Instagram posts? $0.09, plus $0.0000125 to start the run.
Is this the cheapest Instagram post scraper? Yes. $0.09 per 1,000 posts is below every other Instagram post scraper on the Apify store, and below every actor of any kind returning Instagram posts.
Can I scrape reels?
Yes. Reel URLs work exactly like post URLs, and reels appear in profile results with
isReel: true. Caption, likes, comments, the video file URL and the duration all come back.
Does it get every image in a carousel?
Yes — all of them, in images and in childPosts, each with its own dimensions and alt text.
Can I filter by date?
Yes: onlyPostsNewerThan takes 2026-01-01, a full ISO timestamp, or 7 days / 3 months.
Posts filtered out are not charged.
What happens if I run it with empty input? You get one clearly labelled sample row explaining what to fill in, the run finishes as SUCCEEDED, and no result is charged.
Can I export to CSV or Excel? Yes — Apify exports the dataset as JSON, CSV, Excel or XML, and the same data is available from a REST endpoint.
Is this affiliated with Instagram or Meta? No. It reads public post data that Instagram serves to logged-out visitors. Check Instagram's terms and your own local rules before using the output commercially, especially for personal data.
How it works
Instagram's JSON endpoints are walled for logged-out clients. /api/v1/media/<id>/info/ answers with
400 KB of login HTML, i.instagram.com returns 403, and the public GraphQL doc_ids that scrapers
used for years have been retired — they now return {"errors":[{"message":"execution error"}]}.
What is not walled is the ordinary post page. https://www.instagram.com/p/<shortcode>/ is server
rendered for visitors who are not logged in, and it ships the complete media object inside its
<script type="application/json"> blocks: taken_at, like_count, comment_count, the full
caption, every carousel child with its own media URL, the owner, tagged users and location. This
Actor parses that. For profiles it first tries web_profile_info, which returns the recent grid in
one request, and falls back to reading the profile page and then each post page when that route is
rate limited — which is what happened to natgeo in the final test run, and it still returned all
6 requested posts.
Two engineering notes, in case they save you time on your own build:
The transport is impit, not fetch. Plain Node returns 401 or a login wall on the first
request from an address that works fine a second later through impit's Chrome TLS profile. It reads
like an IP block and is not one — it is the TLS handshake. Overriding the user-agent re-breaks it,
so the UA is left to match the fingerprint.
Datacenter addresses, never residential. A post page is about 115 KB on the wire, and residential bandwidth is metered by the gigabyte — at that response size the egress alone would cost more per row than a row is priced at. Rotating datacenter addresses answered every request in testing and are not metered per GB, which is exactly what a per-IP rate limit needs and the reason this price is possible at all.