Instagram Media & Profile Scraper
Pricing
from $3.50 / 1,000 media returneds
Instagram Media & Profile Scraper
Scrape Instagram profile data: reels, posts, stories, highlights, follower counts, engagement.
Pricing
from $3.50 / 1,000 media returneds
Rating
0.0
(0)
Developer
EndSpec
Maintained by CommunityActor stats
1
Bookmarked
62
Total users
15
Monthly active users
19 days ago
Last modified
Categories
Share
Instagram Media Scraper: Extract Public Instagram Posts, Reels, and Photos by Username
Simple Input-Output Example
Input (give one public Instagram username):
{"username": "natgeo","count": 12}
Output (one dataset row per media post):
{"id": "3920738671208852726","code": "DZpQwxqimz2","title": "There's more to the story of Pompeii than just destruction. Join Tom Hiddleston as he traces the stories of those who experienced the eruption and those who survived.","createdAt": 1781625607,"author": {"id": "787132","username": "natgeo","fullName": "National Geographic","isVerified": true,"profilePictureUrl": "https://scontent-sea5-1.cdninstagram.com/v/t51.82787-19/683576066_18653628823019133.jpg"},"totalMedia": 1,"statistics": {"likeCount": 134893,"commentCount": 1160},"mediaList": [{"id": "3920738671208852726","downloadUrl": "https://scontent-sea5-1.cdninstagram.com/o1/v/t2/f2/m86/AQPe8N5mlvlR6qWjIPHTifHxjsbpPCCehRdGbeduKqs2.mp4","thumbnailUrl": "https://scontent-sea1-1.cdninstagram.com/v/t51.82787-15/723942035_18667214785019133.jpg","mediaType": "VIDEO"}],"_query": "{\"username\":\"natgeo\"}"}
Important Notes:
- You get results instantly — no crawling, no browser, no proxy setup
- One run returns the most recent media from one public profile — up to 50 posts (12 by default)
- Pay only per media item returned — failed runs and zero-result runs are never charged
- All data comes from public Instagram profiles only
- Contact: contact@endspec.net
Full Actor Documentation
Instagram Media Scraper
Get the recent public media of any Instagram profile in seconds. Captions, like and comment counts, post timestamps, media type, thumbnails, and direct media URLs — as clean JSON, one row per post.
Overview
The Instagram Media Scraper is an Apify Actor that extracts publicly available media posts from a profile on Instagram. You give it one username; it returns that profile's most recent grid media — photo posts, carousels, video posts, and reels — with engagement counts and media URLs attached to each post.
It is built for people who need Instagram post data fast and per item, not for people who need a full archive. One run is one lookup of one profile's recent media. It is deliberately simple: no login, no session cookies, no proxy configuration, no browser automation.
What this Actor does not do: it does not return profile-level fields such as follower or following counts, it does not scrape stories or highlights, it does not walk a profile's full back catalogue (50 posts is the ceiling), it does not search hashtags or locations, and it cannot access private accounts.
What You Can Do
- Scrape recent Instagram posts by username: Fetch a public profile's latest grid media in a single instant request
- Read engagement per post: Get
likeCountandcommentCountfor every returned post - Get captions and post dates: Full caption text and a Unix timestamp for each post
- Pull direct media URLs: A
downloadUrlandthumbnailUrlfor every photo or video in the post - Handle carousels correctly: Multi-image and multi-video posts return every media entry in
mediaList - Tell photos from videos: Each media entry carries a
mediaTypeofPHOTOorVIDEO - Build post-level datasets: Export to JSON, CSV, or Excel from the Apify dataset, or read it straight from the API
Data Availability
All data returned by this Actor is publicly available information. It only returns media that an Instagram account holder has published publicly on their own profile. It does not access private accounts, does not log in as any user, and does not return anything that is not visible to any logged-out visitor of that profile.
Cost, Performance, and Limits
Performance. The Actor reads the profile grid one page of 12 posts at a time and stops as soon as it has the number you asked for, so a run costs one page fetch for count up to 12 and at most five for count of 50. Most runs finish in a few seconds. There is no browser, no crawling, and no proxy overhead, so runs are cheap in compute and consistent in duration regardless of profile size.
Billing. Pricing is pay-per-event. The Actor charges the media-returned event once for each media post it writes to the dataset. The current per-event price is shown on the Actor's Pricing tab in Apify Store.
What is never charged:
- A username that returns no public media — zero rows, zero charges
- A run that hits an error — the error row itself is not a charged event
- Asking for more posts than the profile has published — you are charged for the posts actually returned, never for the number requested
Limits — read these before planning a large job:
- Up to 50 posts per run. The Actor pages through the profile grid until it has
countposts or the profile runs out.50is the hard ceiling; a highercountis clamped to 50, not rejected. - One profile per run. There is no username array. To cover many profiles, start one run per username (see Best Practices).
- Recent media only. There is no date filter and no offset. You always get the newest end of the profile grid, in order, and paging is handled for you.
- Public profiles only. Private accounts and usernames that do not exist finish with an empty dataset.
Actor Input Parameters
The Actor accepts the following input parameters:
username
• Type: string
• Required: effectively yes (see Important Notes)
• Default: none (the input form is prefilled with natgeo)
• Description: The Instagram username to scrape, without the @ symbol
e.g., natgeo
count
• Type: integer
• Required: optional
• Default: 12
• Range: minimum 1, maximum 50
• Description: How many media posts to return, newest first. The Actor fetches as many pages as it needs to reach this number, or stops early if the profile has fewer posts. Values above 50 are clamped to 50.
e.g., 5
Important Notes:
- Always provide
username— the input schema does not mark it as required, but the Actor has nothing to look up without it. Do not rely on a helpful error if you omit it; supply it every time. - No
@, no URL — passnatgeo, not@natgeoand nothttps://www.instagram.com/natgeo/. countis what you get, and what you pay for — the Actor keeps fetching until it hascountposts. Raising it raises the bill proportionally; lowering it is the way to keep a run cheap. Anything above50is treated as50.- Usernames, not display names —
natgeo, notNational Geographic. - Unknown or private usernames are not an error — the run succeeds with an empty dataset and no charge.
Input Examples
Example 1: Default Lookup (Most Recent Posts)
{"username": "natgeo"}
When to use: The standard case. count defaults to 12, so you get the profile's most recent grid media.
Example 2: Limit the Result Count (and the Bill)
{"username": "nasa","count": 3}
When to use: When you only care about the three newest posts — for a "what did they post today" check or a scheduled monitor. You are charged for 3 items instead of 12.
Example 3: Request a Deeper Slice of the Grid
{"username": "9gag","count": 50}
When to use: When 12 posts is too shallow — trend analysis, a fuller engagement sample, or a monthly content audit. The Actor pages through the grid and returns 50 posts, and you are charged for the 50 rows it writes. If the profile has published fewer than 50, you get what exists and pay for that.
Example 4: Username That Does Not Exist (Empty Run)
{"username": "thisaccountsurelydoesnotexist99213x"}
Result: The run finishes successfully with zero dataset rows and no charge. A non-existent, private, or media-less profile is not reported as an error row.
Output Structure
The Actor writes results to the Apify dataset, one row per media post. Rows can be downloaded as JSON, CSV, Excel, XML, or RSS from the run's Storage tab, or read through the Apify API.
Successful Output Format
{"id": "string","code": "string","title": "string","createdAt": 1781625607,"author": {"id": "string","username": "string","fullName": "string","isVerified": true,"profilePictureUrl": "string"},"totalMedia": 1,"statistics": {"likeCount": 0,"commentCount": 0},"mediaList": [{"id": "string","downloadUrl": "string","thumbnailUrl": "string","mediaType": "PHOTO"}],"_query": "string"}
Field Descriptions:
id • Type: string • Description: Instagram's numeric identifier for the post
code
• Type: string
• Description: The post shortcode. Build the permalink yourself as https://www.instagram.com/p/<code>/
title • Type: string • Description: The post caption, including hashtags, mentions, and line breaks. Empty string when the post has no caption.
createdAt
• Type: number
• Description: Post publication time as a Unix timestamp in seconds (multiply by 1000 for JavaScript Date)
author
• Type: object
• Description: The posting account — id (string), username (string), fullName (string), isVerified (boolean), profilePictureUrl (string). This is the account you asked for; it is repeated on every row.
totalMedia
• Type: number
• Description: How many media entries the post contains. 1 for a single photo or video, greater than 1 for a carousel.
statistics
• Type: object
• Description: Engagement counters for the post — likeCount (number) and commentCount (number). View counts are not included, for videos or otherwise.
mediaList
• Type: array of objects
• Description: One entry per media file in the post. Each entry has id (string), downloadUrl (string, direct file URL), thumbnailUrl (string, preview image URL), and mediaType (string, PHOTO or VIDEO). Reels published to the profile grid appear here as VIDEO.
_query
• Type: string
• Description: A JSON string echoing the query the Actor ran, e.g. "{\"username\":\"natgeo\"}". Useful for matching rows back to inputs when you merge datasets from many runs.
Error Output Format
Errors are written as their own dataset row and are not charged:
{"status": "error","_query": "string","error": "string"}
status — the string "error". Note that successful media rows do not carry a status field; the presence of status is itself the marker of a failed run.
_query — the JSON echo of the query that failed.
error — one of exactly two messages, quoted verbatim in the examples below.
Output Examples
Example 1: Video Post / Reel with Full Engagement Data
{"id": "3920738671208852726","code": "DZpQwxqimz2","title": "There's more to the story of Pompeii than just destruction. Join Tom Hiddleston as he traces the stories of those who experienced the eruption and those who survived.\n\n#PompeiiOutOfTime premieres July 22.","createdAt": 1781625607,"author": {"id": "787132","username": "natgeo","fullName": "National Geographic","isVerified": true,"profilePictureUrl": "https://scontent-sea5-1.cdninstagram.com/v/t51.82787-19/683576066_18653628823019133.jpg"},"totalMedia": 1,"statistics": {"likeCount": 134893,"commentCount": 1160},"mediaList": [{"id": "3920738671208852726","downloadUrl": "https://scontent-sea5-1.cdninstagram.com/o1/v/t2/f2/m86/AQPe8N5mlvlR6qWjIPHTifHxjsbpPCCehRdGbeduKqs2.mp4","thumbnailUrl": "https://scontent-sea1-1.cdninstagram.com/v/t51.82787-15/723942035_18667214785019133.jpg","mediaType": "VIDEO"}],"_query": "{\"username\":\"natgeo\"}"}
Example 2: Carousel Post (Five Photos)
{"id": "3942746327205767191","code": "Da3cuUgk2gX","title": "It's estimated that almost 300 species have been saved from extinction by the Endangered Species Act since it took effect in 1973.\n\nLearn more at the link in bio.\n\nPhotos by @carltonward, Aaron Huey (@argonautphoto), @joelsartore, and @erikalarsen888","createdAt": 1784232024,"author": {"id": "787132","username": "natgeo","fullName": "National Geographic","isVerified": true,"profilePictureUrl": "https://scontent-sea5-1.cdninstagram.com/v/t51.82787-19/683576066_18653628823019133.jpg"},"totalMedia": 5,"statistics": {"likeCount": 37104,"commentCount": 196},"mediaList": [{"id": "3942746260516348186","downloadUrl": "https://scontent-sea5-1.cdninstagram.com/v/t51.82787-15/723941001_18667214111019133.jpg","thumbnailUrl": "https://scontent-sea1-1.cdninstagram.com/v/t51.82787-15/723941001_18667214111019133_thumb.jpg","mediaType": "PHOTO"},{"id": "3942746266950399321","downloadUrl": "https://scontent-sea5-1.cdninstagram.com/v/t51.82787-15/723941002_18667214112019133.jpg","thumbnailUrl": "https://scontent-sea1-1.cdninstagram.com/v/t51.82787-15/723941002_18667214112019133_thumb.jpg","mediaType": "PHOTO"}],"_query": "{\"username\":\"natgeo\"}"}
Note that totalMedia is 5 and mediaList carries one entry per photo (truncated to two here for readability). Carousels are still one dataset row and one charged item, not five.
Example 3: Post With No Caption (Partial Data)
{"id": "1895980730057410141","code": "BpP4G28DsZd","title": "","createdAt": 1540238537,"author": {"id": "6082","username": "examplecreator","fullName": "Example Creator","isVerified": false,"profilePictureUrl": "https://scontent-sea5-1.cdninstagram.com/v/t51.2885-19/10624422_1681113512113145.jpg"},"totalMedia": 1,"statistics": {"likeCount": 42,"commentCount": 3},"mediaList": [{"id": "1895980730057410141","downloadUrl": "https://scontent-sea5-1.cdninstagram.com/v/t51.2885-15/44634159_2179485832302397.jpg","thumbnailUrl": "https://scontent-sea1-1.cdninstagram.com/v/t51.2885-15/44634159_2179485832302397_thumb.jpg","mediaType": "PHOTO"}],"_query": "{\"username\":\"examplecreator\"}"}
Captionless posts return title as an empty string rather than omitting the field. Fields are returned as Instagram publishes them, so treat any individual field as possibly absent and code defensively.
Example 4: No Results Found (Empty Dataset)
When the username does not exist, is private, or has no public media, the run succeeds with no rows at all:
[]
There is no "not found" row and no error row — the dataset is simply empty and nothing is charged. If you expected data here, re-check the spelling of the username and that the profile is public.
Example 5: Temporary Capacity Error
{"status": "error","_query": "{\"username\":\"natgeo\"}","error": "Our servers are busy right now — please retry shortly. You were not charged."}
Emitted when the service is rate-limited. Retry the run in a few minutes.
Example 6: Generic Error
{"status": "error","_query": "{\"username\":\"natgeo\"}","error": "Something went wrong on our side. You were not charged."}
Emitted for any other failure — network trouble, a brief service interruption, or an unreadable response. Retry; if it persists, contact support with your run ID.
Example 7: Partial Result
{"status": "error","_query": "{\"username\":\"natgeo\"}","error": "Only part of the requested results could be retrieved. You were charged only for the results actually returned."}
Emitted only when a count above 12 was interrupted partway through — the posts already written to the dataset above this row are valid and are the only ones charged. Re-run to fetch the remainder.
These three strings are the complete set of error messages this Actor produces. You can match on them exactly in downstream code.
Use Cases
For Influencer Marketers and Agencies
Vetting creators before you pay them. Pull a candidate's last 12 posts and read likeCount and commentCount per post to judge real engagement instead of trusting a media kit. Because rows carry createdAt, you can also see posting cadence.
Example Workflow:
- Assemble your shortlist of Instagram usernames
- Run the Actor once per username with
count: 12 - Export the dataset to CSV
- Compute average likes and comments per post, and posting frequency from
createdAt - Rank candidates and drop the ones whose engagement does not match their reach
For Brands and Social Media Managers
Competitor monitoring. Watch what rival accounts publish and how it performs, without manually scrolling their profiles. Schedule a small daily run per competitor and let the dataset accumulate.
Example Workflow:
- Create an Apify Schedule that runs the Actor daily with
count: 3per competitor - Let each run append to the dataset
- De-duplicate on the
idfield — the newest posts overlap between days - Track like and comment growth and which formats (
mediaType) they lean on - Alert your team when a competitor post crosses an engagement threshold
For Researchers and Analysts
Content and engagement analysis. Study captions, formats, and engagement across a set of public accounts. title gives you the full caption for text analysis, mediaList tells you whether the post was a photo, a video, or a carousel.
Example Workflow:
- Define your sample of public accounts
- Run the Actor for each and collect the datasets
- Parse hashtags and mentions out of
title - Group engagement by
mediaTypeand bytotalMedia(single vs. carousel) - Publish results — the underlying data is public
For Developers
Post-level data in your app. Wire the Actor into your product to fetch a profile's recent media on demand. Runs are a single request, so latency is low enough for interactive use, and pay-per-event means an idle integration costs nothing.
Example Workflow:
- Call the Apify API to run the Actor with a
username - Read the dataset items from the run
- Build permalinks as
https://www.instagram.com/p/<code>/from thecodefield - Use
_queryto attribute rows when you fan out many runs in parallel - Cache results — re-running the same profile within minutes returns the same posts
For Content and Media Teams
Asset discovery. mediaList[].downloadUrl and thumbnailUrl point directly at the media files, so you can preview a creator's recent work quickly. Rights still belong to the creator — see Data Sources and Legality.
Best Practices
Getting Reliable Results
- Always pass
username— and pass it clean: no@, no URL, no display name - Do not set
countabove 12 expecting more — the Actor fetches one page;countonly trims it - Set
countlow when you only need the newest posts — it is the direct lever on your bill - Expect the newest end of the grid — there is no way to reach older posts through this Actor
Scaling to Many Profiles
- One run per username — start runs in parallel via the Apify API or a Schedule rather than looking for a batch input that does not exist
- De-duplicate on
id— repeat runs of the same profile will return overlapping posts - Tag your runs with
_query— when merging datasets from many runs,_querytells you which username produced each row - Re-run for freshness, not for depth — running the same profile again gets you newer posts, never older ones
Error Handling
- Check for the
statusfield — successful media rows have nostatus; a row with"status": "error"is a failure - Treat an empty dataset as "not found", not as a bug — unknown, private, and media-less profiles all end this way
- Retry the two error messages — both are transient in character; a retry a few minutes later usually succeeds
- You are not charged for errors or empty runs — a retry costs you nothing but time
- Log run IDs — include them when contacting support
Data Usage
- Media URLs expire —
downloadUrlandthumbnailUrlare time-limited Instagram CDN links. Fetch or re-host what you need soon after the run rather than storing the URLs long-term. createdAtis in seconds — a common off-by-1000 bug in JavaScript- Engagement is a snapshot —
likeCountandcommentCountare counts at the moment of the run; re-run to refresh them - Respect creators' rights — extracting a media URL is not a licence to republish the media
Data Sources and Legality
All data returned by this Actor is publicly available information published on Instagram profiles. The Actor retrieves only what any logged-out visitor could see on a public profile. It does not log in, does not access private accounts, does not bypass any access control, and does not return private or non-public data.
What This Means:
- Every caption, count, and media URL returned was published publicly by the account holder
- Private accounts are not accessible through this Actor by any input
- Personal data may still be present in public content (usernames, full names, faces, mentions). If you are in a jurisdiction covered by the GDPR or a similar regime, you are the controller of what you collect and you need a lawful basis to process it.
- Media remains the copyright of its creator. Retrieving a
downloadUrldoes not grant you a licence to reuse, republish, or train on that media.
You are responsible for complying with applicable laws and terms of service, including copyright law, data-protection law, and Instagram's own terms. Use the data for legitimate purposes such as analytics, research, and market intelligence, and honour any request from an account holder to stop processing their data. Nothing in this README is legal advice.
Frequently Asked Questions
Q: Why did I get fewer posts than I asked for?
A: The profile has fewer public posts than your count. The Actor pages through the grid until it reaches count or runs out, and you are only charged for the rows it actually writes. Note that 50 is the ceiling: a count above 50 is clamped to 50.
Q: Can I get a profile's entire post history? A: No. This Actor returns the newest end of the grid only, up to 50 posts per run. There is no offset or date-range input, and paging is handled internally rather than exposed.
Q: Can I scrape several usernames in one run?
A: No — the input takes a single username. Start one run per profile; the Apify API and Schedules make fanning out straightforward.
Q: Where are follower counts, stories, and highlights? A: This Actor returns post-level media only. Profile-level metrics, stories, and highlights are not part of its output.
Q: Where is the view count for videos?
A: statistics contains likeCount and commentCount only. View counts are not returned for any media type.
Q: My run finished successfully but the dataset is empty. Why? A: The username does not exist, is misspelled, is private, or has no public media. This is not treated as an error and is never charged.
Q: How do I get the URL of a post?
A: Build it from code: https://www.instagram.com/p/<code>/. The Actor does not return a ready-made permalink field.
Q: Do the media URLs keep working?
A: Not indefinitely. downloadUrl and thumbnailUrl are signed, expiring Instagram CDN links. Download what you need promptly.
Q: A carousel with five photos — is that five charges?
A: No. One post is one dataset row and one media-returned charge, however many photos or videos are inside its mediaList.
Q: Am I charged when the Actor errors out? A: No. Error rows and empty runs are not charged events. Both error messages say so explicitly.
Q: Can I use this Actor commercially? A: Yes. The data is public. You remain responsible for copyright and data-protection compliance — see Data Sources and Legality.
Q: How fresh is the data? A: It is fetched live at run time. Engagement counts are a snapshot from the moment of the run.
Q: Do I need an Instagram account, login, or proxy? A: No. There is nothing to configure — supply a username and run.
Contact & Support
Questions? Need Help? We're Here for You!
For questions, technical support, feature requests, or general inquiries about the Instagram Media Scraper, reach out to us:
Email: contact@endspec.net
Response Time: We respond within 24 hours during business days.
What to Include in Your Inquiry:
- Your question or issue description
- The Apify run ID (if applicable)
- The input parameters used
- Any error message or status you saw in the dataset
- What you expected to receive instead
You can also report issues through the Issues tab on this Actor's page in Apify Store.
We are committed to providing excellent support and helping you get the most out of this Actor — whether you are integrating it into a workflow, troubleshooting a run, or exploring new use cases.
Related Actors
- Instagram Media Scraper – this Actor: recent public media for one Instagram profile.
- YouTube Channel Contacts Extractor – emails, socials, and website URLs for any YouTube channel.
- LinkedIn Instant Profile Search – find LinkedIn profiles by name, job title, or company.
Last Updated: July 2026