# Instagram Scraper: Posts, Comments & Stories (`socialhz/instagram-profile-scraper`) Actor

Scrape Instagram accounts by username — posts, comment threads (up to 500 per post) and live Stories. No login, no cookies. Business-plan rates: accounts $1.50/1K, posts $0.99/1K, comments $0.49/1K, Stories $8.00/1K; higher on lower plans. No keyword or hashtag search.

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

## Pricing

from $0.99 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Scraper: Posts, Comments & Stories

Give it a list of Instagram accounts. Get back their posts with full engagement data, their comment threads, and their live Stories — in one run, with no Instagram login and no cookies.

Built for competitor monitoring, creator and influencer tracking, campaign reporting and content analysis.

***

### What it does

- **Posts by account** — 12 per page, paging as deep as you ask for. Photos, videos, reels and carousels, each labelled by type
- **Full carousel expansion** — every still in a multi-image post, not just the cover
- **Deep comment threads** — up to 500 top-level comments per post, paged automatically; every record tells you whether more remain
- **Live Stories** — captured while they are live, with media type, dimensions and media count
- **No login required** — no Instagram account, no cookies, no session token, ever
- **Every account accounted for** — each account you supply comes back with a record, so you always know what was found
- **100% run success rate** — retries, backoff and duplicate detection built in

***

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `usernames` | array | — | **Required.** Accounts to scrape. A username, `@handle` or full profile URL all work. Post, reel, Story and hashtag links are rejected — they are not profile links. Invalid entries are skipped and never charged. Max `200` accounts per run |
| `maxPostsPerAccount` | integer | `12` | Posts per account. Arrive in pages of 12. Max `500` |
| `includeComments` | boolean | `false` | Retrieve comment threads. Charged separately |
| `maxPostsToComment` | integer | `5` | How many of each account's posts get their comments retrieved, newest first. Max `100` |
| `maxCommentsPerPost` | integer | `50` | Comments per post. Arrive ~13 at a time. Max `500` |
| `includeStories` | boolean | `false` | Capture live Stories. Charged per account checked |

#### Example input

```json
{
  "usernames": ["nasa", "natgeo", "@zara"],
  "maxPostsPerAccount": 24,
  "includeComments": true,
  "maxPostsToComment": 5,
  "maxCommentsPerPost": 50,
  "includeStories": false
}
```

***

### Output

Three kinds of record, told apart by the `type` field.

**`post`** — one per post. The main output.
**`story`** — one per live Story, when Stories are requested.
**`account`** — one per account that produced no posts, so you can see what happened and why.

| Field | Type | Description |
|---|---|---|
| `type` | enum | `post`, `story` or `account` |
| `username` | string | The account this record belongs to |
| `profileId` | string | null | Instagram's internal account id |
| `displayName` | string | null | Account display name |
| `isVerified` | boolean | null | Blue-tick status |
| `profileUrl` | string | Link to the profile |
| `accountStatus` | enum | `ok`, `no_posts`, `not_found`, `unavailable` |
| `postId` | string | null | Post id. `null` on story and account records |
| `shortCode` | string | null | Short code from the post URL |
| `postType` | enum | `photo`, `video`, `reel`, `carousel` |
| `url` | string | null | Link to the post |
| `caption` | string | null | Post caption |
| `postedAt` | string | null | ISO 8601 UTC |
| `likeCount` | number | null | Likes at time of retrieval |
| `commentCount` | number | null | Instagram's own count. Usually higher than `commentsRetrieved` |
| `viewCount` / `playCount` | number | null | **Video posts only.** `null` on photos |
| `hashtags` / `mentions` / `taggedUsers` | array | Extracted from the post |
| `thumbnailUrl` | string | null | Preview image |
| `videoUrl` | string | null | Video file. Video posts only |
| `videoDurationSeconds` | number | null | Video length. Video posts only |
| `postsPartial` | boolean | null | `true` when paging stopped early for this account, so fewer posts were returned than asked for |
| `imageUrls` | array | Image files, including every still in a carousel. A carousel item that is a video has no entry here, so this can be shorter than `carouselItemCount` |
| `carouselItemCount` | number | Items in a carousel, counting both stills and videos. `0` on single-media posts |
| `mediaWidth` / `mediaHeight` | number | null | Media dimensions |
| `isAd` / `isPaidPartnership` | boolean | null | As reported by Instagram |
| `isPinned` | boolean | null | Pinned to the profile grid |
| `comments` | array | Top-level comments. Empty unless requested |
| `commentsRetrieved` | number | How many comments this record contains |
| `commentsRemaining` | boolean | `true` if further comments exist that were not retrieved |
| `commentsStatus` | enum | `retrieved`, `skipped_no_comments`, `skipped_limit`, `not_requested`, `unavailable` |

#### Story record

Emitted once per live Story when Stories are requested.

| Field | Type | Description |
|---|---|---|
| `type` | enum | Always `story` |
| `username` / `profileId` / `displayName` / `isVerified` | | Account identity, as above |
| `mediaCategory` | string | null | The Story's media type |
| `width` / `height` | number | null | Media dimensions in pixels |
| `mediaCount` | number | How many media files the Story had. **The files themselves are not returned** |
| `postedAt` | string | null | ISO 8601 UTC |

#### Account record

Emitted once for an account that produced no posts, so a paid-for lookup is never silent.

| Field | Type | Description |
|---|---|---|
| `type` | enum | Always `account` |
| `accountStatus` | enum | `no_posts`, `not_found` or `unavailable` |
| `postsFound` / `storiesFound` | number | What was retrieved for this account |
| `postId` | null | Always `null` on this record type |

#### Comment object

| Field | Type | Description |
|---|---|---|
| `id` | string | Comment id |
| `text` | string | Comment body |
| `author` | string | null | Username |
| `authorName` | string | null | Display name |
| `authorIsVerified` | boolean | null | Blue-tick status |
| `likeCount` | number | null | Likes on the comment |
| `postedAt` | string | null | ISO 8601 UTC |
| `isPinned` | boolean | null | Pinned by the account owner |

***

### Pricing

Charged per account looked up, per row delivered, plus a small fixed fee when a run starts. Cheaper on higher Apify plans.

| Event | Free plan | Business plan |
|---|---|---|
| Account looked up | $2.50 per 1,000 | **$1.50 per 1,000** |
| Post | $1.60 per 1,000 | **$0.99 per 1,000** |
| Comment | $0.89 per 1,000 | **$0.49 per 1,000** |
| Stories checked | $12.00 per 1,000 | **$8.00 per 1,000** |
| Run start | $0.005 | $0.005 |

**Why accounts are charged separately.** Looking up an account costs the same whether it turns out to have 500 posts, no posts, or not to exist at all. Charging for the lookup keeps the per-post price low for everyone instead of loading the cost of dead handles onto customers with clean lists. Entries that are not valid usernames are rejected before any lookup happens and cost you nothing.

**Stories are charged per account checked, not per Story.** One check covers an account however many Stories it has — including none, which is common, since Stories expire after 24 hours.

#### Worked examples, at Business-plan rates

**Daily watch — 10 accounts, 12 posts each, no comments**
10 × $0.0015 + 120 × $0.00099 + $0.005 = **$0.14**

**Deep pull — 10 accounts, 24 posts each, comments on the newest 5 posts (~50 each)**
10 × $0.0015 + 240 × $0.00099 + 2,500 × $0.00049 + $0.005 = **$1.48**

**Stories sweep — 50 accounts, Stories only, 12 posts each**
50 × $0.0015 + 50 × $0.008 + 600 × $0.00099 + $0.005 = **$1.07**

Set **Maximum cost per run** in the run options to cap spend. The Actor stops cleanly at that ceiling and returns everything it charged for.

***

### Tips

- **Start with Stories and comments off.** Get the posts first, see which accounts and posts matter, then re-run narrowly with comments enabled.
- **Five posts per account is usually enough** to read the room on comments, and keeps runs quick.
- **Schedule it for Stories.** A daily or twice-daily schedule catches them while they are live.
- **Keep your username list current.** Every account you supply is looked up, so a tidy list keeps runs lean.

***

### Support

Report a problem through the **Issues** tab on this Actor's page. Issues are reviewed regularly.

When reporting, please include the run ID, the input you used, and what you expected.

For volume enquiries, custom requirements, or anything not specific to a single run, email **socialhtz@gmail.com**.

# Actor input Schema

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

The accounts to scrape. A username, an @handle or a full profile URL all work — you supply the accounts you want to track. Entries that are not valid usernames are skipped and cost you nothing. Up to 200 accounts per run.

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

How many recent posts to retrieve for each account. Posts arrive in pages of 12, so a value of 20 makes two requests. Set to 12 for the cheapest useful run.

## `includeComments` (type: `boolean`):

Retrieves comments for each post, up to 500 per post, paged automatically. Top-level comments, charged separately, and they add to run time.

## `maxPostsToComment` (type: `integer`):

Applies when comment threads are included. Limits how many of each account's posts have their comments retrieved, newest first. Posts beyond this limit are still returned, marked as skipped.

## `maxCommentsPerPost` (type: `integer`):

Applies when comment threads are included. Comments arrive roughly 13 at a time, so higher values mean more requests and a longer run. Each record reports whether further comments remain.

## `includeStories` (type: `boolean`):

Captures the account's currently live Stories, with media type, dimensions and media count. Charged once per account checked, however many Stories it has — including none, since Stories expire after 24 hours.

## Actor input object example

```json
{
  "usernames": [
    "nasa"
  ],
  "maxPostsPerAccount": 12,
  "includeComments": false,
  "maxPostsToComment": 5,
  "maxCommentsPerPost": 50,
  "includeStories": false
}
```

# Actor output Schema

## `results` (type: `string`):

Every record returned by this run — one per post, one per Story, and one for any account that produced nothing.

# 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": [
        "nasa"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("socialhz/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": ["nasa"] }

# Run the Actor and wait for it to finish
run = client.actor("socialhz/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": [
    "nasa"
  ]
}' |
apify call socialhz/instagram-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,socialhz/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/YRYVe9aimoMzGFCSC/builds/BtgkMsUdl55Up4Lmz/openapi.json
