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

Scrape public Instagram posts and reels for one or more hashtags. Get captions, likes, comment counts, media URLs, timestamps, and author details for trend and campaign tracking.
Collect public Instagram posts and reels for the hashtags your team tracks, and land them in a Scrapely dataset ready to export.
Social, content, and research teams use it to turn a list of hashtags into captions, creators, media, and engagement from the public grid. No session cookie.
# is optional. Each entry is its own search. Spaces in one entry are merged into a single hashtag.posts opens the public tag page in headless Chrome. It saves image and sidecar posts on that page, and image and sidecar posts from the public profiles of accounts that page shows when the caption includes the hashtag.reels stays on HTTP. It prefers items Instagram marks as clips (productType clips). When that feed is closed, it keeps videos from the public grid.resultsLimit caps how many items are saved per hashtag.OUTPUT record. It is never saved as a row, and the rest of the list still runs.0 or false stays. A missing count is left off the item.likesCount is -1. That value is the hidden-count flag, not a guessed total.The public grid is the logged-out hashtag page, not a promise of every post ever tagged. In production runs that page was one batch of about a dozen posts. The sections feed answered that a login was required, so a higher resultsLimit does not add a second page.
{
"hashtags": [
"natgeo"
],
"resultsLimit": 20,
"resultsType": "posts"
}
One item from a real run of the example input, shortened. Long text is cut and lists show their first entries.
{
"id": "3000000000000000000",
"type": "Sidecar",
"shortCode": "EXAMPLE1234",
"caption": "Cuál fue tu favorita? 😍 #natgeo #sony #bealpha #natgeo animals",
"hashtags": [
"natgeo",
"sony"
],
"url": "https://www.instagram.com/p/EXAMPLE1234/",
"displayUrl": "https://scontent.cdninstagram.com/…",
"alt": "Photo by Andrés Clausen on October 09, 2026.",
"ownerUsername": "example_creator",
"ownerId": "1234567890",
"productType": "carousel_container",
"inputUrl": "https://www.instagram.com/explore/tags/natgeo/",
"searchTerm": "natgeo"
}
| Field | Required | What you send |
|---|---|---|
hashtags | Yes | List of hashtags. One entry, one search. |
resultsType | No | posts (default) or reels. |
resultsLimit | No | Maximum saved items per hashtag. Default 20. Minimum 1. |
{
"hashtags": ["natgeo"],
"resultsType": "posts",
"resultsLimit": 20
}
travel photography is searched as travelphotography.
A saved post includes only the fields Instagram sent. These are the names the dataset can contain:
id, type, shortCode, caption, hashtags, mentions, url, commentsCount, firstComment, latestComments, dimensionsHeight, dimensionsWidth, originalHeight, originalWidth, displayUrl, images, videoUrl, audioUrl, alt, likesCount, videoViewCount, videoPlayCount, igPlayCount, fbLikeCount, fbPlayCount, reshareCount, timestamp, childPosts, locationName, locationId, ownerFullName, ownerUsername, ownerId, productType, videoDuration, paidPartnership, affiliate, sponsors, taggedUsers, isPinned, musicInfo, coauthorProducers, isCommentsDisabled, facebookPage, threadsNetProfile, inputUrl, searchTerm.
inputUrl is the hashtag page that produced the row. searchTerm is the entry you submitted. url is the public post URL. images and childPosts are present for carousels when the payload includes the slides. musicInfo and audioUrl are present when the clip payload includes that audio. latestComments is the preview on the post, up to five, not a full comment thread and not nested replies. fbLikeCount and fbPlayCount appear only when the payload includes those counts.
Every dataset row is a post or reel. Hashtags that are invalid, blocked or empty are listed in the run's status message and in the OUTPUT record of the run's key-value store:
{
"saved": 10,
"failedInputs": [
{"input": "example", "reason": "No public reels or videos for this hashtag."}
]
}
reason is a short code and status, or an exception type name. It does not include a response body. A run that finds nothing saves no rows and ends with a status message that starts with No results:. The run fails only when no hashtag is valid or Instagram blocked every hashtag after retries.
You pay $0.0005 per saved post or reel ($0.50 per 1,000 results), plus the platform usage of the run: compute, residential proxy traffic and, for posts, the hosted browser that opens the tag page. Hashtags that fail or come back empty are never saved and never charged.
Worked example: 1,000 results ≈ $0.50 in result fees plus platform usage. In our tests at the default 1,024 MB, platform usage was about $0.066 for 100 posts from one hashtag, about $0.09 for a 20-post run, and about $0.003 for a 12-reel run. Most of the usage is the browser and the proxy traffic, so it depends on how many tag pages and profiles the run opens rather than on the number of results.
Set a maximum charge for the run and the scraper stops cleanly when it is reached, keeping every result 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.
hashtagsarrayRequiredHashtags
Hashtags with or without a leading #. Each entry is one search. Spaces in one entry are merged into a single hashtag.
resultsLimitintegerOptionaldefault 20Maximum posts or reels per hashtag
Maximum number of results to save for each hashtag. The limit applies separately to every entry in the list. Each hashtag reads at most 120 feed pages, because its posts are held in memory until the hashtag is done.
Narrow down which results are returned.
resultsTypeenumOptionaldefault "posts"Content type
Posts returns the public hashtag grid. Reels prefers items Instagram marks as clips, and otherwise keeps videos from that grid.
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 (e.g., Image, Video, Sidecar) |
| shortCode | string | null | Short code identifier 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 | Total number of comments on the post |
| firstComment | string | null | Text of the first comment on the post |
| latestComments | array | null | Latest comments on the post |
| dimensionsHeight | number | null | Height of the post media in pixels |
| dimensionsWidth | number | null | Width of the post media 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 | Display image URL of the post |
| images | array | null | Image URLs for carousel (sidecar) posts |
| videoUrl | string | null | URL of the video for video posts |
| audioUrl | string | null | URL of the separate audio stream for reel videos with DASH manifest |
| alt | string | null | Accessibility caption (alt text) for 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 internal 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 | Date and time when the post was published |
| childPosts | array | null | Child posts for carousel (sidecar) posts |
| locationName | string | null | Name of the location tagged in the post |
| locationId | string | number | null | ID of the location tagged in the post |
| ownerFullName | string | null | Full name of the post owner |
| ownerUsername | string | null | Username of the post owner |
| ownerId | string | null | Instagram user ID of the post owner |
| productType | string | null | Product type of the post (e.g., clips, feed) |
| 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 | Sponsor accounts tagged in the post |
| taggedUsers | array | null | Users tagged in the post image |
| isPinned | boolean | null | Whether the post is pinned to the profile |
| musicInfo | object | null | Music attribution information for reels |
| coauthorProducers | array | null | Co-author producers for collaborative posts |
| isCommentsDisabled | boolean | null | Whether comments are disabled on the post |
| inputUrl | string | null | Original input URL that was used to find this post |
| searchTerm | string | null | Search term that led to this post being scraped |
| searchSource | string | null | Source used to find this post (e.g., google, facebook-ads) |
| facebookPage | object | null | Facebook page data associated with the post owner |
| threadsNetProfile | object | null | Threads.net profile data associated with the post owner |
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 |
|---|---|---|
| Result | One saved Instagram post or reel from a hashtag. | $0.0005 |
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-hashtag-scraper').call({
"hashtags": [
"natgeo"
],
"resultsLimit": 20,
"resultsType": "posts"
})
// 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-hashtag-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-hashtag-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.
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.