Request a tool
All toolsGuidesMCP serverRequest a toolPlatformsCategories
Instagram Post Scraper icon

Instagram Post Scraper

Scrape Instagram posts and reels with no login: caption, like and comment counts, publish date, every carousel image and video URL, owner and hashtags.

16 runs on Apify
Run this in the cloudRun on Apify →

Social Media Scrapers

How it works

  1. 1
    Open it on Apify

    Hit Run on Apify — it opens the tool in the cloud, no install.

  2. 2
    Set the inputs

    Adjust postUrls, username, resultsLimit (sensible defaults are pre-filled).

  3. 3
    Click Run

    The tool runs on Apify’s cloud and collects the data for you.

  4. 4
    Export the results

    Download as JSON, CSV or Excel, or pipe straight into your app, Google Sheets, or an AI agent.

Inputs

FieldWhat it doesType
postUrlsInstagram post, reel or TV URLs. A bare shortcode such as DbtErSrlB2J also works. One row is returned per post.array
usernameSame field name as the market-leading Instagram post scraper, so its input works here unchanged. Accepts usernames, profile URLs and post URLs mixed together.array
resultsLimitHow many posts to take from each username or profile URL. Ignored when you pass post URLs. Instagram serves about 12 recent posts per profile without a login, so higher values will not return more.integer
maxItemsHard cap on charged rows for the whole run, across every input. Leave empty for no cap.integer
onlyPostsNewerThanKeep only posts published on or after this moment. Accepts YYYY-MM-DD (2026-01-01), a full ISO timestamp, or a relative value such as 3 days, 2 months, 1 year. Filtered-out posts are never charged.string
skipPinnedPostsDrop posts a profile has pinned to the top of its grid. Only detectable in profile mode.boolean
concurrencyHow many posts to fetch at once. 6 is a good balance; lower it if you see address rotations in the log.integer
requestDelayMsOptional pause after each request. Leave empty for no delay.integer

What you get

A structured dataset — each result includes fields like:

ownerUsernametypecaptionlikesCountcommentsCountchildPostsCounttimestampurl

Export every run as JSON, CSV or Excel, or send it to your app, a database, Google Sheets, or an AI agent.

Related tools in Social Media Scrapers

Other ready-to-run tools in the same category — all pay-per-use on the Apify cloud.

Instagram Profile Scraper iconSocial Media Scrapers

Instagram Profile Scraper

Bulk Instagram profile scraper. Usernames or profile URLs in, one row out: bio, exact follower, following and post counts, verified flag, category, e-mail.

Ready to run — no setup

Instagram Profile Scraper iconSocial Media Scrapers

Instagram Profile Scraper

Scrape Instagram profiles by username: bio, followers, following, posts count, verified flag, business category, bio links and HD avatar. No login.

Ready to run — no setup

Instagram Reel Scraper iconSocial Media Scrapers

Instagram Reel Scraper

Scrape any public Instagram profile's reels: play counts, likes, comments, captions, duration, MP4 and cover links, audio track. No login or API key.

Ready to run — no setup

Instagram Reel Scraper iconSocial Media Scrapers

Instagram Reel Scraper

Scrape any public Instagram profile's reels: play counts, likes, comments, duration, video and cover URLs, audio track. No login or API key.

Ready to run — no setup

LinkedIn Post Search Scraper iconSocial Media Scrapers

LinkedIn Post Search Scraper

Scrape public LinkedIn posts by keyword: author, text, exact date, reactions, comments and post URL. No login or cookies.

Ready to run — no setup

LinkedIn Profile Search Scraper iconSocial Media Scrapers

LinkedIn Profile Search Scraper

Find public LinkedIn profiles by keyword, job title, company, school or location. Get name, headline, location, current role and profile URL.

Ready to run — no setup

See all Social Media Scrapers →

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.

PostsCost
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 onlyPostsNewerThan or skipPinnedPosts filters. 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"
  ]
}
FieldWhat it does
usernameUsernames, 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.
postUrlsPost, reel or TV URLs. A bare shortcode such as DcCUKZoAS8h works too. directUrls and startUrls are accepted as aliases.
resultsLimitPosts to take from each profile. Default 20, but logged-out Instagram serves about 12 — see Limits. Ignored when you pass post URLs.
maxItemsHard cap on charged rows for the whole run, across every input.
onlyPostsNewerThan2026-01-01, a full ISO timestamp, or a relative value like 3 days, 2 months, 1 year. Older posts are dropped and never charged.
skipPinnedPostsDrop the posts a profile has pinned to the top of its grid. Detectable in profile mode only.
concurrencyPosts fetched at once. Default 6.
requestDelayMsOptional pause after each request. Default none.
proxyConfigurationLeave 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:

FieldPresent
shortCode, url, type, caption, likesCount, commentsCount, timestamp12/12
ownerUsername, ownerId, ownerProfileUrl, displayUrl, images12/12
ownerFullName, ownerIsVerified11/12 — missing on the one coauthored post owned by a third account
videoUrl on type: "Video" rows6/6
videoDurationvideo rows fetched by post URL only
taggedUsers, locationName, musicTitleonly 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"
Depthabout 12 most recent posts, logged outunlimited — as many URLs as you pass
Requests per postone profile request returns the whole gridone request per post
videoDurationnot availablereturned
skipPinnedPostsworksnot detectable
onlyPostsNewerThanworks, and filtered posts are freeworks, 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: 100 will 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 into postUrls; 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, videoUrl and the images list 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_LIMITED diagnostic row — which is free, and re-running usually clears it.

  • videoDuration only 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.