Instagram Scraper: Profiles, Posts, Likes & Reels, No Login avatar

Instagram Scraper: Profiles, Posts, Likes & Reels, No Login

Pricing

from $0.24 / 1,000 post with likes and comments

Go to Apify Store
Instagram Scraper: Profiles, Posts, Likes & Reels, No Login

Instagram Scraper: Profiles, Posts, Likes & Reels, No Login

Instagram scraper with likes and comments on every post, up to 6x cheaper per post than the official Apify Instagram scrapers. Profiles with engagement rate and full post history, reels with plays, exact timestamps, tags and locations. No login, no cookies. From $0.28 per 1,000 posts.

Pricing

from $0.24 / 1,000 post with likes and comments

Rating

0.0

(0)

Developer

Scrapegeist

Scrapegeist

Maintained by Community

Actor stats

0

Bookmarked

373

Total users

11

Monthly active users

8 days ago

Last modified

Share

Instagram Scraper: Profiles, Posts, Likes and Reels, No Login

Scrape public Instagram profiles with their full post history, not only the latest 12 posts, for a fraction of the official actors' price. One profile row per account with stats, bio links and an engagement rate, one row per post with caption, type, thumbnail, exact timestamp, likes and comment count on every post, and plays on every reel. Views and comment texts when you ask for them. Hashtag pages and single post URLs work too. No login, no cookies, no browser.

Posts table of a run over organisation accounts at detail level: thumbnail, account, timestamp, type, caption, likes, comments


Why this one

The same public data as the official Apify Instagram actors, with likes and comments on every post from $0.24 per 1,000 posts, an engagement rate on every profile, and no limit of 12 posts.

Prices per 1,000 rows, Free / Bronze / Silver / Gold tier, as listed on 2026-10-10:

What you needThis actorOfficial Instagram Profile ScraperOfficial Instagram Post Scraper
A profile with its 12 newest posts, likes and comments included$1.40 / $1.27 / $1.13 / $1.00 (from 2026-10-26: $1.90 / $1.80 / $1.60 / $1.40)$2.60 / $2.30 / $2.00 / $1.60
Every further post, with likes and comments$0.28 / $0.27 / $0.25 / $0.24not available (12 posts at most)$1.70 / $1.50 / $1.30 / $1.00
A post with its visible comments$2.20 / $1.90 / $1.70 / $1.50$2.70 / $2.30 / $2.00 / $1.60 (post plus post details)
Plays of a reelincluded in the post row
Engagement rate per profileincluded in the profile row
Start feenonenonenone

Example, a Bronze (Starter plan) user: 1,000 profiles with their 50 newest posts each. Here: 1,000 profiles at $1.27 plus 38,000 further posts at $0.267 = $11.41 ($11.95 from 2026-10-26). With the official actors, the profile scraper stops at 12 posts, so the 50,000 posts come from the post scraper at $1.50 plus 1,000 profiles at $2.30 = $77.30.

What you also get here and not there: accounts that do not exist, private posts, duplicates and login walls are never billed; every failure is a skipped row you can join back to your input; all rows are flat, so a run of 5,000 posts exports to one CSV.

When the official actors are the better pick: you need the total post count of an account, business category or contact fields, music data, transcripts, video downloads, or more than about 14 comments per post. A logged-out reader cannot get those here (see Honest limits).

What you get in 30 seconds

Press Start with the prefilled input. It asks for two organisation accounts and nothing else:

{
"usernames": ["natgeo", "https://www.instagram.com/sfmoma/"]
}

The run gives you, per account:

  • one profile row: username, full name, biography, bio links, follower and following count, verified and private flags, profile picture, pronouns and highlight titles, plus an engagement rate, average likes, average comments and average plays of the 12 newest posts;
  • up to 100 post rows (maxPostsPerProfile, raise it to 5,000): caption, media type, product type, thumbnail, timestamp, post URL, the account that owns it, likes, comment count, exact timestamp, video URL, usertags, location, co-authors, pinned and paid-partnership flags, and play_count on reels. The first 12 posts are included in the profile price.

Every row carries scraped_at or a taken_at you can sort by, and discoveredVia, which says what brought the row into the dataset (input, profile:natgeo, hashtag:museum).

The dataset has three views in the Apify Console: Profiles, Posts and Skipped. Export any of them as JSON, CSV, Excel or XML.

Input kinds, with examples

You can mix all four in one run. Each has its own cap.

FieldWhat you pasteWhat comes back
usernamesnatgeo, @sfmoma, https://www.instagram.com/britishmuseum/One profile row plus its post history
postUrlshttps://www.instagram.com/p/DdG4RIxIPyf/, or the bare shortcode DdG4RIxIPyfOne detail row per post
hashtagsmuseum, #architectureAbout 60 post rows per tag (see Limits)
searchQueriesany keywordA skipped row. Keyword search needs a login (see Limits)
{
"usernames": ["natgeo", "sfmoma", "britishmuseum"],
"postUrls": ["https://www.instagram.com/p/DdG4RIxIPyf/"],
"hashtags": ["museum"],
"maxPostsPerProfile": 200,
"postDetail": "list",
"detailSample": 3
}

Handles that do not exist and invalid entries do not stop the run. They become free skipped rows with a reason you can filter on.

A private account is the one case that is not free: Instagram publishes its profile card to anyone, so you get a normal profile row with the name, follower and following count, biography and bio links, charged as a profile row, plus a free skipped row with the reason private_no_posts. The posts are not public, so none are delivered and the engagement fields stay null.

Sample output

The run's Output tab has one table per row type (Posts, Profiles, Skipped). Apify tables cannot filter rows, so every table lists every row, and a row of another type shows undefined in the columns it does not have. Download the JSON or CSV and filter on type for a clean per-type file.

Four real rows, from a run on organisation accounts. Long CDN URLs are shortened here with ...; the dataset carries them in full.

Profile row

{
"type": "profile",
"username": "natgeo",
"pk": "787132",
"url": "https://www.instagram.com/natgeo/",
"full_name": "National Geographic",
"biography": "Step into wonder and find your inner explorer with National Geographic 🌎",
"bio_links": [
{ "url": "http://visitstore.bio/natgeo", "title": null },
{ "url": "https://ngmdomsubs.nationalgeographic.com/servlet/OrdersGateway?cds_mag_code=NGM", "title": "Subscribe Here!" }
],
"external_url": "http://visitstore.bio/natgeo",
"follower_count": 268532277,
"following_count": 194,
"post_count": null,
"is_verified": true,
"is_private": false,
"profile_pic_url": "https://scontent.cdninstagram.com/v/t51.82787-19/683576066_...jpg",
"pronouns": [],
"highlights": ["Jaguar vs. Croc"],
"engagement_rate": 0.0003,
"avg_likes": 81761.6,
"avg_comments": 278.7,
"avg_video_views": null,
"avg_plays": 1113332.5,
"sample_size": 12,
"scraped_at": "2026-10-10T03:51:56.573Z",
"discoveredVia": "input"
}

Post row, list level (the default)

A profile timeline row carries the likes, the comment count and the exact timestamp at the list price. A reel also carries play_count, the number Instagram shows on its reels grid.

{
"type": "post",
"pk": "4004322549189214782",
"shortcode": "DeSNiSGgoI-",
"url": "https://www.instagram.com/p/DeSNiSGgoI-/",
"owner_username": "natgeo",
"owner_pk": "787132",
"media_type": "video",
"product_type": "clips",
"caption": "We could stay here a while. 🌿 ...",
"thumbnail_url": "https://scontent-lhr6-1.cdninstagram.com/v/t51.82787-15/8441...jpg",
"taken_at": "2026-10-09T19:01:07.942Z",
"detail_level": "list",
"discoveredVia": "profile:natgeo",
"like_count": 117972,
"comment_count": 393,
"taken_at_exact": "2026-10-09T19:01:40.000Z",
"video_url": "https://scontent-lhr6-2.cdninstagram.com/o1/v/t2/f2/m86/...mp4",
"usertags": [],
"location": null,
"co_authors": [],
"like_and_view_counts_disabled": false,
"play_count": 1067444,
"is_pinned": false,
"is_paid_partnership": false
}

Post row, engagement level

Adds view_count to a video: the legacy view count of its embed page, a smaller number than play_count. Likes and comments are already on every list row, so pick this level only when you need that view count. Posts that are not videos have no view count and stay list rows at the list price.

{
"type": "post",
"pk": "3947535193527528320",
"shortcode": "DbIdlf0j6uA",
"url": "https://www.instagram.com/p/DbIdlf0j6uA/",
"owner_username": "natgeo",
"owner_pk": "787132",
"media_type": "video",
"product_type": "clips",
"caption": "Describe Pompeii: Out of Time in 3 words? It's Now Streaming.",
"thumbnail_url": "https://scontent-tpe5-1.cdninstagram.com/v/t51.82787-15/752808040_...jpg",
"taken_at": "2026-07-23T10:34:47.618Z",
"detail_level": "engagement",
"discoveredVia": "profile:natgeo",
"like_count": 70603,
"comment_count": 408,
"view_count": 768770,
"taken_at_exact": null,
"video_url": "https://instagram.fkhh1-2.fna.fbcdn.net/o1/v/t2/f2/m86/...mp4",
"usertags": [],
"location": null,
"co_authors": [],
"comments": [],
"like_and_view_counts_disabled": false
}

Post row, detail level

Everything the post page itself carries: exact timestamp, usertags, co-authors, location and the visible comments. The post page publishes no view count, so view_count is null here. The one exception is a post URL run with minViews, where the embed page is read first and its view count is carried over into the detail row.

{
"type": "post",
"pk": "3983118388705688735",
"shortcode": "DdG4RIxIPyf",
"url": "https://www.instagram.com/p/DdG4RIxIPyf/",
"owner_username": "natgeo",
"owner_pk": "787132",
"media_type": "video",
"product_type": "clips",
"caption": "Presented by @Rolex. Welcome to Africa 🌍 ...",
"thumbnail_url": "https://scontent-tpe5-1.cdninstagram.com/v/t51.82787-15/802503979_...jpg",
"taken_at": "2026-09-10T12:52:15.035Z",
"detail_level": "detail",
"discoveredVia": "input",
"like_count": 70358,
"comment_count": 408,
"view_count": null,
"taken_at_exact": "2026-09-10T13:00:14.000Z",
"video_url": "https://scontent-tpe5-1.cdninstagram.com/o1/v/t2/f2/m86/...mp4",
"usertags": ["rolex"],
"location": null,
"co_authors": ["rolex"],
"comments": [
{ "pk": "18126357995498209", "username": "...", "text": "...", "created_at": "2026-09-17T13:43:16.000Z", "like_count": 0 }
],
"like_and_view_counts_disabled": false
}

And a skipped row, for anything that could not be delivered:

{ "type": "skipped", "input": "sfmoma_does_not_exist", "reason": "not_found", "message": "profile: null_user" }

reason is one of not_found, private_no_posts, login_wall, rate_limited, identity_mismatch, invalid_input, not_available_logged_out, error.

What it costs

This actor bills per row (pay per event). The current prices are in the pricing box at the top of this page, and the comparison above puts them next to the official actors. There is no actor start fee: a run that delivers nothing costs nothing. Compute and the residential proxy are included in the row prices and are never billed on top.

Row typeCharge eventWhat it covers
Profile rowprofile-scrapedThe profile row with its engagement rate and the first 12 posts of that account, delivered as free list rows with likes and comments.
Timeline post row, list levelpost-scrapedA post from a profile timeline beyond the first 12. Caption, type, product type, thumbnail, timestamp, post URL, the owner, likes, comment count, exact timestamp, video URL, usertags, location, co-authors, and plays on a reel.
Hashtag row, list levelgrid-post-scrapedThe same fields, off a hashtag grid, plus the view count that grid printed for free. Its own event, because one grid page delivers many rows and costs less per row than a timeline page.
Post row, engagement levelpost-engagement-scrapedA video with likes, comment count and the view count of its embed page. From a timeline, a hashtag grid, or postUrls.
Post row, detail levelpost-detail-scrapedA post with likes, comment count, exact timestamp, video URL, usertags, location and the visible comments. From a timeline, a hashtag grid, or postUrls.

Charged by the level you asked for and by where the row came from, never by what arrived. A timeline list row is post-scraped, a hashtag list row is grid-post-scraped, an engagement row is post-engagement-scraped and a detail row is post-detail-scraped. With the default postDetail: list, hashtag rows stay list rows at the grid price, even though their grid hands over a view count for free. A number the page volunteered does not raise your bill. Ask for postDetail: detail and those rows become detail rows, with the detail price.

What you are never charged for

  • Skipped rows. Every skipped row is free, whatever the reason.
  • Duplicates. A post is delivered once per run and billed once, even when two hashtags and a profile all contain it.
  • Accounts that do not exist, and every other invalid input.
  • Login walls and rate limits. If Instagram gates a request, you get a skipped row, not a bill.
  • Rows dropped by minViews. The fetch that read the view count is our cost, not yours.
  • Empty runs. No rows, no charge, and no start fee.
  • The first 12 posts of every profile, with their likes and comments. They come with the profile row, as list rows, and the engagement rate is computed from them at no extra request.
  • Comments. They are part of a detail row and are never billed separately.
  • Any row after your charge limit is reached. When Apify reports that the limit is hit, the run stops fetching and hands you the page-1 posts the profile row already paid for, as free list rows.

maxTotalPosts is a hard ceiling on billed post rows: the four post events together. It is checked before each fetch, not after the row lands, so the number you type is the most post rows you can be charged for in that run. If your Apify charge limit is lower, the actor lowers maxTotalPosts to match it and says so in the log.

Honest limits

Everything below was measured on 2026-09-18 from a logged-out reader. This actor does not log in, so these are the real edges of the public surface, not settings you can raise.

Hashtag pages give about 60 posts per tag. That is where Instagram's public tag page ends: it keeps paging until it reports that there is no next page, which happened after 60 posts for tedx and 64 for museum. A higher maxResultsPerHashtag gives you no more. The logged-out tag grid also contains reels only, no photo posts: all 172 posts measured across two tags were videos. For photo posts from an account, scrape the account itself through usernames.

Location pages need a login. Since 2026-10-09 Instagram answers both location queries with "Rate limit exceeded" to every logged-out reader, on every IP, and the location page carries no posts. So the actor has no locations input any more. A run that still sends one gets a free skipped row with the reason not_available_logged_out. The location field on each post row is not affected.

View counts exist for videos and reels only. Instagram publishes no view count for an image or a carousel post to a logged-out reader, on any route. Those posts have view_count: null and never pass minViews. Profile timeline reels carry play_count, the number Instagram shows on the reels grid. view_count holds the video view count of the embed page at the engagement level, and the play count on hashtag rows. Plays and views are two metrics: one natgeo reel had 939,456 plays and 168,398 views on the same day.

Likes and comments on timeline rows come from the web timeline. That is the route Instagram's own site uses, and it answers a logged-out reader with the counts. If it ever stops doing so, the actor falls back to the logged-out posts tab on its own: the rows still arrive, but list rows then carry no likes or comments until the route is back. The run log says so with [ALERT][IG_FEED].

A detail row carries up to about 14 comments. The post page prints them inline and says there is a next page, but every pagination variable is dropped and the same 14 come back (measured on a post with 408 comments). comment_count is the real total, so you always see how many a post has.

Keyword search needs a login. Use hashtags for discovery. A keyword in searchQueries comes back as a skipped row with the reason not_available_logged_out and is never billed.

Stories and highlight contents need a login. Highlight titles are on the profile row. The story routes answer a login page, and the highlights query answers an empty list to a logged-out reader, so there is nothing to deliver.

Not served to a logged-out reader at all, so not returned: business contact fields (email, phone, WhatsApp, category), follower and following lists, and the total post count (post_count is null).

A reel pinned to the top of a grid can come without plays. Plays are read from the reels tab, newest first, as far back as the timeline goes. A pinned reel that is older than that range keeps play_count off its row.

Hidden counts come back as null, never 0. When an account hides its like and view counts, the row carries like_and_view_counts_disabled: true and null counts, so you can tell "hidden" from "zero".

How to

Get the full post history, not 12 posts

Set maxPostsPerProfile to how far back you want to go, up to 5,000. The actor pages the profile timeline 10 posts at a time and stops at your number, at maxTotalPosts, or when the account runs out of posts.

{ "usernames": ["sfmoma"], "maxPostsPerProfile": 2000 }

Get an engagement rate per profile

It is on by default and costs nothing extra. The profile row carries engagement_rate, avg_likes, avg_comments, avg_plays and sample_size, computed from the 12 newest posts the profile price already covers. The rate is (avg_likes + avg_comments) / follower_count, rounded to four decimals, and is null when the sample has no counts or the follower count is hidden. Set detailSample to a number from 1 to 12 to compute it from fewer, newer posts. avg_video_views needs the embed view count, so it is filled only at postDetail: engagement.

Only posts since a date

{ "usernames": ["britishmuseum"], "since": "2026-01-01" }

Profile pagination stops once the posts are older than the date. Hashtag grids are ranked, not sorted by date, so there since drops the older posts out of the grid and the actor keeps reading the rest.

Only videos above a view threshold

{ "usernames": ["natgeo"], "minViews": 500000 }

A match from a profile is delivered as an engagement row (the embed page is the only source of its view count). A match from a hashtag grid stays at the level you asked for, because the grid prints the count. A match from postUrls is a detail row, because a post URL is always fetched at detail level. Posts below the threshold are not delivered and not billed. Image and carousel posts have no view count anywhere, so they are dropped without a request. On a hashtag page the view count is already in the grid, so the filter there costs nothing extra.

Scrape hashtags

{ "hashtags": ["museum", "architecture"], "maxResultsPerHashtag": 60 }

Rows arrive with discoveredVia: "hashtag:museum" and a view_count the grid printed, at the plain list price. Add "postDetail": "detail" to fetch each post on top for likes, the exact timestamp, usertags, the location and the comments, at the detail price.

Use your own proxy

The default is Apify Proxy with the RESIDENTIAL group, which needs no setup. Instagram gates datacenter IPs on the post routes, so residential is required. Your own proxy URLs are accepted in the proxy editor.

{ "proxyConfiguration": { "useApifyProxy": false, "proxyUrls": ["http://user:pass@my-proxy:8000"] } }

API and integrations

Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("scrapegeist/instagram-profile-posts-scraper").call(run_input={
"usernames": ["natgeo", "sfmoma"],
"maxPostsPerProfile": 100,
"detailSample": 3,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["type"] == "profile":
print(item["username"], item["follower_count"], item["engagement_rate"])

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('scrapegeist/instagram-profile-posts-scraper').call({
usernames: ['natgeo', 'sfmoma'],
maxPostsPerProfile: 100,
detailSample: 3,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const posts = items.filter((i) => i.type === 'post');
console.log(`${posts.length} posts`);

cURL

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

n8n, Make, Zapier and MCP

The actor is a normal Apify actor, so every Apify integration drives it without extra work. In n8n use the Apify node, pick this actor and paste the JSON input. In Make use the Apify "Run an Actor" module and map the dataset items into the next step. In Zapier use the Apify app with the "Run Actor" action. Through the Apify MCP server the actor is a callable tool, so an assistant can start a run and read the dataset back without any code. You can also let Apify push the dataset straight into Google Sheets, Airtable, Slack or a webhook after every run.

For AI agents

Call this actor when you need public Instagram profile or post data and you have no Instagram account to use.

  • Actor id: scrapegeist/instagram-profile-posts-scraper.
  • Minimal input: {"usernames": ["natgeo"]}. Nothing else is required.
  • Output is a flat array of objects. Branch on the type field: profile, post, skipped. Do not assume all rows have the same shape.
  • Post rows have a detail_level field: list, engagement or detail. like_count and comment_count are on profile timeline rows at every level, and on hashtag rows where the grid printed them. play_count is on reels. view_count is filled at engagement level and on hashtag rows.
  • A null count means "not published", never "zero". Check like_and_view_counts_disabled before you report a zero.
  • Never retry a skipped row with reason not_found, private_no_posts or not_available_logged_out. Those do not change on a second run. A login_wall or rate_limited row is worth one retry later.
  • Bound spend with maxTotalPosts and maxPostsPerProfile before you start a run over a long list.

Switching from the official Instagram actor

The field names differ. This map covers the ones people use most:

Official actorThis actorNote
usernameusernameSame
fullNamefull_name
biographybiographySame
followersCountfollower_count
followsCountfollowing_count
postsCountnot availableInstagram does not serve the total to a logged-out reader. It is post_count: null.
verifiedis_verified
privateis_private
profilePicUrlprofile_pic_url
externalUrlexternal_urlbio_links carries all of them, not only the first
latestPosts[]separate post rowsOne row per post in the same dataset, with type: "post" and discoveredVia: "profile:<username>". Filter on that instead of reading a nested array, and you are no longer capped at 12.
latestPosts[].captioncaption
latestPosts[].likesCountlike_countOn every profile timeline row, at the list price
latestPosts[].commentsCountcomment_countSame
latestPosts[].videoViewCountview_countVideos only, at postDetail: engagement
latestPosts[].videoPlayCountplay_countReels, on every profile timeline row
latestPosts[].isPinnedis_pinned
latestPosts[].paidPartnershipis_paid_partnership
latestPosts[].taggedUsers, coauthorProducersusertags, co_authorsUsernames
latestPosts[].timestamptaken_at, taken_at_exacttaken_at is derived from the post id and is on every row. taken_at_exact is the value Instagram prints, on every profile timeline row and on detail rows.
latestPosts[].urlurl
latestPosts[].typemedia_typeimage, video, carousel or unknown

Three behaviour differences worth knowing before you migrate:

  1. The profile row and the posts are separate rows, not one nested document. That keeps a run of 5,000 posts flat and exportable to CSV.
  2. Failures are rows, not gaps. A handle that does not exist produces a skipped row you can join back to your input list.
  3. Nothing is guessed. A value Instagram does not publish is null, never 0 and never an estimate.

Input reference

FieldTypeDefaultWhat it does
usernamesstring[]["natgeo", "https://www.instagram.com/sfmoma/"] (prefill)Handles, @handles or profile URLs. One profile row per account plus its posts.
hashtagsstring[][]Hashtags with or without #. About 60 posts per tag, reels only.
searchQueriesstring[][]Keywords. Needs a login, so each one returns a free skipped row.
postUrlsstring[][]Post or reel URLs or shortcodes. Each becomes a detail row, whatever postDetail says. With minViews, a view count is added from the embed page (videos and reels only).
maxPostsPerProfileinteger1000 to 5,000. How far back to page each profile. 0 returns the profile row only.
maxTotalPostsinteger10000Hard cap on billed post rows across all inputs (the four post events together), checked before each fetch.
maxResultsPerHashtaginteger100Cap per hashtag. The public page ends before 100 anyway.
postDetailenumlistlist, engagement or detail. See the level table below.
detailSampleinteger0How many of the newest posts the engagement rate is computed from. 0, the default, uses all 12 posts the profile price covers, at no extra request.
sincestring (date)noneISO date, for example 2026-01-01. Paging stops at posts older than this.
minViewsintegernoneKeep only posts with at least this many views. Videos and reels only. Dropped rows are not billed.
includeCommentsbooleantruePut the visible comments (about 14) on each detail row. Never billed separately.
proxyConfigurationobjectApify Proxy, RESIDENTIALResidential is required for post pages. Your own proxy URLs are accepted.
concurrencyinteger41 to 8 parallel sessions. Each keeps one proxy IP and one cookie jar.

The three post detail levels

LevelExtra request per postFields it addsBilled as
listnoneCaption, media type, product type, thumbnail, taken_at, URL, owner. On a profile timeline also like_count, comment_count, taken_at_exact, video_url, usertags, location, co_authors, carousel_media, is_pinned, is_paid_partnership, and play_count on reelspost-scraped from a timeline, grid-post-scraped off a hashtag grid
engagementabout 55 KB, videos onlyview_count (embed view count), video_urlpost-engagement-scraped
detailabout 88 KBcomments (about 14 visible comments), plus everything a timeline list row has. No view count.post-detail-scraped

Use list for almost everything: profile timeline rows already carry likes, comments, plays and the post metadata. Use engagement when you need the embed view count of videos, and detail when you need the comment texts.

FAQ

Do I need an Instagram login, cookies or a session id? No. The actor reads only what a logged-out visitor sees. There is nothing to configure and nothing of yours to put at risk.

Why do I only get about 60 posts from a hashtag? Because that is where Instagram's public tag page ends. The actor pages until the page itself says there is no next page. Raising maxResultsPerHashtag changes nothing.

Can I get more than 14 comments on a post? No, not without a login. The post page prints about 14 and then drops every pagination attempt. comment_count still gives you the real total.

What is the difference between play_count and view_count? play_count is the number Instagram shows on a reels grid, read from the reels tab at no extra cost. view_count is the older view count of a video's embed page, read at postDetail: engagement. For the same reel the play count is usually several times larger. Compare a field only with itself.

Why is view_count null on an image post? Instagram publishes a view count for videos and reels only. There is no logged-out route that carries one for an image or a carousel post, so the field stays null rather than guessing.

Why is post_count null? The total post count of an account is not served to a logged-out reader. The actor returns null instead of an estimate.

Can I search Instagram by keyword? No. Keyword search needs a login. Use hashtags for discovery.

Can I get stories or highlight media? No. Both need a login. The profile row carries the highlight titles, which is everything the public surface exposes.

Can I get an email or a phone number from a profile? No. Business contact fields are behind the login. If you need contact data for organisations, the Facebook Pages Contact Scraper reads the public contact block of a Facebook Page instead.

What happens with private accounts? You get the profile row, charged as a profile row, because Instagram shows the profile card of a private account to anyone: name, follower and following count, biography, bio links, verified and private flags. On top of it comes a free skipped row with the reason private_no_posts. What you do not get is the posts, because they are not public, so the engagement rate and the averages stay null. Put another way, you pay for a private account only when there was something to deliver, and never for its posts.

Why a residential proxy? Instagram gates datacenter IPs on the post routes. The default RESIDENTIAL group of Apify Proxy is enough and needs no setup.

A run failed. What now? Re-run it. Failed and skipped rows are never billed, so a failed run costs you nothing. When Instagram gates the whole proxy pool, the actor stops early and says so in the status message instead of burning your budget on walls.

Can I run this over thousands of accounts? Yes. Use maxTotalPosts to bound spend, and Apify's scheduler to split the work. The actor streams rows to the dataset as it goes, so a stopped run keeps everything it already delivered.

This actor reads only what a logged-out visitor sees on public Instagram pages. No login, no cookies, no account, no CAPTCHA solving, no circumvention of a technical access control. The posts of a private account are not public, so they are never read: only the profile card Instagram itself shows to every visitor.

The output is research, enrichment and verification data. It is not an opt-in marketing list, and it does not become one because it is in a dataset. If you contact anyone in it, you are the controller for that processing, and you are responsible for the rules that apply to you: GDPR, UWG § 7 in Germany, CAN-SPAM in the US, PECR in the UK, and the equivalents elsewhere. Check before you send.

Every row carries discoveredVia and a timestamp, so you can show where a record came from and when.

Data notice for account owners

What is collected: the public profile of an Instagram account (username, name, biography, bio links, follower and following count, verified and private flags, profile picture, pronouns, highlight titles) and its public posts (caption, media type, thumbnail, timestamp, and where public, likes, comment count, view count, usertags, location and the comments the post page prints). Nothing behind the login is read. Follower and following lists, stories, direct messages and the posts of private accounts are never read. Of a private account only the public profile card is collected, which is what Instagram shows to any logged-out visitor.

Who processes it and why: the customer who starts a run chooses the input and receives the output, and is the controller for any use of it. The operator of this actor processes the data only to deliver it to that customer and keeps no database beyond the run storage the customer configures. Legal basis for the collection is legitimate interest (GDPR Art. 6(1)(f)): the data is published by the account owner for a public audience, it is limited to what a logged-out visitor sees, and no contact details are collected.

Your rights: to have your account excluded from future runs, to learn whether it was included, or to have data erased on the operator's side, open an issue on this actor's Store page (the Issues tab) with the subject "Exclude my account" and the profile URL. Exclusions are applied within 30 days and checked on every run. You also have the right to complain to a supervisory authority.

Other Scrapegeist actors

Support

Use the Issues tab on this actor's Apify Store page for bugs and feature requests. Include the profile URL or the hashtag when a field is missing: that is what makes a fix possible. Failed and skipped rows are never billed, so a broken run costs you nothing while we investigate.