Instagram Profile Scraper: Posts and Reels avatar

Instagram Profile Scraper: Posts and Reels

Pricing

from $10.64 / 1,000 post returneds

Go to Apify Store
Instagram Profile Scraper: Posts and Reels

Instagram Profile Scraper: Posts and Reels

Instagram scraper for public profiles, no login. Give it usernames, get one row per post or reel: caption, hashtags, mentions, likes, comments, views, video length, video file, audio track, tagged accounts and location, plus an optional profile row. A profile that returns nothing is free.

Pricing

from $10.64 / 1,000 post returneds

Rating

0.0

(0)

Developer

Adrian Voss

Adrian Voss

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

3 days ago

Last modified

Share

One row per Instagram post, without a login. Give this actor a list of public usernames and it returns a flat row per post or reel: caption, hashtags, mentions, likes, comments, views, video length, the video file, the audio track, tagged accounts and location, plus an optional profile row with followers, bio and bio link. Field names are frozen and designed to drop straight into a Sheet, a Clay table, an n8n workflow or an AI agent with no post-processing.

Who it's for

A social analyst benchmarking a brand against three competitors wants the same twenty columns for every account, every week, in one table - not four browser tabs and a screenshot. An influencer- marketing team vetting creators before a deal needs the last dozen posts with real engagement numbers attached, so they can see whether a 200k-follower account actually gets 200 likes. A content team auditing its own hashtag strategy wants every hashtag and @mention its account has used recently, counted. And an agent or workflow that already knows which accounts matter needs a single HTTP call that answers "what did they post, and how did it do" in a shape it can read.

All four get the same thing here: usernames in, one flat row per post out, priced per post returned, with a profile row that costs nothing.

Why this one

  • The row shape is the product. Every field name is frozen: shortcode, posted_at, like_count, view_count, hashtags, audio, tagged_users, carousel_children. They do not change between runs, which is what makes this safe to point a scheduled workflow or an AI agent at. The full untouched source objects come along in raw for anything this actor does not surface as its own column.
  • Listing and downloading are separate jobs, and this one is listing. You get video_url, display_url and thumbnail_url as direct links. Nothing is downloaded, nothing is re-hosted, and you are not billed for bytes you did not want. Fetch the media yourself, for the posts you actually chose.
  • Two detail levels, priced apart. "Basic" reads only each post's embed page; "Full" also reads the post's own page for the exact posting time, tagged accounts, location and collaborators, and moves about 1.7x the data. They are billed as two separate events rather than one blended price, so a "Basic" run is genuinely cheaper and not just faster.
  • A cheap date filter that runs before you pay. "Only posts since" is answered from the free listing data, so posts older than your cut-off are dropped before any per-post request is made. They cost you nothing.
  • Honest about the logged-out ceiling. Instagram shows a signed-out visitor 12 posts per tab and no more. This actor reads both the posts grid and the reels tab and merges them, which is the most anyone gets without an account, and it tells you in the run's summary when your limit could not be filled. See "What you can and cannot get without logging in" below.
  • Never charged for a miss. A username that does not exist, a private account, a profile whose posts were all filtered out - each returns a row explaining what happened, and costs nothing.

What you get

One row per post by default (turn off "Expand rows" in Advanced to get one row per username instead, with everything nested under posts). With "Include a profile row" on, each username also gets one kind: "profile" row, which is never billed.

FieldTypeRowDescription
querytextbothThe username you submitted, echoed back
foundbooleanbothtrue if Instagram served a readable profile
statustextbothOK on a match; NOT_FOUND, BLOCKED or BAD_FORMAT on a miss
messagetextbothPlain-English reason for a miss - only present when found is false
usernametextbothThe account, normalised to a bare lowercase name
postCountnumberbothPost rows returned and billed for this username
truncatedbooleanbothtrue if more posts matched than your limit allowed
detailCountnumberbothPost rows that got the Full-detail second request, and were charged the surcharge
kindtextbothprofile or post
ig_user_idtextbothInstagram's own numeric user ID
full_nametextprofileDisplay name
biotextprofileBiography text
followersnumberprofileExact follower count
followingnumberprofileExact following count
posts_countnumberprofileTotal posts on the account, as Instagram displays it. Instagram itself rounds this on large accounts ("30K"), so treat it as approximate above a few thousand
profile_pic_urltext (link)profileProfile picture
is_verifiedbooleanprofileVerified badge
external_urltext (link)profileThe link in the bio
shortcodetextpostInstagram's own post code
urltext (link)postPermanent post link (/reel/ for reels, /p/ otherwise)
typetextpostreel, image or carousel
posted_atdatepostPosting time, UTC. Exact in "Full" detail, day-precision in "Basic"
captiontextpostFull caption text
hashtagsarraypostEvery hashtag, lowercased, without the #
mentionsarraypostEvery @mention, lowercased, without the @
like_countnumberpostLikes
comment_countnumberpostComments
view_countnumberpostViews. Videos and reels only - an image has none, and the field is empty rather than zero
duration_snumberpostVideo length in seconds
widthnumberpostMedia width in pixels
heightnumberpostMedia height in pixels
display_urltext (link)postFull-size still
video_urltext (link)postDirect .mp4 link. Empty for images
thumbnail_urltext (link)postThumbnail still
carousel_childrenarraypostOne {type, display_url, video_url} per slide, for carousels
audioobjectpost{title, artist, is_original} for reels with an attributed track
tagged_usersarraypostUsernames tagged in the media. "Full" detail only
locationobjectpost{id, name} when the post has one. "Full" detail only
is_paid_partnershipbooleanpostAlways false - see the caveat below
coauthorsarraypostCollaborator usernames. "Full" detail only
detailFetchedbooleanposttrue if this post's own page was read and parsed. Only these rows are charged the Full-detail surcharge
rawobjectpostThe untouched source objects: {stub, embed, post}
scrapedAtdatebothWhen this row was fetched

Deselect any field in the Input tab's column picker to drop it from every row - query, found, status, message and scrapedAt always stay.

What you can and cannot get without logging in

This actor never logs in, never uses a cookie or a session token, and has no input field that could accept one. That is a deliberate design choice, and it has hard consequences worth knowing before you buy:

  • About 12 posts per tab. Instagram serves a signed-out visitor exactly 12 posts from the posts grid and 12 from the reels tab, and refuses every request for the next page. This actor reads both tabs and merges them, so a typical account yields somewhere between 12 and 24 unique posts. On one test account the two tabs shared 11 of 12 posts and produced 13 unique ones. No setting raises this. When your "Most posts to return" is larger than what the account exposes, the run's summary record says so explicitly rather than quietly handing you fewer rows.
  • is_paid_partnership is always false. The field does not exist anywhere in what Instagram serves a logged-out visitor - not merely absent on some posts, absent from the data entirely. It is kept as a column so the row shape stays stable for callers that expect it, but it carries no information. Do not filter on it.
  • view_count is videos only. Images and carousels of images have no view count at all, so the field is empty for them rather than 0.
  • No comment text. Comment counts are available; the comments themselves are behind a login.
  • tagged_users, location and coauthors need "Full" detail. They live only on each post's own page, which is the more expensive of the two per-post requests.
  • Private accounts return a miss, not an error. A private profile comes back with found: false, status: NOT_FOUND and a message saying so.

Pricing

  • Post returned: $14 per 1,000 posts
  • Full detail added: $12 per 1,000 posts

Plus a $0.00005 start fee per run. Each event above is billed independently, only when it actually returns data — misses (found:false) are never charged.

Billing is per post, in two parts:

  • post fires once for every post row delivered. Profile rows are never billed - only kind: "post" rows count toward postCount.
  • post-detail is a surcharge that fires only in "Full" detail, and only for posts whose second request actually came back with data. A "Basic" run never fires it at all.

The surcharge exists because the two modes genuinely cost different amounts to run: "Full" reads a second page per post and moves about 1.7x the data. Charging one blended price would make every "Basic" run subsidise the "Full" ones, so the modes are priced apart instead.

A username that returns no posts (private, nonexistent, or everything filtered out by your date or media-type settings) costs nothing beyond the flat run fee. A detail request that fails or comes back empty is not charged either - you still get the row, built from what the cheaper sources gave.

How to use

  1. In the Apify Console. Open the actor page and click Start — the usernames field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
  2. Via the API. Call it directly with a POST request — no Console needed once you have an API token:
    curl "https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"usernames":["instagram","natgeo"]}'
  3. On a schedule. Save this actor as an Apify Task with the input you want, then add a Schedule (hourly, daily, weekly) so it runs on its own — no server of your own required.
  1. Set "How much detail per post" to Basic if you only need engagement, media links and the audio track - it skips a whole request per post and is not charged the detail surcharge.
  2. Set "Only posts since" to a date when you are running the same accounts on a schedule: older posts are dropped before any per-post request, so a weekly run pays only for the week.

Input

{
"usernames": [
"instagram",
"natgeo"
]
}

One Instagram username per line. @name, a bare name, or a full profile link all work. No login and no cookies: this reads only what a signed-out visitor can see. Accepted formats: instagram, @natgeo, https://www.instagram.com/nasa/.

SettingWhat it does
Most posts to return per profileYour budget control, since you pay per post. The real ceiling is about 12 to 24 per profile whatever you set - see the logged-out section above
Only posts sinceA date like 2026-08-01. Older posts are dropped for free, before any per-post request
Which media to collectAll merges the posts grid and the reels tab. Reels only reads the reels tab, which is the only place some reels appear. Posts only skips the reels tab and is the cheapest
Include a profile rowAdds one unbilled row per username with followers, bio, bio link and verified status
How much detail per postFull adds exact posting time, tagged accounts, location and collaborators, and is charged a per-post surcharge. Basic skips both

Sample output

queryfoundstatususernamepostCounttruncateddetailCountpostskindig_user_idfull_namebiofollowersfollowingposts_countprofile_pic_urlis_verifiedexternal_urlshortcodeurltypeposted_atcaptionhashtagsmentionslike_countcomment_countview_countduration_swidthheightdisplay_urlvideo_urlthumbnail_urlcarousel_childrenaudiotagged_userslocationis_paid_partnershipcoauthorsdetailFetchedrawscrapedAt
instagramtrueOK<all rows found (full list)><row type (profile or post)><display name (profile row)><bio (profile row)><followers (profile row)><following (profile row)><total posts on the account (profile row)><profile picture (profile row)><verified (profile row)><bio link (profile row)>
<views (videos only)>1970-01-01T00:00:00.000Z

A username that returns nothing comes back as a row with "found": false and is never charged.

Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

curl "https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
-X POST \
-H "Content-Type: application/json" \
-d '{"usernames":["instagram","natgeo"]}'

n8n. Add an HTTP Request node: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body Content Type JSON, JSON Body {"usernames":["instagram","natgeo"]} (swap in an expression from an earlier node for a real value).

Clay. Add an "HTTP API" column: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body {"usernames":["{{profile}}"]}, mapping the row's profile into the usernames array.

MCP. In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Instagram Scraper API: Profile Posts & Reels" — the agent will find and run this actor.

The run also writes a summary to its OUTPUT key-value record:

{requested, returned, errors[], caps[]}
. errors lists every username that missed and why; caps names any profile where Instagram's logged-out limit stopped the run short of your requested count. A workflow can read that one record instead of scanning the whole dataset to find out whether the run did what you asked.

Tips

  • Run competitor accounts as one input list rather than one run each - the profile rows are free, and one run gives you a single table to pivot on.
  • Use Posts only when you are auditing captions and hashtags: reels carry the same caption data and skipping the reels tab removes a whole page load per username.
  • Use Reels only when you are studying short-form performance. The reels tab is the only place Instagram exposes reels that have been hidden from the main grid, and it carries play counts inline.
  • Keep raw selected while you are building a workflow and deselect it once your mapping is settled - it is by far the largest field in a row.
  • Leave the proxy on Residential. Instagram rate-limits Apify's shared datacenter IPs on this route, and a REQUEST_FAILED or BLOCKED row is usually that. Lowering "Max concurrency" in Advanced helps too.
  • Do not build a report that needs more than about two dozen posts per account. That is not a limit of this actor, it is Instagram's, and no scraper without a logged-in session gets past it.

vs. alternatives

What it costsWhat you getTrade-off
This actor (ig-profile-scraper)Per post returned, plus a surcharge per post in Full detail; nothing for a miss or for the profile rowOne frozen, flat row per post or reel - caption, hashtags, mentions, likes, comments, views, duration, dimensions, media links, audio, tagged accounts, location - plus the raw source objects and an optional free profile rowAbout 12 to 24 posts per profile, because it never logs in. is_paid_partnership is always false and comment text is unavailable. It lists media, it does not download it
apify/instagram-scraperAbout $2.70 per 1,000 postsThe Store leader by a wide margin: 4.73 stars over 590 reviews, far broader input surface (hashtags, locations, search), and deeper historyIf you need hashtag or location scraping, or more posts per account than a logged-out view allows, use it. This one is narrower on purpose: profiles only, a fixed row shape aimed at agents and tables, and the raw payload passed through
apify/instagram-reel-scraperAbout $2.60 per 1,000 reelsReels specifically, from the same publisherReels only. This actor covers reels as one of three media types in the same row shape, so a mixed account does not need two runs and two schemas
Instagram's own Graph APIFree, with a Meta appOfficial, complete, and the only way to get insights on accounts you controlIt only reaches accounts you own or have been granted access to, and requires app review and a Business or Creator account. This reads public profiles you do not control, which the Graph API cannot do
Doing it yourselfYour time, plus the maintenanceThe same dataThe header set that makes these pages serve data at all, the double-encoded embed payload, the two tabs that each carry half the fields, and the fact that the pagination endpoint returns a null user logged out. That research is what this actor absorbs

Prices for third-party tools are their published list prices as of September 2026 and are not tracked here - check the vendor before relying on the comparison.

Data & privacy

This actor reads public Instagram profiles that anyone can see without logging in. It does not log in, does not use cookies or session tokens, does not solve CAPTCHAs, and does not reveal hidden or private content. Private accounts return a miss.

The rows include usernames, display names, biographies and profile pictures, which are personal data about real people. If you process them you are the controller of that processing and need your own lawful basis for it - this actor gives you the data, not permission to use it. Do not use it to build profiles of private individuals. Not affiliated with Instagram or Meta.

Is this allowed? It collects the same public post data your browser shows a signed-out visitor, for the accounts you choose, at a modest request rate. It is built for analysis and monitoring of public accounts, not for copying Instagram. Check that your use fits Instagram's terms and your local law.

FAQ

Why do I only get about 12 posts per account? Because nobody logged in. Instagram serves a signed-out visitor 12 posts from the profile grid and 12 from the reels tab, and answers every request for a further page with an empty user. This actor reads both tabs and merges them, which is the maximum available without an account. Raising "Most posts to return" will not change it; the run's OUTPUT summary tells you when this happened.

Why is is_paid_partnership always false? The field is not present in anything Instagram serves logged out. It is kept as a column so the row shape never changes for callers that expect it, but it carries no information and should not be filtered on.

Why is view_count empty on some rows? Those rows are images or image carousels, which have no view count. Videos and reels have one. Empty is deliberate - reporting 0 would look like a real measurement.

What is the difference between Basic and Full detail? Basic reads one page per post (the embed) and gives caption, hashtags, mentions, likes, comments, views, video length, dimensions, the media links and the audio track, with the posting date to the day. Full reads a second page per post and adds the exact posting time, tagged accounts, location and collaborators, at about 1.7x the data transfer and a per-post surcharge. The detailFetched column tells you, row by row, which posts actually got the second request.

Does it download the videos and images? No. You get direct links (video_url, display_url, thumbnail_url, and per-slide links for carousels) and fetch what you actually want yourself. Listing and downloading are kept as separate jobs so you are never billed for bytes you did not ask for.

Can I get comments? Comment counts, yes. Comment text, no - it is behind a login, and this actor does not log in.

Do I need a proxy? Residential is the default and you should leave it on. Instagram served these pages happily to a plain request from an ordinary connection during the build probe, but it answered Apify's shared datacenter IPs with HTTP 429 on the first live run: one username was rescued by session rotation on its third attempt and the other exhausted its retries. The same call through Residential returned every row with no errors. That is why the price is set against residential data transfer rather than the cheaper datacenter rate.

Can an AI agent call this directly? Yes. It is registered on the Apify MCP server, and the field names are frozen specifically so an agent's mapping does not break between runs. The OUTPUT summary record gives an agent a single place to check whether the run was complete.