Instagram Profile Scraper - Bio & Posts
Scrape public Instagram profiles from a list of usernames: bio, followers, following, post count, verified badge and bio links. Add recent posts with captions, hashtags, likes and comments. Export to Excel, CSV or JSON.
Input schema
Input fields, types, required flags, descriptions, defaults, and prefills come from the published definition. Prefill examples are distinct from defaults.
| Field | Type | Required | Description |
|---|---|---|---|
usernames | array | required | Instagram profiles to scrape. Each entry can be a username (with or without @) or a profile link such as https://www.instagram.com/natgeo/. Each profile is scraped in turn. Example: 'instagram', '@instagram' or 'https://www.instagram.com/instagram/'. |
maxPosts | integer | optional | Maximum number of posts to extract per profile (after the date and pinned-post filters below). Set to 0 to only extract profile data without posts. Public profiles need no sign-in, however many posts you ask for. |
newerThan | string | optional | Skip posts published before this date. Leave empty for no date filter. Must be a date from 2000-01-01 up to the day before the run. The date is read from the post's id, so it can be off by up to a day. |
Show all input fields
| Field | Type | Required | Description |
|---|---|---|---|
usernames | array | required | Instagram profiles to scrape. Each entry can be a username (with or without @) or a profile link such as https://www.instagram.com/natgeo/. Each profile is scraped in turn. Example: 'instagram', '@instagram' or 'https://www.instagram.com/instagram/'. |
maxPosts | integer | optional | Maximum number of posts to extract per profile (after the date and pinned-post filters below). Set to 0 to only extract profile data without posts. Public profiles need no sign-in, however many posts you ask for. |
newerThan | string | optional | Skip posts published before this date. Leave empty for no date filter. Must be a date from 2000-01-01 up to the day before the run. The date is read from the post's id, so it can be off by up to a day. |
skipPinnedPosts | boolean | optional | Leave out the posts a profile has pinned to the top of its grid. A pinned post that is also the newest post cannot be recognised and stays in. |
cookies | string | optional | Optional. Leave empty to scrape public profiles without signing in. Paste your own Instagram cookies in JSON format to also scrape private profiles your account follows and age-restricted posts. It is a secret input, so the Apify Console hides and encrypts it. Format: [{"name":"sessionid","value":"...","domain":".instagram.com"}, ...] |
Download the published input schema
View complete JSON
{
"properties": {
"cookies": {
"description": "Optional. Leave empty to scrape public profiles without signing in. Paste your own Instagram cookies in JSON format to also scrape private profiles your account follows and age-restricted posts. It is a secret input, so the Apify Console hides and encrypts it.\n\nFormat: [{\"name\":\"sessionid\",\"value\":\"...\",\"domain\":\".instagram.com\"}, ...]",
"editor": "textarea",
"isSecret": true,
"title": "Instagram Cookies (Optional)",
"type": "string"
},
"maxPosts": {
"default": 12,
"description": "Maximum number of posts to extract per profile (after the date and pinned-post filters below). Set to 0 to only extract profile data without posts. Public profiles need no sign-in, however many posts you ask for.",
"example": 50,
"maximum": 500,
"minimum": 0,
"prefill": 3,
"title": "Maximum Posts to Scrape",
"type": "integer"
},
"newerThan": {
"dateType": "absolute",
"description": "Skip posts published before this date. Leave empty for no date filter. Must be a date from 2000-01-01 up to the day before the run. The date is read from the post's id, so it can be off by up to a day.",
"editor": "datepicker",
"nullable": true,
"title": "Only posts newer than",
"type": "string"
},
"skipPinnedPosts": {
"default": false,
"description": "Leave out the posts a profile has pinned to the top of its grid. A pinned post that is also the newest post cannot be recognised and stays in.",
"title": "Skip pinned posts",
"type": "boolean"
},
"usernames": {
"description": "Instagram profiles to scrape. Each entry can be a username (with or without @) or a profile link such as https://www.instagram.com/natgeo/. Each profile is scraped in turn. Example: 'instagram', '@instagram' or 'https://www.instagram.com/instagram/'.",
"editor": "stringList",
"example": [
"instagram"
],
"minItems": 1,
"prefill": [
"instagram"
],
"title": "Instagram Usernames",
"type": "array"
}
},
"required": [
"usernames"
],
"schemaVersion": 1,
"title": "Instagram Profile Scraper Input",
"type": "object"
}
Output schema
Published result links and storage locations. This schema is not an exhaustive contract for every field in a dataset record.
| Field | Type | Required | Description |
|---|---|---|---|
posts | string | optional | Scraped Posts & Profiles |
Download the published output schema
View complete JSON
{
"actorOutputSchemaVersion": 1,
"properties": {
"posts": {
"template": "{{links.apiDefaultDatasetUrl}}/items",
"title": "Scraped Posts & Profiles",
"type": "string"
}
},
"title": "Instagram Profile Scraper Output",
"type": "object"
}
Dataset schema
Published dataset views and any declared field definitions. A view can select only a subset of the returned record fields.
Download the published dataset schema
View complete JSON
{
"actorSpecification": 1,
"fields": {
"properties": {
"accessibility_caption": {
"description": "Instagram's auto-generated alt-text description of the media",
"nullable": true,
"type": "string"
},
"author_meta": {
"description": "Full profile data of the scraped account",
"properties": {
"biography": {
"nullable": true,
"type": "string"
},
"category": {
"nullable": true,
"type": "string"
},
"email": {
"nullable": true,
"type": "string"
},
"external_urls": {
"type": "array"
},
"followers_count": {
"type": "integer"
},
"following_count": {
"type": "integer"
},
"full_name": {
"nullable": true,
"type": "string"
},
"is_business": {
"type": "boolean"
},
"is_private": {
"type": "boolean"
},
"is_verified": {
"type": "boolean"
},
"phone": {
"nullable": true,
"type": "string"
},
"posts_count": {
"type": "integer"
},
"profile_pic_url": {
"nullable": true,
"type": "string"
},
"profile_url": {
"type": "string"
},
"username": {
"type": "string"
}
},
"type": "object"
},
"caption": {
"description": "Full caption text of the post",
"nullable": true,
"type": "string"
},
"caption_is_edited": {
"description": "Whether the caption was edited after posting",
"type": "boolean"
},
"collaborators": {
"description": "Usernames listed as co-authors of the post",
"type": "array"
},
"comment_count": {
"description": "Number of comments on the post",
"nullable": true,
"type": "integer"
},
"comments_disabled": {
"description": "Whether commenting is disabled on the post",
"type": "boolean"
},
"hashtags": {
"description": "Hashtags extracted from the caption",
"type": "array"
},
"like_count": {
"description": "Number of likes on the post",
"nullable": true,
"type": "integer"
},
"likes_hidden": {
"description": "Whether the author hid the like count",
"type": "boolean"
},
"location": {
"description": "Tagged location, when present",
"nullable": true,
"type": "object"
},
"media_count": {
"description": "Number of media items in the post (1 for single image/video)",
"nullable": true,
"type": "integer"
},
"media_items": {
"description": "Every image/video in the post, in order, with per-item url/dimensions",
"nullable": true,
"type": "array"
},
"media_type": {
"description": "Type of post: Photo, Video, Carousel, Reel, or IGTV",
"type": "string"
},
"mentions": {
"description": "@mentions extracted from the caption",
"type": "array"
},
"music": {
"description": "Music/audio attribution, when present (reels)",
"nullable": true,
"type": "object"
},
"post_id": {
"description": "Internal Instagram media id of the post",
"type": "string"
},
"post_url": {
"description": "Direct URL to the Instagram post",
"type": "string"
},
"pub_date": {
"description": "ISO 8601 datetime when the post was published",
"nullable": true,
"type": "string"
},
"scraped_at": {
"description": "ISO 8601 datetime when this record was scraped",
"type": "string"
},
"shortcode": {
"description": "Instagram shortcode identifying the post",
"type": "string"
},
"status": {
"description": "success for a scraped post or profile. Otherwise the reason there is none: post_age_restricted, post_is_private, post_not_found, blocked, error, invalid_url, cookies_unusable, profile_not_found, profile_is_private, profile_age_restricted or profile_restricted. The log explains each one",
"type": "string"
},
"tagged_users": {
"description": "Usernames tagged in the post",
"type": "array"
},
"thumbnail_url": {
"description": "Direct URL to the post's cover image",
"nullable": true,
"type": "string"
},
"username": {
"description": "Instagram username of the post author",
"type": "string"
}
},
"type": "object"
},
"title": "Instagram Posts & Profiles",
"views": {
"overview": {
"display": {
"component": "table",
"properties": {
"author_meta.followers_count": {
"format": "number",
"label": "Followers"
},
"author_meta.full_name": {
"format": "text",
"label": "Name"
},
"author_meta.is_business": {
"format": "boolean",
"label": "Business"
},
"author_meta.is_verified": {
"format": "boolean",
"label": "Verified"
},
"caption": {
"format": "text",
"label": "Caption"
},
"comment_count": {
"format": "number",
"label": "Comments"
},
"like_count": {
"format": "number",
"label": "Likes"
},
"media_count": {
"format": "number",
"label": "Media Count"
},
"media_type": {
"format": "text",
"label": "Type"
},
"post_url": {
"format": "text",
"label": "Post URL"
},
"pub_date": {
"format": "text",
"label": "Published"
},
"status": {
"format": "text",
"label": "Status"
},
"thumbnail_url": {
"format": "text",
"label": "Thumbnail"
},
"username": {
"format": "text",
"label": "Username"
}
}
},
"title": "Posts",
"transformation": {
"fields": [
"username",
"post_url",
"media_type",
"caption",
"like_count",
"comment_count",
"pub_date",
"media_count",
"thumbnail_url",
"status",
"scraped_at",
"author_meta.full_name",
"author_meta.followers_count",
"author_meta.is_verified",
"author_meta.is_business",
"author_meta.category"
]
}
}
}
}
Key-value schema
Published key-value store definitions, when provided by this build.
This definition is not present in the default published build. See the published README for available documentation.
Published README
Read the full published documentation
Instagram Profile Scraper - Bio & Posts
Look up one or many public Instagram accounts and get a clean record for each: display name, bio, follower and following counts, verified flag and bio links. Add recent posts with captions, hashtags, mentions, likes, comments, media links, location and music, then export everything to Excel, CSV or JSON. Built for marketers, agencies, analysts and researchers who need to size up creators, competitors and brands quickly.
What this actor does
- Many accounts per run — enter a list of usernames, with or without the
@, and each one is processed in turn - Profile snapshot — name, bio, follower and following counts, verification and bio links
- Recent posts on demand — up to 500 posts per profile, covering photos, videos, reels, carousels and IGTV, with no sign-in for public profiles
- Date and pin filters — keep only posts newer than a date, and leave out the posts a profile has pinned
- Profile-only mode — set
maxPoststo0to skip posts and collect just the account details
Output per profile
When maxPosts is 0 (or the account has no posts to show), each username produces one record with a single author_meta object. The same object is attached to every post record of that account.
author_meta fields:
username— Instagram usernamefull_name— display namebiography— bio textprofile_pic_url— profile picture URLprofile_url— link to the profile on Instagramis_verified—trueif the account has a verified badgeis_private—trueif the account is privatefollowers_count— number of followersfollowing_count— number of accounts followedexternal_urls— links listed in the bio (empty list if none)
Output per post
One record per post when maxPosts is greater than 0.
post_url— direct link to the poststatus—success,post_not_found,post_age_restricted,post_is_private,error,blockedorinvalid_urlauthor_meta— the profile object described aboveshortcode— Instagram's short ID for the postpost_id— Instagram's numeric media IDusername— username of the post authorcaption— full caption texthashtags— hashtags found in the captionmentions— @mentions found in the captiontagged_users— accounts tagged in the postcollaborators— accounts listed as co-authorscaption_is_edited—trueif the caption was edited after postingpub_date— publication date and time (ISO 8601)media_type—Photo,Video,Carousel,ReelorIGTVmedia_count— number of images / videos in the postthumbnail_url— cover image URLmedia_items— every image or video in the post, in order, each withindex,type,url,widthandheight; videos also carrydurationandhas_audioaccessibility_caption— Instagram's automatic alt-text for the medialike_count— number of likescomment_count— number of commentslikes_hidden—trueif the author hid the like countcomments_disabled—trueif comments are turned offlocation— tagged location, when there is onemusic— audio attribution, when there is one (mostly reels)scraped_at— when the record was collected (ISO 8601)
Only success posts carry the full set of fields. Posts with any other status contain just post_url, status and author_meta. The status values mean:
success— the post was collected in fullpost_not_found— the post no longer exists or is not availablepost_age_restricted— Instagram restricts the post to older viewerspost_is_private— the post belongs to a private accountblocked— Instagram refused the request for that postinvalid_url— the post link was not a valid Instagram post URLerror— something else went wrong while collecting that post
A profile that could not be scraped gets a record with a status that names the cause and only the username and profile_url (a private profile also shows the public details above). The explanation is in the run log, one line per profile, followed by a closing Done: summary, and the run moves on to the next username. The profile statuses are profile_not_found, profile_is_private, profile_age_restricted (an age check, for example alcohol brands), profile_restricted ("unavailable for certain audiences"), cookies_unusable (a private or restricted profile needed your cookies and Instagram did not accept them), blocked and error; a scraped profile is success. The post statuses are listed under Output per post. While the run is going, the status message shows a live count such as Scraped 2/5 profiles.
Profile field availability
The profile details are read the way a visitor who is not signed in sees them. Instagram shows that visitor the name, bio, picture, verified and private flags, the exact follower and following counts and the bio links. The post count is in the page description: exact up to 9,999 posts, and rounded to whole thousands from 10,000 posts (Instagram writes "32K Posts", so posts_count is 32000). It does not show the business flag, the category or a business email and phone, so those are not in the record. A private profile shows the same details.
author_meta field |
Available |
|---|---|
username, full_name, biography, profile_pic_url, profile_url, is_verified, is_private, followers_count, following_count, external_urls |
Yes |
posts_count |
Yes. When the profile has 10,000 posts or more, an approximate post count is given instead of the exact one: Instagram only gives a figure rounded to whole thousands (32K becomes 32000). Below 10,000 it is exact. It comes from a cached copy and can lag a little |
is_business, category, email, phone |
No (shown by Instagram only to signed-in visitors) |
Authentication
Public profiles and public posts need no sign-in, however many posts you ask for: a profile is always read first the way Instagram shows it to a visitor who is not signed in, even when you give cookies. Only a private profile (your account must follow it) and age-restricted posts need a signed-in view, and only your own cookies can give one. Export your instagram.com cookies as JSON from a logged-in browser session using any cookie-export browser extension, then paste the JSON into the optional cookies field. The field is a secret input, so the Apify Console hides and encrypts it, and the cookies are used only during your run. Before anything is read with them, the actor asks Instagram whether it recognises the cookies; if they are invalid, expired or signed out, the run log says so. Without cookies those cases are reported with their status. The actor does not sign in with any account of its own.
If your cookies expire mid-run, re-export them from your browser and restart the actor.
Input
| Field | Type | Default | Description |
|---|---|---|---|
usernames |
array of strings | – (required) | Instagram usernames to scrape, with or without @, or profile links such as https://www.instagram.com/natgeo/. At least one is needed |
maxPosts |
integer | 12 |
Posts to collect per profile, from 0 to 500. Posts left out by the two filters below do not count. 0 returns profile data only |
newerThan |
string | – | Optional date (YYYY-MM-DD). Posts published before it are skipped. Empty means no date filter. It must be from 2000-01-01 up to the day before the run, otherwise it is ignored with a warning in the log. The date is read from the post's id, so it can be off by up to a day |
skipPinnedPosts |
boolean | false |
Leave out the posts a profile has pinned to the top of its grid. Instagram does not mark pinned posts for a visitor who is not signed in, so they are recognised by their place and date; a pinned post that is also the profile's newest post cannot be told apart and stays in |
cookies |
string (secret) | – | Optional. Your own Instagram cookies as JSON, used for private profiles your account follows and age-restricted posts. See Authentication |
Example: quick look at one brand
{
"usernames": ["nike"],
"maxPosts": 12
}
Example: compare several publishers side by side
{
"usernames": ["natgeo", "@nytimes", "bbcnews"],
"maxPosts": 30
}
Example: account details only for a vetting list
{
"usernames": ["nasa", "spacex", "esa"],
"maxPosts": 0
}
Example: deep pull with your own cookies
{
"usernames": ["redbull"],
"maxPosts": 500,
"cookies": "<your cookies JSON>"
}
Example output
A profile-only record (maxPosts: 0); the values are illustrative.
{
"status": "success",
"author_meta": {
"username": "examplebrand",
"full_name": "Example Brand",
"biography": "Better ingredients, better you",
"profile_pic_url": "https://.../profile.jpg",
"is_verified": true,
"is_private": false,
"followers_count": 30213,
"following_count": 12,
"profile_url": "https://www.instagram.com/examplebrand/",
"external_urls": ["https://example.com/shop"]
}
}
Use cases
- Influencer marketers — check follower counts, verification and posting activity before signing a creator
- Agency account managers — compare a client's profile and recent posts with competitors in one export
- Social media analysts — see which post types, captions and hashtags earn the most likes and comments
- Partnership teams — vet creators and brands in a niche by their bio, follower counts and bio links before contacting them individually
- Brand and PR teams — audit how tagged collaborators appear on a brand's feed
- Academic researchers — gather public captions, hashtags, locations and engagement counts for communication studies
Responsible use
The data this actor returns is publicly available on Instagram, but bios (which can hold contact details), names, photos and tagged accounts can still identify real people. Before you collect or store them you need a lawful basis (for example under the GDPR), and you should keep only what your purpose requires. Do not use the results to profile, track or target minors, and do not use them to single out people by special categories of personal data such as health, religion, political opinion or sexual orientation. Contact details found on a profile are meant for the business purpose they were published for, not for unsolicited bulk messaging.
FAQ
Do I need an Instagram account?
No, not for public profiles. Only private profiles and age-restricted posts need cookies, and you can paste your own in the optional cookies field (see Authentication above).
Can it scrape private accounts?
Only with your own cookies, from an account that follows it. Without cookies you get one record with the public profile details and the status profile_is_private.
How many posts can I get per profile?
Up to 500 per profile in one run. Use maxPosts to choose any number from 1 to 500, or 0 for profile details only. Accounts with fewer posts simply return fewer.
Which post types are included?
Photos, videos, reels, carousels and IGTV. For carousels, every slide is listed in media_items, in order.
Why are email, phone, category and is_business missing, and is posts_count exact?
The first four are shown by Instagram only to signed-in visitors, and the actor reads profiles without signing in. posts_count is exact below 10,000 posts. When a profile has 10,000 posts or more, an approximate post count is given instead: Instagram writes it as an abbreviation such as "32K", and the actor turns it into 32000 (see Profile field availability).
What happens if a username does not exist or fails?
That username gets a record with a status such as profile_not_found or error (the log explains it), and the run continues with the rest of your list.
How current is the data? It is collected live when you run the actor, so follower counts, likes and comments reflect that moment.
Do I need to set up a proxy? No. The actor handles its connection to Instagram automatically. Just enter your usernames and run it.
Is this affiliated with Instagram or Meta? No. It is an independent tool that works with publicly available profile data. It is not affiliated with, endorsed by or sponsored by Instagram or Meta Platforms, Inc.
Python quickstart
Use a schema-derived input and the Python standard library to start a run. An Apify account and token are required for execution.
Source build OmzUBfZ01FOWJ3Y1P. Documentation is regenerated from the default published build; private drafts are not included.