# Instagram Reels Scraper (Pay-Per-Event) (`prodiger/instagram-reels-scraper`) Actor

Scrape Instagram Reels by profile, user ID, or direct reel URL. Returns all four Instagram play counters plus a playCountSource field, so view numbers never come back silently empty. Run-level dedup means you are never billed twice for the same reel.

- **URL**: https://apify.com/prodiger/instagram-reels-scraper.md
- **Developed by:** [Arnas](https://apify.com/prodiger) (community)
- **Categories:** Social media, Videos
- **Stats:** 7 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 reels

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 Reels Scraper

Scrape Instagram Reels by **profile**, **numeric user ID**, or **direct reel URL**. HTTP-only, with
no login or user-supplied cookies. Pay-per-event: you pay for reels that actually land in your
dataset.

### What you get

Every row carries the full reel record — engagement, media URLs, audio, music attribution,
tagged users, location, paid-partnership flags, and timestamps. See the dataset schema for all
41 fields.

#### View counts that don't silently come back empty

Instagram deprecated `view_count` and now populates its play counters inconsistently depending on
which surface serves the response. Scrapers that read one field return `null` views on a large
share of reels.

This actor emits **all four** counters on every row —

| Field            | Instagram source                  |
| ---------------- | --------------------------------- |
| `videoPlayCount` | `play_count`                      |
| `igPlayCount`    | `ig_play_count`                   |
| `videoViewCount` | `view_count` (legacy, often null) |
| `fbPlayCount`    | `fb_play_count`                   |

— plus **`playCountSource`**, which names the field the number came from. If Instagram returned no
counter at all, `playCountSource` is `null` and you know it's missing rather than zero.

#### Highest-resolution video, explicitly

`videoUrl` is selected by pixel count across every rendition Instagram offers, so you get the
1080p file when one exists instead of whichever rendition happened to be listed first.

#### Run-level dedup and event-aware billing

Reels are deduplicated by Instagram media ID across the whole run. Pass a profile and one of its
reel URLs in the same input and you are charged once. Output admission is serialized, and each row
uses Apify's event-aware dataset write so budget admission, storage, and the `reel` charge result
share one SDK operation. Failed writes remain retryable, and no later row is admitted after the
charge limit is reached.

### Input

| Field                | Type    | Default           | Notes                                                                             |
| -------------------- | ------- | ----------------- | --------------------------------------------------------------------------------- |
| `username`           | array   | —                 | **Required.** Usernames, profile URLs, numeric user IDs, or direct reel URLs.     |
| `resultsLimit`       | integer | 25                | Reels per profile. Ignored for direct reel URLs.                                  |
| `onlyPostsNewerThan` | string  | —                 | `YYYY-MM-DD`, a full ISO timestamp, or relative (`3 days`, `2 weeks`). UTC.       |
| `skipPinnedPosts`    | boolean | false             | Exclude reels pinned to the top of the profile.                                   |
| `skipTrialReels`     | boolean | false             | Exclude trial reels. Reels whose trial status Instagram does not report are kept. |
| `customMapFunction`  | string  | —                 | Arrow function applied to each row before storage.                                |
| `maxRequestRetries`  | integer | 5                 | Retries on a fresh session when blocked.                                          |
| `proxyConfiguration` | object  | Apify RESIDENTIAL | Instagram blocks datacenter IPs.                                                  |

```json
{
  "username": ["natgeo", "https://www.instagram.com/nasa/"],
  "resultsLimit": 50,
  "onlyPostsNewerThan": "3 months",
  "skipPinnedPosts": true
}
```

#### Migrating from `apify/instagram-reel-scraper`

Input field names and semantics match, so most runs transfer unchanged. Differences:

- `username` additionally accepts **numeric user IDs**.
- `includeSharesCount`, `includeTranscript`, and `includeDownloadedVideo` are **not implemented**
  here, so those add-on charges do not exist. Core reel pricing is identical.
- Extra fields on every row: `playCountSource`, `igPlayCount`, `fbPlayCount`, `fbLikeCount`,
  `reshareCount`, `isTrialReel`, `audioUrl`, `scrapedAt`.

### Output

```json
{
  "id": "3512345678901234567",
  "type": "Video",
  "shortCode": "DAbCdEfGhIj",
  "url": "https://www.instagram.com/reel/DAbCdEfGhIj/",
  "caption": "Sunset over #Lisbon",
  "hashtags": ["lisbon"],
  "likesCount": 1234,
  "commentsCount": 42,
  "videoPlayCount": 98765,
  "igPlayCount": 98765,
  "videoViewCount": null,
  "playCountSource": "play_count",
  "videoDuration": 17.5,
  "videoUrl": "https://.../1080.mp4",
  "displayUrl": "https://.../thumb.jpg",
  "ownerUsername": "natgeo",
  "timestamp": "2026-08-12T14:20:00.000Z"
}
```

### Pricing

Pay-per-event, identical to the reference actor's core tier schedule:

| Apify plan | Actor start | Per reel |
| ---------- | ----------- | -------- |
| Free       | $0.001      | $0.0026  |
| Bronze     | $0.001      | $0.0023  |
| Silver     | $0.001      | $0.0014  |
| Gold       | $0.001      | $0.0010  |
| Platinum   | $0.001      | $0.0008  |
| Diamond    | $0.001      | $0.0004  |

On the Free plan, 1,000 reels in one run cost **$2.60 + $0.001**. Rows skipped by a filter, by
dedup, or by a failed push are not charged.

### How it works

The actor first opens a normal public Instagram page to obtain an ephemeral logged-out session.
Profile pages expose the numeric user ID in their server-rendered Relay data. In that same
proxy/session, the actor pages Instagram's unified profile feed and keeps only
`product_type: "clips"`. Each page gets a fresh bootstrap, so a blocked cookie can rotate cleanly
instead of poisoning every retry.

Direct reel URLs are parsed from the public page's server-rendered media object. The actor also
checks the owner's current feed for richer play counters when the reel is recent enough to appear
there; failure of this optional enrichment never discards the reel.

Residential proxies are required: Instagram blocks datacenter IP ranges.

### Limitations

- **Public profiles only.** Private accounts are skipped with a warning; there is no login.
- A `/p/` or `/reel/` URL pointing at a photo rather than a reel is reported and skipped.
- `isTrialReel` is `null` when Instagram does not report trial status; `skipTrialReels` then keeps
  the reel rather than guessing.
- No transcript, video download, or shares-count add-ons.

# Actor input Schema

## `username` (type: `array`):

Add Instagram usernames, profile URLs, numeric user IDs, or direct reel URLs. Each item is processed individually.

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

How many reels to scrape per profile. Ignored when scraping direct reel URLs.

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

Scrape reels from the provided date up to the present. Supported formats: `YYYY-MM-DD`, a full ISO timestamp (e.g. `2026-08-01T10:02:01`), or relative time such as `1 day`, `2 weeks`, `3 months`. All times are evaluated in UTC.

## `skipPinnedPosts` (type: `boolean`):

Exclude reels pinned to the top of the profile. Pinned reels are often much older than the rest, so skipping them keeps results chronologically accurate.

## `skipTrialReels` (type: `boolean`):

Exclude trial reels — an Instagram feature creators use to test a video with non-followers before sharing it with their existing audience. Reels whose trial status Instagram does not report are always kept.

## `customMapFunction` (type: `string`):

A JavaScript arrow function applied to every reel before it is stored, e.g. <code>(object) => ({ url: object.url, plays: object.videoPlayCount })</code>. Errors fall back to the unmapped row.

## `maxRequestRetries` (type: `integer`):

How many times a blocked or failed request is retried on a fresh session.

## `proxyConfiguration` (type: `object`):

Instagram blocks datacenter IPs, so residential proxies are required. Leave the default unless you know what you are changing.

## Actor input object example

```json
{
  "username": [
    "natgeo"
  ],
  "resultsLimit": 25,
  "skipPinnedPosts": false,
  "skipTrialReels": false,
  "customMapFunction": "(object) => { return {...object} }",
  "maxRequestRetries": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing all scraped reels

# 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 = {
    "username": [
        "natgeo"
    ],
    "resultsLimit": 25,
    "customMapFunction": (object) => { return {...object} }
};

// Run the Actor and wait for it to finish
const run = await client.actor("prodiger/instagram-reels-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 = {
    "username": ["natgeo"],
    "resultsLimit": 25,
    "customMapFunction": "(object) => { return {...object} }",
}

# Run the Actor and wait for it to finish
run = client.actor("prodiger/instagram-reels-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 '{
  "username": [
    "natgeo"
  ],
  "resultsLimit": 25,
  "customMapFunction": "(object) => { return {...object} }"
}' |
apify call prodiger/instagram-reels-scraper --silent --output-dataset

```

## MCP server setup

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