# Instagram Profile Scraper 📸 Posts, Followers & Engagement (`gatherworks/instagram-profile-scraper`) Actor

Scrape any Instagram account plus their posts. Followers, bio, category, verification, and per-post likes, comments, plays, hashtags and media. Paste usernames or profile URLs. Export to JSON, CSV or Excel. No login, no cookies, no API key.

- **URL**: https://apify.com/gatherworks/instagram-profile-scraper.md
- **Developed by:** [GatherWorks](https://apify.com/gatherworks) (community)
- **Categories:**
- **Stats:** 4 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.30 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Instagram Profile Scraper 📸 Posts, Followers & Engagement

**Give it an Instagram handle and get the account and their posts — followers,
bio, bio link, category and verification, then per-post likes, comments, plays,
hashtags and media, on a clean row each.**

**No account, no cookies, no API key.** Paste handles and run.

***

### 🔍 What is the Instagram Profile Scraper?

Give it handles — `instagram`, `@instagram` or a full profile URL, all
normalised for you. For each account it returns:

- 👤 the account — followers, following, post count, bio, category, verification
- 🔗 the link in the bio
- 📸 their posts, with likes, comments, plays and every image URL, Reels and
  video posts marked with the video URL where there is one, plus hashtags lifted
  from each caption and any tagged location

That is a normal run. If you want the accounts on their own, switch **📸 Include
their posts** off.

### ⚡ What can it do?

- **👥 Take many accounts at once** and return one dataset covering all of them.
- **📸 Return their posts as part of the same run**, one row per post, keyed back
  to the handle that published it.
- **🎬 Keep only Reels and videos**, if that is what you are analysing.
- **🔢 Filter by likes** and ignore the long tail.
- **📅 Filter by date.** Keep only posts inside a window you set.
- **🚧 Cap how far a filter searches**, so a narrow filter stops rather than
  running away with your budget.
- **🔎 Filter before you pay.** A post that does not match is never stored and
  never charged for.
- **📄 Export anywhere.** JSON, CSV, Excel — the dataset is yours.

#### 🎯 A private account is reported, not retried

An account set to private has no publicly visible posts. Rather than returning an
empty list that looks like a failure, the account row comes back with
`isPrivate: true` and the run says so plainly — and you are not charged for posts
that were never available.

### ⚡ Speed and accuracy

**Speed.** Measured on a real run: **1 account and 8 posts in 8 seconds**. Each
account is one lookup, and their posts come back twelve at a time.

**Accuracy.** Handles are normalised, so `instagram`, `@instagram` and a full
profile URL resolve to the same account and are collected once. Counts are
numbers, not display text. Timestamps are ISO 8601. Post links are canonical
`instagram.com/p/…` permalinks built from the post's own short code. Carousels
return every image URL, not just the first. A handle that does not resolve is
reported and skipped rather than returned empty.

### 📊 What data does it return?

**Account rows** (`rowType: "account"`) — one per handle:

| Field | Notes |
|---|---|
| 🏷️ `handle`, `fullName` | Handle and display name |
| 🔗 `profileUrl` | Direct link to the profile |
| 📝 `bioText` | The bio text |
| 🔢 `followersCount`, `followsCount`, `postsCount` | Audience and output, as numbers |
| ⚖️ `followerRatio` | Followers divided by following |
| ✅ `isVerified`, 🚩 `isPrivate`, 🏢 `isBusiness` | Account state |
| 🏷️ `categoryName` | The business category, where the account sets one |
| 🔗 `externalUrl` | The link in the bio |

**Post rows** (`rowType: "post"`) — one per post kept, following the account they
belong to:

| Field | Notes |
|---|---|
| 📝 `postCaption`, `captionLength` | The caption and its length |
| 🔗 `postUrl`, `postShortCode` | Canonical permalink and its short code |
| 📊 `likesCount`, `commentsCount`, `playsCount` | Engagement, as numbers |
| 📈 `engagementTotal`, `engagementPerPlay` | Likes plus comments, and that as a share of plays |
| 🖼️ `mediaKind`, `imageUrls`, `hasVideo`, `videoUrl` | Media type and files |
| 🏷️ `hashtags` | Lifted from the caption |
| 📍 `locationName` | Tagged location, where there is one |
| 🚩 `isSponsored` | Whether it is a declared paid partnership |
| 📅 `publishedAt` | When it was posted, ISO 8601 |

#### 🚫 What it does **not** return

- **Contact details.** **No email address and no phone number is returned on any
  row, for any account** — business, creator or personal, and regardless of what
  the account displays in the Instagram app. If you need contact data, this is
  not the Actor for it.
- **Posts from private accounts.** They are not publicly visible; the account row
  says so.
- **Stories and Highlights.** They expire and are not part of a profile feed.
- **Follower lists.** Audience size is a number; the individual followers are not returned.
- **Analytics only the account owner can see** — reach, impressions, saves,
  audience demographics.

### 💰 How much does it cost?

You pay per result — for the rows the run actually produces, never for runtime,
and never on a subscription.

Standard rate: **💵 $0.0022 per row**, and a row is a row: an account row and a
post row cost exactly the same.

| Run | Rows | Cost |
|---|---|---|
| 🟩 10 accounts + 30 posts each | 310 | 💵 $0.68 |
| 🟨 100 accounts, posts switched off | 100 | 💵 $0.22 |
| 🟧 1 account + 1,000 posts | 1,001 | 💵 $2.20 |

**A handle that does not resolve is free.** So is a post your filters remove.
**A month you do not run it costs nothing at all** — there is no plan to keep alive.

#### What you get for that

| | Included here |
|---|---|
| 📸 Posts as part of a normal run | ✅ same rate as the account row |
| 🖼️ Every image in a carousel, not just the first | ✅ |
| 📈 Engagement per play, computed | ✅ |
| 🏷️ Hashtags as an array | ✅ free |
| 📍 Tagged location and paid-partnership flag | ✅ free |
| 🔎 Video-only, likes and date filters | ✅ free |
| 💳 Minimum spend | none |

### 🚀 How do I use it?

1. Add one or more **usernames or profile URLs**.
2. Run as-is. You get each account plus their posts, every row at the same rate.
3. Set **maximum posts per account** to control how much each account returns.
4. Optionally filter the posts to videos, a like count, or a date window.
5. Only want the profiles? Switch **📸 Include their posts** off.
6. Run, then export the dataset as JSON, CSV or Excel.

### ⚙️ Input

| Field | Default | Notes |
|---|---|---|
| 👤 `usernames` | — | Handles, @handles or profile URLs |
| 📸 `includePosts` | `true` | Collect their posts — same rate as the account row |
| 🔢 `maxPostsPerAccount` | `50` | How many posts to return per account |
| 🎬 `videosOnly` | `false` | Keep only Reels and video posts |
| 🔢 `minLikes` | none | Keep only posts with at least this many likes |
| 📅 `postedAfter`, `postedBefore` | none | Keep only posts inside this window |
| 🚧 `maxScanned` | `1000` | How far a filter may search before stopping |

```json
{
  "usernames": ["instagram", "@natgeo"],
  "includePosts": true,
  "maxPostsPerAccount": 50,
  "videosOnly": false
}
```

### 📄 Output example

A default run returns the account row first, captured from a live run:

```json
{
  "rowType": "account",
  "handle": "nasa",
  "fullName": "NASA",
  "profileUrl": "https://www.instagram.com/nasa/",
  "bioText": "Making the seemingly impossible, possible. \u2728",
  "externalUrl": "https://www.nasa.gov/",
  "followersCount": 104444393,
  "followsCount": 91,
  "postsCount": 4899,
  "categoryName": "Government Agencies",
  "isBusiness": true,
  "isVerified": true,
  "isPrivate": false
}
```

then their posts, one row each:

```json
{
  "rowType": "post",
  "accountHandle": "instagram",
  "postId": "3974396910131444700",
  "postShortCode": "Dcn5O64y3xi",
  "postUrl": "https://www.instagram.com/p/Dcn5O64y3xi/",
  "postCaption": "5, 6, 7, ATE 🔥",
  "mediaKind": "video",
  "hasVideo": true
}
```

### ❓ Frequently asked questions

**Do I need an Instagram account?** No. No account, no cookies, no API key.

**What forms of username work?** `instagram`, `@instagram`, and
`https://www.instagram.com/instagram/` all resolve to the same account.

**Does it return posts?** Yes — posts come with a normal run, at the same rate as
the account row. Switch **📸 Include their posts** off if you only want the
profiles.

**What happens with a private account?** You get the account row with
`isPrivate: true`, and no posts — because none are public. You are not charged
for posts that were never available.

**Does it return email addresses or phone numbers?** No. No contact details are
returned on any row, for any account, business or otherwise.

**Does it return Stories?** No. Stories expire and are not part of a profile feed.

**How do I keep a run small?** Lower **maximum posts per account**, or switch the
posts off entirely and pay for one row per handle.

### 🏷️ Pricing

Pay per result — you are charged for output, not for platform time.

| Event | Price | Covers |
|---|---|---|
| 📊 `result` — **primary** | 💵 $0.0022 | Any row returned, account or post, at one price |
| ▶️ `apify-actor-start` | 💵 $0.00005 | Starting one run, charged once per GB of memory |
| 🔗 Bio link, follower and engagement counts | ✅ **free** | On every account row |
| 🏷️ Hashtags, location and paid-partnership flag | ✅ **free** | On every post row |
| 🔒 Private accounts | ✅ **free** | Posts never fetched, never charged |
| ❌ Handles that do not resolve | ✅ **free** | Reported, never charged |
| 🔎 Posts your filters remove | ✅ **free** | Never stored, never charged |

**One price, one event.** Whatever the run returns, each row costs the same.

#### 🏅 Rates by tier

The prices above are the standard rate. Higher Apify plans are billed at the
discounted tiers below, automatically:

| Event | FREE | BRONZE | SILVER | GOLD |
|---|---|---|---|---|
| 📊 `result` | 💵 $0.0022 | 💵 $0.0019 | 💵 $0.0016 | 💵 $0.0013 |
| ▶️ `apify-actor-start` | 💵 $0.00005 | 💵 $0.00005 | 💵 $0.00005 | 💵 $0.00005 |

#### 🛑 You are never billed past your limit

Set a maximum charge on the run and the Actor stops when it reaches it, with
every account collected up to that point complete and saved. Filtered-out posts
are never stored and never billed.

# Actor input Schema

## `usernames` (type: `array`):

One or more Instagram handles. All of these work and are normalised for you: <code>instagram</code>, <code>@instagram</code>, or <code>https://www.instagram.com/instagram/</code>.

## `includePosts` (type: `boolean`):

Collect the posts each account published, alongside the account row. <b>On by default</b> — switch it off if you only want the account itself. A private account has no publicly visible posts, so none are returned.

## `maxPostsPerAccount` (type: `integer`):

Caps how many posts are returned for each account. Cost scales linearly with this number.

## `videosOnly` (type: `boolean`):

Keep only video posts and Reels, leaving out photos and carousels.

## `minLikes` (type: `integer`):

Keep only posts with at least this many likes. Leave empty for no minimum.

## `postedAfter` (type: `string`):

Keep only posts published on or after this date.

## `postedBefore` (type: `string`):

Keep only posts published on or before this date.

## `maxScanned` (type: `integer`):

Safety cap on how many posts are examined while filtering, so a narrow filter cannot run away with your budget.

## Actor input object example

```json
{
  "usernames": [
    "instagram",
    "@natgeo"
  ],
  "includePosts": true,
  "maxPostsPerAccount": 50,
  "videosOnly": false,
  "maxScanned": 1000
}
```

# Actor output Schema

## `accounts` (type: `string`):

One row per account, plus a row for each post with likes, comments, plays and media.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "usernames": [
        "instagram",
        "@natgeo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gatherworks/instagram-profile-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "usernames": [
        "instagram",
        "@natgeo",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("gatherworks/instagram-profile-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "usernames": [
    "instagram",
    "@natgeo"
  ]
}' |
apify call gatherworks/instagram-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gatherworks/instagram-profile-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/LiIPFL7zR8f1VC8Bb/builds/ZnzfrP9c6oHrXpsg9/openapi.json
