
Instagram Reel Scraper
scrape.ly/store/echofield/instagram-reel-scraperSocial mediaVideos
1 monthly user

Scrape public Instagram posts from usernames, profile URLs, or post URLs. Get captions, hashtags, likes, comments count, media URLs, and timestamps for every post.
Public Instagram posts, from a username or a URL, in one dataset.
Built for social teams, agencies, and researchers who need a repeatable pull of public posts: captions, media, engagement, and the people attached to the post. Paste a list, set a cap, and run it. A bad URL is one error row. The rest of the list still finishes.
username list. @natgeo and https://www.instagram.com/p/… both work.resultsLimit per profile. A single post URL returns that post and ignores the cap.skipPinnedPosts drops posts pinned to the top of the profile.onlyPostsNewerThan stops at a UTC date (YYYY-MM-DD or ISO) or a relative window such as 1 day. Pinned posts stay unless you also skip them.basicData for caption, counts, images, owner, and timestamp.detailedData (the default) adds alt text, latest comments, music info, paid partnership, and video play count when Instagram sends them.childPosts plus an images list. Videos include videoUrl and videoDuration when the file is public.private_profile and stop. No login and no session cookie.likesCount: -1. A real 0 or false is kept. Anything Instagram did not send is left off the item.Request-based scraping, not a browser. The run spends its time on Instagram’s own post payloads, so a profile list is something you can put on a schedule.
One failed target does not kill the run. A typo, a private account, or a removed post becomes {inputUrl, error, errorDescription}. Every other username still writes its posts.
The output uses the same field names as the public Instagram Post Scraper dataset, so a table, a warehouse load, or a downstream actor can read shortCode, caption, ownerUsername, likesCount, and url without a second mapping step.
Residential proxy is required inside the Scrapely run. Datacenter addresses are rejected by Instagram. The actor does not ask you to paste a session.
{
"dataDetailLevel": "detailedData",
"resultsLimit": 20,
"username": [
"natgeo",
"https://www.instagram.com/natgeo/",
"https://www.instagram.com/p/DLNsnpUTdVS/"
]
}
One item from a real run of the example input, shortened. Long text is cut and lists show their first entries.
{
"id": "3983118388705688735",
"type": "Video",
"shortCode": "DdG4RIxIPyf",
"caption": "Presented by @Rolex. Welcome to Africa 🌍 Africa Earth’s Wild Home takes you deep into the heart of our wildest continent, bringing you closer to its animals,…",
"hashtags": [
"Rolex",
"PerpetualPlanet"
],
"mentions": [
"Rolex.",
"wunmimosaku"
],
"url": "https://www.instagram.com/p/DdG4RIxIPyf/",
"commentsCount": 578,
"dimensionsHeight": 1920,
"dimensionsWidth": 1080,
"originalHeight": 1920,
"originalWidth": 1080,
"displayUrl": "https://instagram.fnak3-1.fna.fbcdn.net/…",
"images": [
"https://instagram.fnak3-1.fna.fbcdn.net/…"
],
"videoUrl": "https://instagram.fnak3-1.fna.fbcdn.net/…",
"audioUrl": "https://instagram.fnak3-1.fna.fbcdn.net/…",
"likesCount": 79633,
"timestamp": "2026-09-10T13:00:14.000Z",
"ownerFullName": "National Geographic",
"ownerUsername": "natgeo",
"ownerId": "787132",
"isPinned": true,
"productType": "clips",
"videoDuration": 72.614,
"sponsors": [
{
"username": "rolex",
"full_name": "ROLEX"
}
]
}
| Field | Required | What it does |
|---|---|---|
username | Yes | Usernames, profile URLs, or post URLs. Duplicates of the same profile or post are scraped once. |
resultsLimit | No | Maximum posts per profile. Default 20. Minimum 1. Not applied to a single post URL. |
skipPinnedPosts | No | Default false. |
onlyPostsNewerThan | No | UTC YYYY-MM-DD, full ISO, or a relative value such as 2 weeks. |
dataDetailLevel | No | basicData or detailedData. Default detailedData. |
Hashtag, place, and audio URLs, and numeric /p/{id} or /reel/{id} links, are rejected by the input form before a run starts. Use a username, a profile URL, or the shortcode URL.
Each public post is one dataset item. Fields are written only when the payload contains them.
Always attempted from the post itself: id, type (Image, Video, or Sidecar), shortCode, caption, hashtags, mentions, url, commentsCount, dimensionsHeight, dimensionsWidth, originalHeight, originalWidth, displayUrl, images, videoUrl, likesCount, timestamp, childPosts, ownerUsername, ownerId, ownerFullName, isPinned, productType, videoDuration, affiliate, sponsors, taggedUsers, coauthorProducers, locationName, locationId, pk, isCommentsDisabled, inputUrl.
facebookPage and threadsNetProfile are set only when the author object already includes them. Share links are not an input.
Detailed data can also include alt, firstComment, latestComments, musicInfo, paidPartnership, videoPlayCount, videoViewCount, and igPlayCount. audioUrl is included only when Instagram sends a preview file. Logged-out responses often omit it.
A failed target looks like this:
{
"inputUrl": "https://www.instagram.com/private_example/",
"error": "private_profile",
"errorDescription": "This profile is private. Only public profiles are scraped."
}
Pay per result, plus platform usage (compute and proxy traffic) at the normal rates.
| Event | When it is charged | Price |
|---|---|---|
| Post | Each post saved to the dataset | $0.0013 ($1.30 per 1,000 posts) |
| Post details | Each post saved with detailed data (dataDetailLevel = detailedData, the default) | $0.0006 |
Example: 1,000 posts with detailed data ≈ $1.90 in event fees plus platform usage; with basic data ≈ $1.30. In our tests a 20-post detailed run used about $0.009 of platform usage. Error rows (private profile, unsupported URL) are never charged. If you set a maximum charge for the run, the scraper stops cleanly when it is reached and keeps every post it already saved.
Every field below can be set in the run form, passed through the API, or saved as a task. Unset fields fall back to the defaults shown.
usernamearrayRequiredInstagram username, profile URL, or post URL
A username, @username, profile URL, or shortcode post or reel URL. Hashtag, place, and audio URLs are rejected, as are numeric /p/ and /reel/ ids. Use the shortcode URL.
resultsLimitintegerOptionaldefault 20Maximum posts per profile
This is the maximum number of posts you want to scrape per profile. If you set it to 5, you'll get 5 posts for each profile you've included. This setting does not apply if you're scraping by post URLs.
Narrow down which posts are returned.
onlyPostsNewerThanstringOptionalExtract posts that are newer than
Limit how far back to the history the scraper should go. The date should be in YYYY-MM-DD or full ISO absolute format or in relative format e.g. 1 days, 2 months, 3 years. All time values are taken in UTC timezone
skipPinnedPostsbooleanOptionaldefault falseSkip pinned posts
Check if you do not want to save pinned posts.
dataDetailLevelenumOptionaldefault "detailedData"Data detail level
Choose the data package you want to extract. Please note the Detailed data are paid extra.
Every run writes its results to a dataset. Preview it in the dashboard, then export it or read it through the API.
| Field | Type | Description |
|---|---|---|
| id | string | null | Unique identifier of the post |
| type | string | null | Type of the post (Image, Video, Sidecar, etc.) |
| shortCode | string | null | Short code used in the post URL |
| caption | string | null | Caption text of the post |
| hashtags | array | null | Hashtags extracted from the post caption |
| mentions | array | null | User mentions extracted from the post caption |
| url | string | null | URL of the Instagram post |
| commentsCount | number | null | Number of comments on the post |
| firstComment | string | null | Text of the first comment on the post |
| latestComments | array | null | Array of the most recent comments on the post |
| dimensionsHeight | number | null | Height of the post image or video in pixels |
| dimensionsWidth | number | null | Width of the post image or video in pixels |
| originalHeight | number | null | Height of the originally uploaded photo in pixels, before Instagram's resize. Instagram serves images at most 1080px wide, so this may be larger than dimensionsHeight. |
| originalWidth | number | null | Width of the originally uploaded photo in pixels, before Instagram's resize. Instagram serves images at most 1080px wide, so this may be larger than dimensionsWidth. |
| displayUrl | string | null | URL of the post thumbnail image |
| images | array | null | URLs of all images in the post (carousel posts have multiple) |
| videoUrl | string | null | URL of the post video file |
| audioUrl | string | null | URL of the audio stream for reel posts with separate audio |
| alt | string | null | Accessibility caption (alt text) of the post image |
| likesCount | number | null | Number of likes on the post |
| videoViewCount | number | null | Number of views for video posts. Deprecated by Instagram; may be null or stale. Prefer videoPlayCount. |
| videoPlayCount | number | null | Number of plays for video posts |
| igPlayCount | number | null | Instagram-specific play count for video posts |
| fbLikeCount | number | null | Number of Facebook likes on the post |
| fbPlayCount | number | null | Number of Facebook plays for video posts |
| reshareCount | number | null | Number of times the post was reshared |
| timestamp | string | null | ISO 8601 timestamp when the post was published |
| childPosts | array | null | Child posts in a carousel (sidecar) post |
| ownerUsername | string | null | Username of the post author |
| ownerId | string | null | Instagram user ID of the post author |
| ownerFullName | string | null | Full display name of the post author |
| isPinned | boolean | null | Whether the post is pinned to the top of the profile |
| productType | string | null | Product type of the post (e.g., feed, clips, igtv) |
| videoDuration | number | null | Duration of the video in seconds |
| paidPartnership | boolean | null | Whether the post is a paid partnership |
| affiliate | boolean | null | Whether the post contains affiliate content |
| sponsors | array | null | Sponsors tagged in the paid partnership post |
| taggedUsers | array | null | Users tagged in the post by the author |
| musicInfo | object | null | Music or audio attribution information for the post |
| coauthorProducers | array | null | Co-author producers credited on the post |
| locationName | string | null | Name of the location tagged in the post |
| locationId | string | number | null | ID of the location tagged in the post |
| pk | string | null | Primary key of the post from the Instagram API |
| redirectedFromUrl | string | null | Original URL that redirected to this post |
| isCommentsDisabled | boolean | null | Whether comments are disabled on the post |
| inputUrl | string | null | Input URL that was used to find this post |
| searchTerm | string | null | Search term used to find this post |
| searchSource | string | null | Source of the search that found this post |
| facebookPage | object | null | Facebook page linked to the post author |
| threadsNetProfile | object | null | Threads.net profile linked to the post author |
| metaData | object | null | Additional metadata from the parent profile (present when addParentData is enabled) |
| sharesCount | number | null | Number of shares/reposts of the reel (reel scraper only, when includeSharesCount is enabled) |
| transcript | string | null | Transcript of the reel audio (reel scraper only, when includeTranscript is enabled) |
| downloadedVideo | string | null | URL to the downloaded video file in the run's key-value store (reel scraper only, when includeDownloadedVideo is enabled) |
| error | string | null | Error code if the post could not be fully scraped |
| errorDescription | string | null | Human-readable description of the error |
| requestErrorMessages | array | null | Array of error messages encountered during scraping |
You pay for the events the actor reports, such as each result or page it produces. Platform usage is included in the event prices.
| Event | Charged when | Price |
|---|---|---|
| Post | One Instagram post saved to the dataset | $0.0013 |
| Post details | Detailed data for one saved post (only when Data detail level is Detailed data) | $0.0006 |
Run this actor from your own code. Swap in your API token and the examples work as-is.
import { ScrapelyClient } from 'scrapely-client'
const client = new ScrapelyClient({ token: process.env.SCRAPELY_TOKEN })
// Start the actor and wait for the run to finish.
const run = await client.actor('echofield/instagram-post-scraper').call({
"username": [
"natgeo",
"https://www.instagram.com/natgeo/",
"https://www.instagram.com/p/DLNsnpUTdVS/"
],
"resultsLimit": 20,
"dataDetailLevel": "detailedData"
})
// Read the dataset the run produced.
const { items } = await client.dataset(run.defaultDatasetId).listItems()
console.log(items)Tokens live in your account settings. Keep them server-side: a token carries the full permissions of your account.
Connect the Scrape.ly MCP server once and your agent can run this actor as a tool. Pick your tool:
Fetch scrape.ly/agents.md and run echofield/instagram-post-scraperPaste into Claude Code, Codex, Cursor or any AI agent. It signs you in once in the browser.
{
"mcpServers": {
"scrapely": {
"url": "https://api.scrape.ly/mcp?tools=actors,runs,storage,docs&actors=echofield/instagram-post-scraper"
}
}
}No key in the file: your client opens the browser to sign you in the first time it connects.
Inputs the developer prepared for common jobs. Open one to see its input, then copy it to your account to run it as-is or adjust it.
Latest posts of a public Instagram profile.
Recent posts of a profile within a date window.
Scrape.ly gives you the infrastructure, the tooling, and the team to extract, process, and operationalize web data at any scale. Setup takes minutes. The advantage lasts forever.