Instagram Post Scraper
Pricing
$19.99/month + usage
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
Maintained by CommunityActor stats
1
Bookmarked
92
Total users
9
Monthly active users
7 hours ago
Last modified
Categories
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 category | Publicly available | Restricted (needs login) |
|---|---|---|
| Caption, hashtags, mentions | Yes | — |
| Like count | Yes, 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 comments | Yes, up to 10 per post | Full comment pagination and reply threads — login-walled |
| Image renditions and video file URL | Yes | — |
| Creator username, full name, verified flag | Yes | — |
| Tagged users, collaborators, music/audio info | Yes, when the post carries them | — |
| Full post history / private account posts | No | Entirely login-walled, not reachable logged out |
Stories, DMs, /explore/tags/ pages | No | Not 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.
| Field | Description |
|---|---|
inputUrl | The username, profile URL or post URL you supplied for this row |
id | Instagram's internal media id |
type | Image, Video, or Sidecar (carousel) |
shortCode | The post's short code, e.g. DbGz1jumsFV |
url | Canonical https://www.instagram.com/p/<shortCode>/ link |
caption | Full caption text (empty string if the post has none) |
hashtags | Hashtags parsed out of the caption, Unicode-aware |
mentions | @handles parsed out of the caption |
timestamp | ISO-8601 UTC posting time |
ownerUsername / ownerFullName / ownerId | The creator who posted it |
likesCount | Like count |
commentsCount | Total comment count reported by Instagram |
displayUrl | The largest available image rendition |
images | Every CDN image rendition Instagram shipped, largest first |
dimensionsWidth / dimensionsHeight | Pixel dimensions of the main media |
childPosts | Carousel children (only non-empty when type is Sidecar) |
isCommentsDisabled | Whether comments are disabled on the post |
videoUrl / videoViewCount / videoDuration | Video file URL, view count and duration — Video posts only |
videoPlayCount | Same underlying number as videoViewCount, kept as a second key for compatibility — Detailed, Video posts only |
productType | Instagram's own post classification (feed, clips, carousel_container) when it sets one |
taggedUsers | Accounts tagged in the post, when any are tagged |
coauthorProducers | Collaborators on the post, when it's a collab |
musicInfo | Audio/music metadata, when the post carries clips_metadata — Detailed only |
isPaidPartnership | true/false on profile rows, null when Instagram omits the field (every single-post-URL row) — Detailed only |
alt | Instagram's own accessibility caption — Detailed only |
firstComment | Text of the newest top-level comment — Detailed only |
latestComments | Up 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.
- Open Instagram Post Scraper Pay Per Events on the Apify Console or the Apify Store listing.
- Fill in
usernamewith one or more usernames (natgeo), profile URLs, or single post/reel/tv URLs — this is the only required input. - Optionally set
resultsLimit,skipPinnedPosts,onlyPostsNewerThananddataDetailLevelto shape what comes back. - Click Start and let the run finish.
- 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
likesCountandcommentsCountacross a profile's recent posts to chart engagement trends over time. - 🕵️ A competitive-intelligence researcher uses
hashtagsandmentionspulled from a competitor's captions to see which campaigns and partners they're tagging. - 🖼️ A media archivist uses
images[]andvideoUrlto pull every CDN rendition and the direct video file for a creator's feed, without screenshotting. - 🤝 An influencer marketing manager uses
isPaidPartnershipandcoauthorProducersto audit which sponsored and co-authored posts a creator has actually published. - 🤖 An AI engineer feeds
caption,hashtagsandlatestComments[].textinto 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.
| Parameter | Required | Type | Description | Example value |
|---|---|---|---|---|
username | Yes | array of strings | One 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"] |
resultsLimit | No | integer (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 |
skipPinnedPosts | No | boolean (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 |
onlyPostsNewerThan | No | string, 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" |
dataDetailLevel | No | enum: 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" |
proxyConfiguration | No | object (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 ApifyClientclient = 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.
Is it legal to scrape Instagram posts?
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.
🔗 Related scrapers
| Scraper | What it extracts |
|---|---|
| Instagram Reels Scraper | Reels specifically, for accounts that only need short-form video posts |
| Instagram Tagged Posts Scraper | Posts a profile is tagged in, rather than posts it authored |
| Instagram Followers And Following Scrapper | A profile's follower and following graph |
| Instagram B2B Phone Number Scraper | Publicly listed business contact numbers from Instagram profiles |
| Instagram-Profile-Reels-Scraper | Combined profile details and reels in one run |
| TikTok-Hashtag-Scraper | The 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.