# Instagram Scraper: Profiles, Posts, Reels, Comments, Locations (`fanndev/instagram-content-scraper`) Actor

Scrape public Instagram data without a login: account profiles and bio links, the profile grid, the reels tab with play counts, single posts with engagement and comments, and location pages with the media posted there. HTTP-only, no browser.

- **URL**: https://apify.com/fanndev/instagram-content-scraper.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.20 / 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?

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 Public Content Scraper

Scrape public Instagram data without a login and without a browser: **account
profiles**, the **profile grid**, the **reels tab with play counts**, **single
posts with engagement and comments**, and **location pages** with the media
posted there.

Pure HTTP. No Playwright, no Selenium, no cookies to supply, no account.

***

### Modes

Pick a `mode`, then give `targets` that match it.

| Mode | Targets | One row per | Paginates |
| --- | --- | --- | --- |
| `profile` | usernames or profile URLs | account | — |
| `posts` | usernames or profile URLs | post | ✅ unlimited |
| `reels` | usernames or profile URLs | reel | ✅ unlimited |
| `post_detail` | post/reel URLs or shortcodes | post | comments capped (below) |
| `location` | location ids or `/explore/locations/` URLs | media | ❌ one page |

```json
{
  "mode": "reels",
  "targets": ["nasa", "https://www.instagram.com/natgeo/"],
  "maxItemsPerTarget": 60,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

***

### Read this before you pick a mode: the surfaces publish different fields

Instagram serves the same post with different fields depending on which page it
came from. This is the most important thing to know about the output:

| | `posts` | `reels` | `post_detail` | `location` |
| --- | --- | --- | --- | --- |
| Likes / comments | ❌ | ✅ | ✅ | ✅ |
| **Play count** | ❌ | ✅ | ❌ | ❌ |
| Caption, hashtags | ✅ | ❌ | ✅ | ✅ |
| Publish date | ❌ | ❌ | ✅ | ✅ |
| Video file URL | ❌ | ❌ | ✅ | ✅ |
| Comments | ❌ | ❌ | ✅ | ❌ |

**A field this actor could not get is `null`, never `0`.** A zero would silently
wreck any average you compute. Rows affected by a surface limit also carry a
`_warning` naming it and pointing at the mode that does publish the field.

The usual pattern: run `posts` or `reels` to enumerate an account cheaply, then
feed the shortcodes you care about into `post_detail` for full engagement,
timestamps and comments.

***

### What it cannot do

Stated plainly, because these are platform limits, not bugs, and no setting
works around them:

- **Hashtag scraping.** `/explore/tags/<tag>/` serves a sign-in page to
  logged-out visitors.
- **Search.** Both the search page and the search API require a login.
- **Tagged-posts tab**, followers/following lists, stories, and the contents of
  story highlights — all login-walled. (Highlight *titles and covers* do come
  through in `profile`.)
- **Deep comments.** Instagram ships only the first ~13–15 comments to a
  logged-out viewer, whatever the post's real total. Its comment pagination
  endpoint requires a login. `commentCount` is the true total; `commentsScraped`
  is how many you actually got.
- **Location pagination.** A location page serves one batch — about 63 media on
  the `recent` tab, 27 on `ranked` — and ignores any cursor. That is the whole
  harvest for a place, however large `maxItemsPerTarget` is.
- **Private accounts.** Profile metadata is public; media is not. You get a
  `profile` row, and a `PRIVATE_ACCOUNT` diagnostic row from the media modes.

Also: Instagram Shop / product-catalog data does not exist on any public
surface any more, so no mode here returns it. Note that `productType` values
(`feed`, `clips`, `igtv`) are **media formats, not shopping products** — an
easy field to misread.

***

### Output

Every row carries the same envelope, so a mixed dataset stays queryable:

| Field | Meaning |
| --- | --- |
| `_mode` | which surface produced the row |
| `_input` | the target it belongs to |
| `_source` | `ssr-relay-island` (page 1, from the page itself) or `api-graphql-replay` (a paginated request) |
| `_page` | which page of the connection |
| `_scrapedAt` | UTC ISO 8601 |
| `_warning` | a surface limit affecting this row |
| `_error` / `_errorDetail` | set only on failure rows |

**Every input produces at least one row.** A target that fails yields a
diagnostic row rather than vanishing, so downstream joins stay idempotent:
`INVALID_INPUT`, `NOT_FOUND`, `PRIVATE_ACCOUNT`, `GATED_FOR_LOGGED_OUT`,
`BLOCKED_AFTER_RETRIES`, `EMPTY_RESULT`, `UPSTREAM_SHAPE_CHANGED`.

A `reels` row:

```json
{
  "_mode": "reels", "_input": "nasa", "_source": "ssr-relay-island", "_page": 1,
  "shortcode": "DdEsW1ItqpI",
  "url": "https://www.instagram.com/p/DdEsW1ItqpI/",
  "mediaType": "VIDEO", "productType": "clips",
  "likeCount": 94368, "commentCount": 1039, "playCount": 3992126,
  "imageUrl": "https://scontent.cdninstagram.com/...",
  "ownerUsername": "nasa", "ownerId": "4092263381",
  "_warning": "caption_and_timestamp_not_published_on_this_surface: ..."
}
```

Instagram CDN URLs (`imageUrl`, `videoUrl`, `profilePicUrl`) are **signed and
expire** — download anything you need to keep, promptly.

***

### How it works, and why it keeps working

Instagram's public REST API (`/api/v1/...`) returns **HTTP 401
`require_login`** to anonymous callers as of September 2026 — identically
across five TLS fingerprints, so it is an auth rollout rather than a bot
defence. This actor does not use it.

Instead it reads the **logged-out server-rendered Relay payload**: warm the
entity page, harvest the query id Instagram ships inline next to the data, and
replay it against `/api/graphql/` for pagination.

Because the query id is read from the page being scraped rather than pinned in
the source, **the usual persisted-query rot does not apply** — when Meta
rotates its ids, the next request picks up the new one automatically.

Full evidence, the measurements behind every limit above, and the traps worth
knowing (`/graphql/query` returns `200 OK` with null data; `"404"` appears in
healthy pages) are in [CRAWLING\_METHOD.md](CRAWLING_METHOD.md).

***

### Proxy and pacing

Instagram throttles anonymous traffic **per IP** and signals it with a 200
carrying a login wall rather than a 429. **Residential proxy is strongly
recommended.** Without one, expect a handful of targets to succeed and then
login walls; datacenter ranges are throttled sooner. On a block the actor
rotates both the TLS profile and the proxy session, then retries with
exponential backoff.

`delaySeconds` (default 2) paces targets. Lower it only behind rotating
residential proxy — pacing keeps a run alive considerably longer than
hammering does.

***

### Legal

Scrapes only data Instagram serves to a logged-out visitor. No login, no
credential use, no access to private accounts or restricted content. You are
responsible for how you use the output, including under the GDPR/CCPA where
personal data is involved.

# Actor input Schema

## `mode` (type: `string`):

Which Instagram surface to scrape. 'profile' = account details, bio links and highlight titles. 'posts' = the profile grid (fast, but Instagram does not publish like/comment counts on this surface). 'reels' = the reels tab, which DOES carry like, comment and play counts. 'post\_detail' = one post or reel with full engagement plus the comments Instagram serves logged-out viewers. 'location' = a place's page: its address and coordinates plus the media posted there, with full engagement.

## `targets` (type: `array`):

What to scrape, matched to the mode. For profile/posts/reels: usernames, @handles or profile URLs (e.g. "nasa", "https://www.instagram.com/nasa/"). For post\_detail: post or reel URLs or bare shortcodes (e.g. "https://www.instagram.com/p/DcOX3hWFiey/"). For location: a numeric location id or an /explore/locations/ URL (e.g. "212988663").

## `maxItemsPerTarget` (type: `integer`):

Cap on media rows per target for the posts, reels and location modes. Instagram serves 12 media per page for posts and reels, so 60 is five pages. Ignored by the profile and post\_detail modes, which always produce exactly one row per target. The location mode cannot paginate at all (see the README), so values above roughly 63 have no effect there.

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

Attach the comments Instagram server-renders for logged-out viewers to each post\_detail row. This is roughly the first 13 comments regardless of how many the post has - Instagram's comment pagination endpoint requires a login, so deeper comment scraping is not possible anonymously. Costs no extra request, since the comments arrive with the page.

## `locationTab` (type: `string`):

Which tab of a location page to read. 'recent' returns the most media (about 63 in testing) and is the better choice for monitoring. 'ranked' is what Instagram server-renders by default and returns fewer (about 27), ordered by its own popularity ranking, and saves one request.

## `delaySeconds` (type: `integer`):

Seconds to pause between targets. Instagram throttles anonymous traffic per IP and answers with a login wall rather than an HTTP 429, so pacing keeps a run alive considerably longer than hammering does. Lower this only when running behind rotating residential proxy.

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

Proxy settings. Residential proxy is strongly recommended: Instagram rate-limits anonymous traffic by IP, and datacenter ranges are throttled far sooner. A run without a proxy works for a handful of targets and then starts collecting login walls.

## Actor input object example

```json
{
  "mode": "profile",
  "targets": [
    "nasa",
    "natgeo"
  ],
  "maxItemsPerTarget": 60,
  "includeComments": true,
  "locationTab": "recent",
  "delaySeconds": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

Scraped profiles, posts, reels, comments or location 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 = {
    "targets": [
        "nasa"
    ]
};

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

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

```

## MCP server setup

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