# Instagram Profile Scraper (`omaraw/instagram-profile-scraper`) Actor

Scrape Instagram profiles by handle: followers, follows, post count, business category and engagement averages, one row per account with its latest posts nested. $1.95 per 1,000 profiles: 25% below the Free plan price of apify/instagram-profile-scraper, and the same flat rate on every plan.

- **URL**: https://apify.com/omaraw/instagram-profile-scraper.md
- **Developed by:** [itnlab](https://apify.com/omaraw) (community)
- **Stats:** 2 total users, 1 monthly users, 80.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 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.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Instagram Profile Scraper

Give it Instagram handles and get **one row per account** — followers, follows, post count, verification, business category, engagement averages — with the account's **latest posts nested inside the same row**, at a flat **$1.75 per 1,000 profiles** for every buyer.

This is the narrow Actor of the three. If you want post rows, reel rows or comment rows, use **Instagram Scraper** or **Instagram Comment Scraper with Sentiment**, which are the same collector wearing a different form. What this one sells is the account: paste handles, get a table you can sort by followers.

### What you get

- **The account's own figures** — `followersCount`, `followsCount`, `postsCount`, `verified`, `private`, `isBusinessAccount`, `businessCategoryName`, `fullName`, `id`, `url`.
- **Engagement, computed rather than claimed** — `avgLikes`, `avgComments`, `avgVideoViews` and `engagementRatePct`, worked out over the posts actually collected. `postsInSnapshot` says how many that was, so you can tell an average over 12 posts from an average over 200.
- **`latestPosts`** — the recent posts nested in the row: caption, `hashtags`, `mentions`, `timestamp`, `likesCount`, `commentsCount`, `taggedUsers`, dimensions, and the media URLs when you ask for them.
- **The account as it presents itself** — `biography`, `profilePicUrl`, `externalUrl`, `externalUrls` (the full link list, each with its title) and `highlightReelCount`. Collected since 14 September 2026, under a data-controller decision described below.
- **`relatedProfiles`** — the accounts Instagram suggests beside the profile: handle, name, id, verified and private flags, avatar. **Off by default** (`includeRelatedProfiles`), because it costs an extra request per account and the accounts in it are third parties nobody named.
- **`collectedAt`** — when the snapshot behind the row was taken. Rows are served from a stored snapshot rather than fetched at read time, so this is the honest age of the data.
- **A row for every handle, including the ones that failed** — a handle that does not resolve writes a row carrying `inputUrl`, `error` and `errorDescription` rather than disappearing from the dataset. Fifty handles in means fifty rows out.

### Input

```json
{
  "usernames": ["nasa", "natgeo"],
  "resultsLimit": 12
}
```

`apify/instagram-profile-scraper`'s published form is two fields — `usernames` and `includeAboutSection` — and its own example input runs here unchanged. Both are on this form; `tests/test_actor_variants.py` keeps a copy of that object so the day either side moves, the suite says so.

`usernames` takes handles or profile URLs; both spellings work. `resultsLimit` is how many posts to nest per account. `onlyPostsNewerThan` drops nested posts older than a date or a span like `"3 days"`, and the profile row still comes back either way — an account with nothing recent is still an account. `includeMediaUrls` adds the signed image and video URLs, which expire within days and are withheld unless asked for. `includeRelatedProfiles` adds the suggested-accounts list, at one extra request per account. `enrichPosts` is on by default and is what gives each nested post its `timestamp`, `likesCount`, `commentsCount`, dimensions and `taggedUsers`: Instagram's logged-out grid carries none of them, so they are read from each post's own page at one request per post. Turn it off for a faster, thinner listing.

### Output

One row per account:

```json
{
  "dataType": "profile",
  "id": "528817151",
  "username": "nasa",
  "fullName": "NASA",
  "url": "https://www.instagram.com/nasa/",
  "verified": true,
  "private": false,
  "isBusinessAccount": true,
  "businessCategoryName": "Government Organization",
  "followersCount": 97400000,
  "followsCount": 78,
  "postsCount": 4210,
  "postsInSnapshot": 12,
  "avgLikes": 412903,
  "avgComments": 1363,
  "engagementRatePct": 0.42,
  "biography": "Exploring the universe and our home planet.",
  "profilePicUrl": "https://scontent.cdninstagram.com/v/t51.2885-19/a1_n.jpg",
  "externalUrl": "http://nasa.gov",
  "externalUrls": [{"url": "http://nasa.gov", "title": "NASA"}],
  "highlightReelCount": 14,
  "latestPosts": [
    {
      "shortCode": "DCMUrLltVlM",
      "type": "Image",
      "caption": "A new view of the Carina Nebula.",
      "hashtags": ["webb"],
      "timestamp": "2026-08-30T18:02:11.000Z",
      "likesCount": 412903,
      "commentsCount": 1363
    }
  ],
  "collectedAt": "2026-08-30T21:14:05.000Z",
  "inputUrl": "nasa"
}
```

### What it does not return, and why that is written down

A figure the collected surface did not carry is an **absent column** — never `0`, never a stand-in. `postsCount` missing means nobody measured it, not that the account has no posts, and `latestPosts` is absent rather than `[]` when no posts were collected.

**What a nested post carries depends on where it was read.** Instagram's logged-out profile grid does not send per-post like counts, comment counts or timestamps, so those columns are left out of the nested entries rather than written as `0` — and with no likes to average, `avgLikes`, `avgComments` and `engagementRatePct` come back absent too. They are absent because nobody measured them, not because the account has no engagement. A post fetched by its own URL carries the full set, which is what **Instagram Scraper** does when you give it post URLs.

**`biography`, `profilePicUrl`, `externalUrl`, `externalUrls` and `highlightReelCount` were on this list until 14 September 2026** and are now collected. The data controller's decision was to match what `apify/instagram-profile-scraper` publishes and not one field further, so that a buyer switching does not lose the bio — and so that the line where collection stops is a stated purpose rather than the shape of a payload. The compliance file carries the dated row, the amended legitimate-interest assessment and the field list.

What the reference publishes and this Actor still does not:

- **`public_email` and `public_phone_number`** — they arrive in the same payload as everything above and are not published here. The reference Actor does not publish them either, which is the ceiling this listing was aligned to; "it was in the response" has never been a reason to keep something.
- **`profilePicUrlHD`, `businessAddress`, `fbid`** — excluded by an explicit data-protection decision recorded in this project's compliance file, not by an oversight.
- **`includeAboutSection`** — the paid About-this-account panel: date joined, country of registration, broadcast channel. None of it is on the surfaces this Actor reads, so sending the flag is **refused with that sentence** rather than accepted and billed for an empty panel. It is the one paid add-on the reference sells on top of its per-profile rate.
- **Stories, hashtag feeds and `/tagged/` URLs** — outside the perimeter. They are refused with an explanation rather than answered with an empty result set, because `[]` reads as "there is nothing there", which is a different and false claim.

### How it collects

Public, logged-out surfaces only: no login, no cookies, no session. That is what makes the perimeter above real, and it is also why the figures that *are* here can be trusted — they are what any visitor to the page would see.

### How many posts get nested

`resultsLimit` is how many posts to nest per account, and a run returns that many or every post the account has, whichever is smaller. The profile row comes back either way — an account with nothing to nest is still an account.

### Pricing

**$1.75 per 1,000 profiles** ($0.00175 each), pay-per-event.

**One flat rate, for everybody.** $1.75 per 1,000 — no plans, no tiers, no volume brackets, and no Apify platform usage charged on top. That is 32% below the $2.60 that `apify/instagram-profile-scraper` charges its Free-plan buyers (read from Apify's public API, 17 September 2026); their rate moves with the subscription a buyer is on, and ours does not move at all.

One thing a price comparison leaves out and an invoice does not: theirs charges the About-this-account panel as a **separate add-on**, at $7.00 per 1,000 on their Free plan. Here there is no add-on, because this Actor does not collect that panel at all.

A result is a row: **one account is one result**, however many posts are nested in `latestPosts`. The nested posts are part of the row, not rows of their own, and are not charged separately.

# Changelog

This Actor's version history is a separate document: https://apify.com/omaraw/instagram-profile-scraper/changelog.md

# Actor input Schema

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

The accounts to collect, as handles (nasa) or profile URLs (https://www.instagram.com/nasa/). One row comes back per account, with its recent posts nested in latestPosts. Post, hashtag, place, story and /tagged/ URLs are refused with an explanation rather than collected: this Actor returns accounts.

## `resultsLimit` (type: `integer`):

How many of each account's most recent posts to nest in latestPosts. The profile's own figures - followers, follows, postsCount - are the account's totals and do not depend on this number; the engagement averages are computed over the posts actually collected, so a larger value makes them steadier and a slower run.

## `enrichPosts` (type: `boolean`):

Read each post's own page for the fields Instagram's profile grid does not carry: timestamp, likesCount, commentsCount, videoViewCount, dimensions and taggedUsers (the accounts tagged IN the picture, which no caption mentions). Leave it on for complete rows. Turning it off makes a listing about ten times cheaper in requests and leaves those fields null.

## `includeRelatedProfiles` (type: `boolean`):

Also collect the accounts Instagram suggests alongside each profile, published as relatedProfiles: handle, name, id, verified and private flags, avatar. It costs one extra request per profile, and the accounts in it are third parties you did not name — which is why it is off by default rather than always on.

## `onlyPostsNewerThan` (type: `string`):

Drop nested posts older than this date, as ISO 8601 (2026-07-01) or a relative span ("3 days", "2 weeks"). The profile row itself is returned either way - an account with nothing recent is still an account.

## `includeMediaUrls` (type: `boolean`):

Add the signed image and video URLs to each nested post. They expire, typically within days, so they are withheld unless asked for.

## `includeAboutSection` (type: `boolean`):

Not available, and the run stops instead of pretending. This is the paid extra apify/instagram-profile-scraper sells: the date an account joined, the country it registered from, and its former usernames. Measured on the logged-out payload on 2026-09-14 - none of those three keys is there, so a run that accepted this would bill you for a panel that could only come back empty. Ticking it fails input validation immediately, before anything is collected and before anything is charged. The field is on this form so that you find that out here, in a second, rather than from a column of nulls.

## Actor input object example

```json
{
  "usernames": [
    "nasa",
    "natgeo"
  ],
  "resultsLimit": 12,
  "enrichPosts": true,
  "includeRelatedProfiles": false,
  "includeMediaUrls": false,
  "includeAboutSection": false
}
```

# Actor output Schema

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

One row per collected profile.

# 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",
        "natgeo"
    ]
};

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

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

```

## MCP server setup

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