Instagram Post Scraper - $0.18/1k, No Login avatar

Instagram Post Scraper - $0.18/1k, No Login

Pricing

Pay per event

Go to Apify Store
Instagram Post Scraper - $0.18/1k, No Login

Instagram Post Scraper - $0.18/1k, No Login

Instagram post and reel links in, clean rows out: caption, likes, comments, publish time, every carousel image and video URL, owner, hashtags, mentions and tagged users. Runs logged out, no browser. $0.18 per 1,000 posts, flat on every plan.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

0

Bookmarked

13

Total users

8

Monthly active users

2 days ago

Last modified

Share

Instagram Post Scraper

Paste Instagram post or reel links and get one row per post: the full caption, the like and comment counts, the exact publish time, every image and video URL including all the frames of a carousel, the owner, the hashtags, the mentions, the tagged users and the co-authors.

Nothing to set up, and no browser is started.

  • Takes post links, reel links, IGTV links or bare shortcodes. Mix them freely in one run.
  • Carousels come back whole. Every child image and video URL, not just the cover frame.
  • Reels come back with the video file URL and its exact duration in seconds.
  • Duplicate links are removed before anything is charged, so pasting a messy list is safe.
  • A link that's dead, private or deleted produces a free diagnostic row, never a charge.

Price

$0.18 per 1,000 posts, plus a $0.0005 start fee per run (billed per gigabyte of run memory, so exactly that on the default 1 GB).

$0.18 on the free plan, $0.18 on a paid one. No volume tiers, no minimum spend, nothing charged monthly.

PostsTotal cost
100$0.0185
1,000$0.1805
10,000$1.8005
100,000$18.0005

What is actually charged

  • One post-scraped event per post row written to the dataset. Nothing else is metered per row.
  • Never charged: the sample row, and any diagnostic row. A dead URL, a deleted post and a link the target refused all come back at no cost, marked "charged": false.
  • Duplicate links in your list are collapsed before the run starts, so the same post is never charged twice.
  • Links that aren't Instagram post or reel links are reported free of charge and never fetched.
  • If every link in your list is dead, you pay the start fee and not a cent more.
  • Every row has a charge behind it and every charge has a row, so the two reconcile. The billed event is named post-scraped, not Apify's generic per-item event.

Input

{
"postUrls": [
"https://www.instagram.com/p/DbtErSrlB2J/",
"https://www.instagram.com/reel/DcCH2ZygIiP/",
"DbwWBxpDm_0"
],
"maxItems": 10
}
FieldWhat it does
postUrlsThe post, reel or IGTV links you want. Bare shortcodes such as DbtErSrlB2J work too, and so do instagram.com/username/p/<shortcode>/ links copied from a profile. Up to 5,000 per run; duplicates are removed first.
maxItemsStop after this many posts, counting from the top of your list. Leave it empty to take every link you supplied.
sessionCookiesOptional. Leave it empty for public posts. See About logins below.
proxyUrlsLeave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for.

Hand it no links at all and it writes one labelled sample row, free of charge, so you can see the shape first.

Output

One row per post. Real row from a real run, with the media URLs and the carousel frames trimmed:

{
"ok": true,
"charged": true,
"recordType": "post",
"inputUrl": "https://www.instagram.com/p/DbtErSrlB2J/",
"id": "3957840214710558089",
"shortCode": "DbtErSrlB2J",
"url": "https://www.instagram.com/p/DbtErSrlB2J/",
"type": "Sidecar",
"productType": "carousel_container",
"isReel": false,
"isSponsored": false,
"caption": "Where in the (Martian) world is Curiosity?\n\nOur Curiosity Mars rover has started its climb up a Martian valley called Valle Grande…\n\n#NASA #Mars #CuriosityRover",
"hashtags": ["NASA", "Mars", "CuriosityRover"],
"mentions": [],
"likesCount": 273272,
"commentsCount": 1692,
"videoViewCount": null,
"likesAndViewCountsDisabled": false,
"isCommentsDisabled": false,
"timestamp": "2026-08-06T15:49:01.000Z",
"displayUrl": "https://scontent-fra3-1.cdninstagram.com/v/t51.82787-15/766074877_...jpg",
"videoUrl": null,
"videoDuration": null,
"images": ["…jpg", "…jpg", "…jpg", "… 5 more …"],
"videos": [],
"childPosts": ["… one object per frame, trimmed …"],
"childPostsCount": 8,
"dimensionsWidth": 495,
"dimensionsHeight": 292,
"alt": null,
"ownerId": "528817151",
"ownerUsername": "nasa",
"ownerFullName": "NASA",
"ownerIsVerified": true,
"ownerIsPrivate": false,
"ownerProfilePicUrl": "https://scontent-fra3-1.cdninstagram.com/v/t51.2885-19/…jpg",
"ownerProfileUrl": "https://www.instagram.com/nasa/",
"coauthorProducers": [],
"taggedUsers": [],
"locationName": null,
"locationId": null,
"musicArtist": null,
"musicTitle": null,
"source": "graphql",
"scrapedAt": "2026-08-20T19:51:52.669Z"
}

A reel row fills in videoUrl, videoDuration and videos, and carries type: "Video" with productType: "clips".

Field notes

  • typeImage, Video or Sidecar. Sidecar is Instagram's name for a carousel.
  • productType — the finer-grained kind: feed for an ordinary post, clips for a reel, carousel_container for a carousel, igtv for a long-form video.
  • timestamp — the moment the post was published, normalised to ISO 8601 in UTC. It never moves, so it's the right key for sorting and for date filtering after the fact.
  • likesCount / commentsCount — a snapshot at the moment the row was read, not at publish time. On an account that has hidden its counts these come back null and likesAndViewCountsDisabled is true.
  • images / videos — flat arrays of every media URL in the post, in carousel order. For a single-image post images holds one URL; for a six-frame carousel it holds six.
  • childPosts — the same carousel frames as objects, each with its own id, shortcode, type, dimensions and alt text. Use this when you need to tell which frame is a video.
  • videoDuration — length of the clip in seconds, to three decimals. null on a still image.
  • taggedUsers — people tagged in the photo itself, as {username, fullName, id}. People merely @-mentioned in the caption are in mentions instead.
  • coauthorProducers — usernames of accounts sharing authorship of a collab post. Empty for an ordinary post.
  • displayUrl / videoUrl — direct links to the media on Instagram's CDN. They carry a signed query string that expires after a few days, so download what you need in the same session rather than storing the URL for later.
  • source — which reader produced the row. graphql is the normal one; post-page means the run fell back to the slower reader mid-flight. The fields are identical either way.

About logins

Runs are logged out. A public post normally answers a logged-out reader on the first attempt, and that's how the vast majority of links are read.

Instagram does sometimes refuse a logged-out reader on a specific post or during a busy stretch. When that happens you get a free RATE_LIMITED or BLOCKED diagnostic row for that one link and the run carries on. If you're hitting that a lot, sessionCookies lets you paste your own Instagram cookie (sessionid=…; csrftoken=…, from Chrome's F12 → Application → Cookies → instagram.com) and the run reads through your account's own rate limit instead. Treat those values like a password. They're used for that run's requests and nothing else, never stored, never logged, never written to the dataset.

Nothing is ever posted, liked, followed or changed on any account.

How it works

It asks Instagram directly for the post record the public post page is built from, and reads the answer as structured data. No page is rendered and no browser is started.

Requests leave through a large pool of rotating addresses, and the run moves to a fresh one every few requests, so the per-address rate limit is answered by moving rather than by waiting.

Posts are fetched several at a time, which is why a hundred links take seconds rather than minutes.

A second, slower reader is kept in reserve. If the fast path stops answering, the run switches to it automatically, writes one free note in the dataset saying so, and carries on returning the same fields.

Nothing is stored between runs.

Diagnostic rows

A link that doesn't work out comes back as a row rather than failing the run. Those rows carry "_diagnostic": true, "charged": false and an errorCode: NOT_FOUND when a post is deleted, private or the link is wrong; BAD_INPUT when an entry isn't an Instagram post link at all; RATE_LIMITED or BLOCKED when the target pushed back on that one link; TIME_BUDGET when the run ran out of time before reaching a link. Each carries the inputUrl it belongs to, so you can retry exactly those.

The free sample row is tagged "_sample": true and only appears when the run was given no links. Filter on charged == true for the data alone, and counting those rows checks the bill.

Limitations

  • It reads posts, not profiles. Give it post, reel or IGTV links. It doesn't take a username and walk somebody's grid.
  • Public posts only. A post on a private account isn't visible to anyone who isn't logged in and following, so it returns a free NOT_FOUND row rather than data.
  • View and play counts aren't exposed to logged-out callers, so videoViewCount is almost always null. Likes and comments are exposed and are returned.
  • Comments themselves aren't returned, only how many there are.
  • Like and comment counts are a snapshot at read time and will differ from what you see if you open the post a minute later.
  • Media URLs are signed and expire after a few days. Fetch the files during the run or shortly after. Don't treat the URL as permanent.
  • Carousel child dimensions are whatever Instagram reports for that frame, which for some older posts is a scaled size rather than the original.
  • Age-restricted posts, and posts removed for a copyright claim, return a free NOT_FOUND row.
  • Instagram rate-limits per address. The run handles that by moving addresses, but a very large single run will be slower per post than a small one.
  • If the first 40 links in a list all return nothing, the run stops there rather than reading the rest, writes one free note saying so, and charges nothing. That's a guard against a list of dead links, not a limit on real work. A single good post resets it.
  • The ceiling is 5,000 links per run.

Questions

Do I need an Instagram account, a session cookie or a login?

Not for public posts. Runs work with nothing filled in and read post links the way a logged-out visitor does. The sessionCookies field is there for the case where Instagram starts refusing your links and you'd rather read through your own account's rate limit.

What happens to a link that's dead or private?

One uncharged diagnostic row for that link with errorCode: "NOT_FOUND", and the run carries on through the rest of your list. You're never billed for a link that returned nothing.

Does a carousel count as one post or as several?

One. A carousel is one row, one charge, with every frame's URL inside it, however many frames it has.

Can I hand it a reel link?

Yes. Reel links, post links, IGTV links and bare shortcodes all resolve to the same record, and reels come back with the video URL and its duration.

Will the run fail if Instagram blocks something?

No. A blocked or empty link produces an uncharged diagnostic row explaining what happened and the run finishes as succeeded. Failing it would still bill the start fee, so you'd be paying to hear that one link was dead.

Do I need a proxy?

No. Egress is included in the rate above and there's nothing to set up. proxyUrls is there only for people who already pay for their own servers and want the traffic to leave through those.

Can I run this on a schedule?

Yes. Nothing is held between runs, so a schedule with the same links is safe and gives you a clean time series of engagement counts. Use shortCode as the key when you diff one run against the next.