Instagram Post Scraper avatar

Instagram Post Scraper

Pricing

$19.99/month + usage

Go to Apify Store
Instagram Post Scraper

Instagram Post Scraper

Instagram Profile Post Scraper lets you collect structured post data from any public profile. Get captions, hashtags, media links, engagement stats, and publish times. Ideal for marketers, researchers, and developers needing fresh Instagram content insights in a usable format.

Pricing

$19.99/month + usage

Rating

5.0

(2)

Developer

Scraper Engine

Scraper Engine

Maintained by Community

Actor stats

1

Bookmarked

92

Total users

9

Monthly active users

11 hours ago

Last modified

Share

Instagram Post Scraper — Captions, Likes, Comments and Media URLs

Instagram Post Scraper pulls every public post, reel and carousel from an Instagram profile — or from a single post URL — into structured JSON: caption, hashtags, mentions, like and comment counts, a sample of the newest comments, every CDN image rendition, the direct video file URL, tagged users, collaborators and an exact UTC timestamp. No Instagram login or cookie is used. Point it at a username, a profile URL, or a /p/, /reel/ or /tv/ link and start the run on Apify.


What is Instagram Post Scraper Pay Per Events?

Instagram Post Scraper Pay Per Events is an Apify Actor that scrapes public Instagram posts, reels and carousels — either an entire profile's grid, up to a limit you set, or one row per single post URL. It runs logged out: nothing in src/main.py authenticates against Instagram, and the input schema has no field for a username/password, cookie or session id. Every run returns one normalized JSON row per post, covering the caption, engagement counts, media URLs, tagged users and a comment sample. It's built for social media managers, marketers, competitive-intelligence and growth teams, and developers or AI engineers who need Instagram post data as a structured feed rather than screenshots or manual copy-paste.


What Instagram post data is publicly available to scrape?

Anyone who opens a public Instagram profile without logging in — in a browser or an incognito window — can already see its recent posts, captions, like counts, a slice of comments, and the media itself. That is the same surface this Actor reads.

Data categoryPublicly availableRestricted (needs login)
Caption, hashtags, mentionsYes
Like countYes, unless the creator hid it (Instagram then omits the count)Hidden like counts stay hidden regardless of login
Comment count and a sample of the newest commentsYes, up to 10 per postFull comment pagination and reply threads — login-walled
Image renditions and video file URLYes
Creator username, full name, verified flagYes
Tagged users, collaborators, music/audio infoYes, when the post carries them
Full post history / private account postsNoEntirely login-walled, not reachable logged out
Stories, DMs, /explore/tags/ pagesNoNot supported by this Actor and not part of the public grid

Instagram Post Scraper only returns publicly visible data — what any visitor sees. Nothing behind a login wall.


What data can I extract with Instagram Post Scraper?

Every run returns post identity fields, engagement and media fields, and — on Detailed runs — a comment sample, alt text and paid-partnership status. The table below lists every key transform_post() writes to the dataset in src/main.py.

FieldDescription
inputUrlThe username, profile URL or post URL you supplied for this row
idInstagram's internal media id
typeImage, Video, or Sidecar (carousel)
shortCodeThe post's short code, e.g. DbGz1jumsFV
urlCanonical https://www.instagram.com/p/<shortCode>/ link
captionFull caption text (empty string if the post has none)
hashtagsHashtags parsed out of the caption, Unicode-aware
mentions@handles parsed out of the caption
timestampISO-8601 UTC posting time
ownerUsername / ownerFullName / ownerIdThe creator who posted it
likesCountLike count
commentsCountTotal comment count reported by Instagram
displayUrlThe largest available image rendition
imagesEvery CDN image rendition Instagram shipped, largest first
dimensionsWidth / dimensionsHeightPixel dimensions of the main media
childPostsCarousel children (only non-empty when type is Sidecar)
isCommentsDisabledWhether comments are disabled on the post
videoUrl / videoViewCount / videoDurationVideo file URL, view count and duration — Video posts only
videoPlayCountSame underlying number as videoViewCount, kept as a second key for compatibility — Detailed, Video posts only
productTypeInstagram's own post classification (feed, clips, carousel_container) when it sets one
taggedUsersAccounts tagged in the post, when any are tagged
coauthorProducersCollaborators on the post, when it's a collab
musicInfoAudio/music metadata, when the post carries clips_metadata — Detailed only
isPaidPartnershiptrue/false on profile rows, null when Instagram omits the field (every single-post-URL row) — Detailed only
altInstagram's own accessibility caption — Detailed only
firstCommentText of the newest top-level comment — Detailed only
latestCommentsUp to 10 newest top-level comment objects — Detailed only

Post identity and content fields

inputUrl, id, shortCode, type, url, caption, hashtags, mentions, timestamp, ownerUsername, ownerFullName, ownerId — what the post is, who posted it, and when.

Engagement, media and comment fields

likesCount, commentsCount, displayUrl, images, dimensionsWidth, dimensionsHeight, childPosts, isCommentsDisabled, the video fields, productType, taggedUsers, coauthorProducers, musicInfo, isPaidPartnership, alt, firstComment, latestComments — the numbers, the media, and (on Detailed runs) the comment sample.

🤖 Add-on: Need additional Instagram data?

If you only need reels, Instagram Reels Scraper is a narrower, faster fit. If you're auditing which posts a creator has been tagged in, Instagram Tagged Posts Scraper covers tagged-post grids. Following/follower graphs live in Instagram Followers And Following Scrapper, and public business contact details are covered separately by Instagram B2B Phone Number Scraper — none of that is duplicated here.


Why not build this yourself?

Instagram ships no stable public API for reading an arbitrary public profile's posts — the closest official surface, the Graph API, is scoped to Business or Creator accounts the developer has connected and had approved through Meta's own app-review process, not the general public grid. Building a scraper of your own means solving problems this Actor's own source history already documents fixing: the profile page's embedded auth tokens (APP_ID, csrf_token) have to be re-harvested any time Instagram changes its page bundle; the post-page markup has moved anchors before — comments_connection replaced a retired xdt_api__v1__media__shortcode__web_info key, and every single-post URL silently returned zero rows until that break was found and fixed; and residential proxy rotation is needed because the profile-token request is the piece most likely to be rate-limited from a datacenter exit. Running the Actor means those repairs land centrally instead of breaking your own pipeline the next time Instagram's front end changes.


How to use Instagram Post Scraper

Instagram Post Scraper runs entirely inside Apify — there's no separate sign-up or API key to obtain before your first run.

  1. Open Instagram Post Scraper Pay Per Events on the Apify Console or the Apify Store listing.
  2. Fill in username with one or more usernames (natgeo), profile URLs, or single post/reel/tv URLs — this is the only required input.
  3. Optionally set resultsLimit, skipPinnedPosts, onlyPostsNewerThan and dataDetailLevel to shape what comes back.
  4. Click Start and let the run finish.
  5. Open the Storage tab to view the dataset, or export it as JSON, CSV, Excel, XML or HTML.

How to scale to bulk post extraction

username is an array (editor: stringList), so one run already accepts any mix of usernames, profile URLs and post URLs — there's no separate "bulk mode" to switch on. Duplicate targets (natgeo, @natgeo and https://www.instagram.com/natgeo/ all resolving to the same profile) are collapsed automatically before scraping starts, so padding the list with variants costs nothing extra.


What can you do with Instagram post data?

  • 📊 A social media analyst tracking brand performance uses likesCount and commentsCount across a profile's recent posts to chart engagement trends over time.
  • 🕵️ A competitive-intelligence researcher uses hashtags and mentions pulled from a competitor's captions to see which campaigns and partners they're tagging.
  • 🖼️ A media archivist uses images[] and videoUrl to pull every CDN rendition and the direct video file for a creator's feed, without screenshotting.
  • 🤝 An influencer marketing manager uses isPaidPartnership and coauthorProducers to audit which sponsored and co-authored posts a creator has actually published.
  • 🤖 An AI engineer feeds caption, hashtags and latestComments[].text into a RAG pipeline or an LLM-based sentiment tool — every field is a typed JSON primitive, so no HTML parsing step is needed first.

How does Instagram Post Scraper handle rate limits and blocking?

Every request goes through Apify Proxy, defaulting to the RESIDENTIAL proxy group; the Actor requests a fresh proxy URL from the pool on each retry attempt rather than reusing one address. Failed requests are retried up to 3 times with an increasing pause between attempts, except for HTTP 401, 403 and 404, which are treated as verdicts and not retried. Feed pages are fetched one at a time with a 1-second pause between them, and a profile scan stops after 400 pages as a safety ceiling, since Instagram fixes the feed page size at 12 items regardless of what you request. Concurrent connections are capped at 10 total and 5 per host. When a target can't be scraped at all — a private account, a missing account, or an HTTP 400 — the run fails loudly with the specific reason attached to that target, rather than exiting green with an empty dataset.


⬇️ Input

This Actor is billed pay-per-event on a single event, row_result ($0.002 per event, per .actor/pay_per_event.json). _push_row() in src/main.py is the only place the Actor calls Actor.push_data(row, charged_event_name="row_result"), and every dataset row goes through that one function — so every row that lands in the dataset was charged, and there is no separate uncharged accounting or diagnostic row type pushed alongside it. Posts that fail to fetch, and posts excluded by skipPinnedPosts or onlyPostsNewerThan, are never passed to _push_row() at all, so they are never charged and never appear in the dataset.

ParameterRequiredTypeDescriptionExample value
usernameYesarray of stringsOne or more Instagram usernames, profile URLs, or single post/reel/tv URLs. Story links, /explore/tags/ links and /share/ links are rejected with a warning rather than being scraped as a handle.["https://www.instagram.com/natgeo/", "nasa"]
resultsLimitNointeger (default 10, min 1, max 100000)Maximum posts to return per profile. Ignored for single post URLs — one URL always returns one row. Instagram's feed page size is fixed at 12, so a large limit means many sequential requests.25
skipPinnedPostsNoboolean (default false)Drop posts pinned to the top of the profile grid being scraped. A collaboration post pinned on a co-author's grid is kept.false
onlyPostsNewerThanNostring, nullable (default null)Only return posts newer than this UTC cutoff. Absolute (YYYY-MM-DD or full ISO timestamp) or relative (6 hours, 3 days, 1 week, 2 months, 1 year). An unparseable value fails the run instead of being silently ignored."1 week"
dataDetailLevelNoenum: detailedData | basicData (default detailedData)Detailed adds alt, latestComments, firstComment, musicInfo, isPaidPartnership, real carousel-child shortcodes and videoPlayCount, at the cost of one extra request per post. Both levels save one row per post and are charged identically per row."detailedData"
proxyConfigurationNoobject (default {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]})Proxy used for every Instagram request. Datacenter groups do return posts, but the profile-token request is more likely to be rate-limited from a datacenter exit.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

Example input

{
"username": [
"https://www.instagram.com/natgeo/",
"nasa",
"https://www.instagram.com/p/DbGz1jumsFV/"
],
"resultsLimit": 25,
"skipPinnedPosts": false,
"onlyPostsNewerThan": "1 week",
"dataDetailLevel": "detailedData",
"proxyConfiguration": {
"useApifyProxy": true,
"apifyProxyGroups": ["RESIDENTIAL"]
}
}

⬆️ Output

Results are written to the Actor's dataset as typed, normalized JSON — one row per post, with a consistent field set across runs. Export the dataset as JSON, CSV, Excel, XML or HTML from the Storage tab, or fetch it through the Apify API.

Example output

{
"inputUrl": "https://www.instagram.com/natgeo/",
"id": "3660778310592222546",
"type": "Sidecar",
"shortCode": "DbGz1jumsFV",
"url": "https://www.instagram.com/p/DbGz1jumsFV/",
"caption": "A rare glimpse of the aurora over Svalbard. #arctic #aurora",
"hashtags": ["arctic", "aurora"],
"mentions": [],
"timestamp": "2026-06-18T21:04:12.000Z",
"ownerUsername": "natgeo",
"ownerFullName": "National Geographic",
"ownerId": "528817151",
"likesCount": 184213,
"commentsCount": 1042,
"displayUrl": "https://scontent.cdninstagram.com/v/t51.../image1.jpg",
"images": [
"https://scontent.cdninstagram.com/v/t51.../image1_large.jpg",
"https://scontent.cdninstagram.com/v/t51.../image1_medium.jpg"
],
"dimensionsWidth": 1080,
"dimensionsHeight": 1350,
"childPosts": [
{
"id": "3660778310592222547",
"type": "Image",
"shortCode": "DbGz1jumsFW",
"url": "https://www.instagram.com/p/DbGz1jumsFW/",
"dimensionsWidth": 1080,
"dimensionsHeight": 1350,
"displayUrl": "https://scontent.cdninstagram.com/v/t51.../image2.jpg",
"images": ["https://scontent.cdninstagram.com/v/t51.../image2_large.jpg"],
"alt": "Aurora borealis over a snow-covered fjord"
}
],
"isCommentsDisabled": false,
"productType": "carousel_container",
"taggedUsers": [
{
"full_name": "Svalbard Tourism",
"id": "998877",
"is_verified": true,
"profile_pic_url": "https://scontent.cdninstagram.com/v/t51.../profile.jpg",
"username": "visitsvalbard"
}
],
"alt": "Aurora borealis over a snow-covered fjord",
"firstComment": "This is breathtaking!",
"latestComments": [
{
"id": "18070377008012681",
"text": "This is breathtaking!",
"ownerUsername": "skywatcher_",
"ownerProfilePicUrl": "https://scontent.cdninstagram.com/v/t51.../c1.jpg",
"timestamp": "2026-06-18T22:11:03.000Z",
"repliesCount": null,
"replies": [],
"likesCount": 14,
"owner": {
"id": "5522901",
"is_verified": false,
"profile_pic_url": "https://scontent.cdninstagram.com/v/t51.../c1.jpg",
"username": "skywatcher_"
}
}
],
"isPaidPartnership": false
}

How does it work?

Requests are routed through Apify Proxy, defaulting to residential IPs, and every call carries a browser-like User-Agent and header set. For a profile, the Actor first loads the profile's public page to harvest Instagram's own APP_ID and csrf_token values, then calls Instagram's internal feed endpoint with those tokens attached — the same endpoint the public web app itself uses — and pages through it 12 posts at a time. For a single post, reel or tv URL, it loads the post's own public page and recovers the embedded media object with a string-aware brace-matching parser, since that data ships inside a large inline script block rather than a clean API response. Only what a logged-out visitor could already see is returned; no login, cookie or session is used anywhere in the request path. The output schema — field names and types — is fixed by the Actor's own transformation code, so it stays the same across runs even when Instagram changes its page markup underneath.


Integrations

Instagram Post Scraper runs on Apify, so it works with anything that can call the Apify API or trigger an Apify Actor run.

Calling Instagram Post Scraper programmatically

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_API_TOKEN>")
run = client.actor("your-account/instagram-post-scraper-pay-per-events").call(
run_input={
"username": ["https://www.instagram.com/natgeo/"],
"resultsLimit": 25,
"dataDetailLevel": "detailedData",
}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["shortCode"], item["likesCount"])

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request.

No-code tools (n8n, Make, LangChain)

In n8n, use the HTTP Request node (or the community Apify node) pointed at the Actor's run endpoint, passing the same JSON input shown above. In Make, the Apify app module lets you start a run and iterate the resulting dataset as part of a scenario, no custom code required. In LangChain or a comparable agent framework, wrap the Apify API call as a tool so an agent can request fresh Instagram post data mid-conversation.


Scraping publicly available data — the kind any logged-out visitor can already see — is generally lawful in most jurisdictions. Instagram Post Scraper returns only what Instagram serves to a logged-out visitor: no private accounts, no login-walled comment threads, and no data harvested from a signed-in session. Because post captions, comments and creator handles can constitute personal data, GDPR (EU) and CCPA (California) may still apply to how you store and use anything you collect that identifies a person — the lawfulness of scraping the data and the lawfulness of what you do with it afterward are separate questions. Consult legal counsel if your use case involves bulk storage of personal data.


❓ Frequently asked questions

What Instagram post fields does Instagram Post Scraper return?

The top fields are caption, likesCount, commentsCount, timestamp and ownerUsername — see the full field table under "What data can I extract with Instagram Post Scraper?" above.

Does Instagram Post Scraper require an Instagram account or login?

No. src/main.py never authenticates against Instagram — there is no username/password or cookie field anywhere in the input schema, and every request is made as a logged-out visitor.

How many posts can I extract in one run?

Up to resultsLimit per profile (default 10, maximum 100,000), and any number of profiles or post URLs per run since username accepts an array. Single post URLs always return exactly one row each, regardless of resultsLimit.

Is Instagram Post Scraper billed per post?

Yes. It registers one pay-per-event, row_result, charged once for every post actually saved to the dataset — $0.002 per row as configured in .actor/pay_per_event.json. Posts that fail to fetch, and posts dropped by skipPinnedPosts or onlyPostsNewerThan, are never pushed to the dataset and are never charged.

What happens if a profile is private, doesn't exist, or a post has been removed?

The run fails for that target with a specific reason attached instead of returning an empty, unexplained dataset: "the account is private," "the account does not exist," "Instagram rejected the feed request (HTTP 400)," or, for a post URL, that the post page could not be read. If every target in a run fails this way, the whole run fails with the combined reasons in its status message.

Can I scrape multiple Instagram profiles or posts at once?

Yes. username is an array — mix usernames, profile URLs and post/reel/tv URLs freely in one run, and duplicates targeting the same profile or post are automatically collapsed before scraping starts.

Does Instagram Post Scraper work with Claude, ChatGPT and other AI agent tools?

It's callable as a standard HTTP endpoint through the Apify API, so any agent framework that can make an HTTP request — including custom tools built for Claude or ChatGPT-based agents — can trigger a run and read back the resulting dataset.

How does Instagram Post Scraper compare to other Instagram post scrapers?

Checked on the Apify Store on 2026-07-30: Instagram Post Scraper by Apify documents a similar core field set plus a repost-count metric they describe as not yet shipped, and documents likesCount returning -1 when a creator hides likes. This Actor's likesCount reads 0 in that case, since Instagram's response carries no like count at all for hidden-like posts and the row builder does not fabricate a sentinel value. Instagram Profile Scraper (Bio & Posts) by coderx focuses primarily on profile/bio fields with a smaller recent-posts sample rather than deep per-post data. Neither competitor's README documents a tolerance for the Instagram feed's own out-of-order pagination the way this Actor's onlyPostsNewerThan cutoff does.

Does Instagram Post Scraper return data in a format LLMs can use directly?

Yes. Every field is typed, normalized JSON with consistent names across runs — no HTML parsing or CSS selectors required. Pass a row directly to an LLM prompt, index caption and latestComments[].text into a vector store, or feed the whole dataset to an agent tool.

What happens when Instagram changes its layout or anti-bot system?

The Actor is maintained: its parsing logic already survived one such change, where Instagram retired the xdt_api__v1__media__shortcode__web_info anchor the parser previously relied on, and the post-page parser was rewritten around the comments_connection container and a string-aware brace walk instead. No specific turnaround time for future fixes is promised here.

Can I use Instagram Post Scraper without managing proxies or browser infrastructure?

Yes. Proxy selection and rotation are handled through Apify Proxy (proxyConfiguration), and the Actor makes plain HTTP requests rather than driving a browser — you don't provision or maintain either yourself.

Which Instagram post fields work best for AI training data and RAG indexing?

For RAG indexing, caption, hashtags and latestComments[].text carry the highest-information free text. For structured training data, likesCount, commentsCount, timestamp and type are present on every row across both detail levels and all three media types, making them the most consistent fields to key on.


ScraperWhat it extracts
Instagram Reels ScraperReels specifically, for accounts that only need short-form video posts
Instagram Tagged Posts ScraperPosts a profile is tagged in, rather than posts it authored
Instagram Followers And Following ScrapperA profile's follower and following graph
Instagram B2B Phone Number ScraperPublicly listed business contact numbers from Instagram profiles
Instagram-Profile-Reels-ScraperCombined profile details and reels in one run
TikTok-Hashtag-ScraperThe same hashtag-driven content discovery pattern, for TikTok instead of Instagram

💬 Your feedback

Found a bug, or a field that doesn't match what Instagram actually returns? Let us know through the Actor's Issues tab on Apify so it can be checked against a live run and fixed. Bug reports and missing-field requests are what keeps this Actor's parsing logic current as Instagram's own page markup changes.