Instagram Profile and Posts Scraper, Full History, No Login
Pricing
from $1.00 / 1,000 profile scrapeds
Instagram Profile and Posts Scraper, Full History, No Login
Scrape public Instagram profiles with their full post history, not only the latest 12 posts. Profile stats, bio links, every post with caption and timestamp, optional likes, comments and views. No login, no cookies, no browser. Never billed for private or missing accounts or duplicates.
Pricing
from $1.00 / 1,000 profile scrapeds
Rating
0.0
(0)
Developer
Scrapegeist
Maintained by CommunityActor stats
0
Bookmarked
9
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Scrape public Instagram profiles with their full post history, not only the latest 12 posts. One profile row per account with stats and bio links, one row per post with caption, type, thumbnail and timestamp, and likes, comments and views when you ask for them. Hashtag pages, location pages and single post URLs work too. No login, no cookies, no browser.

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. Set
detailSampleabove0and it also carries an engagement rate computed from a sample of the newest posts; - up to 100 post rows (
maxPostsPerProfile, raise it to 5,000): caption, media type, product type, thumbnail, timestamp, post URL and the account that owns it. 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, location:212988663).
The dataset has four views in the Apify Console: Profiles, Posts, Locations and Skipped. Export any of them as JSON, CSV, Excel or XML.
Input kinds, with examples
You can mix all five in one run. Each has its own cap.
| Field | What you paste | What comes back |
|---|---|---|
usernames | natgeo, @sfmoma, https://www.instagram.com/britishmuseum/ | One profile row plus its post history |
postUrls | https://www.instagram.com/p/DdG4RIxIPyf/, or the bare shortcode DdG4RIxIPyf | One detail row per post |
hashtags | museum, #architecture | About 60 post rows per tag (see Limits) |
locations | 212988663 (New York), 213385402 (London), or /explore/locations/213385402/ | One location row plus about 70 post rows |
searchQueries | any keyword | A skipped row. Keyword search needs a login (see Limits) |
{"usernames": ["natgeo", "sfmoma", "britishmuseum"],"postUrls": ["https://www.instagram.com/p/DdG4RIxIPyf/"],"hashtags": ["museum"],"locations": ["213385402"],"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, Locations, 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": 70596,"avg_comments": 408,"avg_video_views": 768614,"sample_size": 3,"scraped_at": "2026-09-18T09:12:44.000Z","discoveredVia": "input"}
Post row, list level (the default)
{"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 🌍 Africa Earth's Wild Home takes you deep into the heart of our wildest continent ...","thumbnail_url": "https://scontent-tpe5-1.cdninstagram.com/v/t51.82787-15/802503979_...jpg","taken_at": "2026-09-10T12:52:15.035Z","detail_level": "list","discoveredVia": "profile:natgeo"}
Post row, engagement level
Likes, comment count and the view count of a video, at about 55 KB per post instead of the 88 KB of a full detail row.
{"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}
A location input adds a fifth shape, the location row:
{"type": "location","id": "212988663","name": "New York, New York","lat": 40.714,"lng": -74.005,"nearby": [{ "id": "378453", "name": "The Metropolitan Museum of Art", "lat": 40.779, "lng": -73.963 }],"discoveredVia": "input"}
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. 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 type | Charge event | What it covers |
|---|---|---|
| Profile row | profile-scraped | The profile row and the first 12 posts of that account, delivered as free list rows. The detailSample posts among those 12 are the exception: they are fetched for the engagement rate and charged at the level they are fetched at. detailSample is 0 by default, so nothing extra is charged unless you ask for it. |
Timeline post row, list level | post-scraped | A post from a profile timeline beyond the first 12. Caption, type, product type, thumbnail, timestamp, post URL and the account that owns it. |
Hashtag or location row, list level | grid-post-scraped | The same fields, off a hashtag or a location grid, plus whatever counts that grid printed for free: a view count on hashtag rows, likes, comment count and the exact timestamp on location rows. Its own event, because one grid page delivers many rows and costs less per row than a timeline page. |
Post row, engagement level | post-engagement-scraped | A post with likes, comment count and the view count. From a timeline, a hashtag or location grid, or postUrls. |
Post row, detail level | post-detail-scraped | A post with likes, comment count, exact timestamp, video URL, usertags, location and the visible comments. From a timeline, a hashtag or location grid, or postUrls. |
| Location row | location-scraped | The place with its coordinates, plus the other places its posts are tagged at. Priced like a profile row. |
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 or location 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 and location rows stay list rows at the grid price, even though their grid hands over likes, the comment count, the exact timestamp or 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
skippedrow 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. A place whose grids were all gated still gives you its location row, free, next to that skipped row.
- 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. They come with the profile row, as list rows.
detailSampleis0by default, so none of them costs you anything extra. Raise it and the N newest of those posts are fetched for the engagement rate and charged at the level they are fetched at. - 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 give about 70 posts per place. The location page does not page. Its grid answers "there is more" and then serves an empty page for the cursor, so the actor reads the two grids the page itself has (the ranked one and the newest one) and stops there. Measured: 69 posts for New York (212988663) and 72 for The Metropolitan Museum of Art (378453), with almost no overlap between the two grids. A location id nobody uses comes back as a skipped row with not_found.
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. The view_count field holds the number Instagram itself publishes: the play count for a reel and for every hashtag row, the video view count for a feed video read from its embed page. Both live in one field, so a comparison across the two kinds compares two metrics.
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 and locations 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).
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 12 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
detailSample is 0 by default, so the sample is off and the engagement fields stay null. Set it and the N newest posts of each account are fetched at a level that carries counts, which writes engagement_rate, avg_likes, avg_comments, avg_video_views and sample_size onto the profile row. 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. Each sampled post is charged as a post engagement or a post detail row, so a sample of 3 is what most runs need.
Only posts since a date
{ "usernames": ["britishmuseum"], "since": "2026-01-01" }
Profile pagination stops once the posts are older than the date. Hashtag and location 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 a location grid is an engagement row when the grid printed no view count for it. 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.
Scrape locations
{ "locations": ["212988663", "213385402"] }
You get one location row per place, with its coordinates and a nearby list, and about 70 post rows that already carry likes, the comment count and the exact timestamp. They stay list rows at the list price: the grid printed those counts for free, so they do not raise the bill. The exception is minViews, which buys an embed per video and delivers those as engagement rows.
nearby is not a nearby-places list. Instagram publishes no such list to a logged-out reader. nearby holds the other places that the posts on this page are tagged at, with their coordinates, which is the only place data the public page exposes. A city page therefore lists many of them, a single venue usually none, because its posts all name the venue itself.
To find a location id, open the place on instagram.com and read it out of the URL: instagram.com/explore/locations/213385402/london-united-kingdom/.
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 ApifyClientclient = 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
typefield:profile,post,location,skipped. Do not assume all rows have the same shape. - Post rows have a
detail_levelfield:list,engagementordetail. Readlike_count,comment_countandview_countonly whendetail_levelis notlist, or on hashtag and location rows. - A
nullcount means "not published", never "zero". Checklike_and_view_counts_disabledbefore you report a zero. - Never retry a
skippedrow with reasonnot_found,private_no_postsornot_available_logged_out. Those do not change on a second run. Alogin_wallorrate_limitedrow is worth one retry later. - Bound spend with
maxTotalPostsandmaxPostsPerProfilebefore 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 actor | This actor | Note |
|---|---|---|
username | username | Same |
fullName | full_name | |
biography | biography | Same |
followersCount | follower_count | |
followsCount | following_count | |
postsCount | not available | Instagram does not serve the total to a logged-out reader. It is post_count: null. |
verified | is_verified | |
private | is_private | |
profilePicUrl | profile_pic_url | |
externalUrl | external_url | bio_links carries all of them, not only the first |
latestPosts[] | separate post rows | One 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[].caption | caption | |
latestPosts[].likesCount | like_count | Needs postDetail engagement or detail, or detailSample above 0 |
latestPosts[].commentsCount | comment_count | Same |
latestPosts[].videoViewCount | view_count | Videos and reels only |
latestPosts[].timestamp | taken_at, taken_at_exact | taken_at is derived from the post id and is on every row. taken_at_exact is the value Instagram prints, on detail rows, on location rows, and on engagement rows where the grid printed it. |
latestPosts[].url | url | |
latestPosts[].type | media_type | image, video, carousel or unknown |
Three behaviour differences worth knowing before you migrate:
- 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.
- Failures are rows, not gaps. A handle that does not exist produces a
skippedrow you can join back to your input list. - Nothing is guessed. A value Instagram does not publish is
null, never0and never an estimate.
Input reference
| Field | Type | Default | What it does |
|---|---|---|---|
usernames | string[] | ["natgeo", "https://www.instagram.com/sfmoma/"] (prefill) | Handles, @handles or profile URLs. One profile row per account plus its posts. |
hashtags | string[] | [] | Hashtags with or without #. About 60 posts per tag, reels only. |
locations | string[] | [] | Location ids or /explore/locations/<id>/ URLs. One location row plus about 70 posts. |
searchQueries | string[] | [] | Keywords. Needs a login, so each one returns a free skipped row. |
postUrls | string[] | [] | 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). |
maxPostsPerProfile | integer | 100 | 0 to 5,000. How far back to page each profile. 0 returns the profile row only. |
maxTotalPosts | integer | 10000 | Hard cap on billed post rows across all inputs (the four post events together), checked before each fetch. |
maxResultsPerHashtag | integer | 100 | Cap per hashtag and per location page. The public pages end before 100 anyway. |
postDetail | enum | list | list, engagement or detail. See the level table below. |
detailSample | integer | 0 | 0, the default, disables the sample. Above 0, and with postDetail: list, the N newest posts per profile are fetched for the engagement rate and charged at the level they are fetched at. |
since | string (date) | none | ISO date, for example 2026-01-01. Paging stops at posts older than this. |
minViews | integer | none | Keep only posts with at least this many views. Videos and reels only. Dropped rows are not billed. |
includeComments | boolean | true | Put the visible comments (about 14) on each detail row. Never billed separately. |
proxyConfiguration | object | Apify Proxy, RESIDENTIAL | Residential is required for post pages. Your own proxy URLs are accepted. |
concurrency | integer | 4 | 1 to 8 parallel sessions. Each keeps one proxy IP and one cookie jar. |
The three post detail levels
| Level | Extra request per post | Fields it adds | Billed as |
|---|---|---|---|
list | none | Caption, media type, product type, thumbnail, taken_at, URL, owner | post-scraped from a timeline, grid-post-scraped off a hashtag or location grid |
engagement | about 55 KB | like_count, comment_count, view_count, video_url | post-engagement-scraped |
detail | about 88 KB | like_count, comment_count, taken_at_exact, video_url, usertags, co_authors, location, comments, carousel_media. No view count. | post-detail-scraped |
Use engagement when you need views, detail when you need comments, usertags or the location, and list when you need reach and speed.
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. The same holds for locations at about 70 posts.
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.
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 and locations 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.
Legal and responsible use
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
- Facebook Events Scraper, Past the 8-Event Limit: every public event of an organizer or a city, not the 8 Facebook shows you when logged out.
- Facebook Pages Contact Scraper: one contact row per Facebook Page with email, phone, WhatsApp, website, address, followers and rating.
Support
Use the Issues tab on this actor's Apify Store page for bugs and feature requests. Include the profile URL, the hashtag or the location id 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.