# Instagram Post Scraper - $0.09 per 1,000 Posts (`dami_studio/instagram-post-scraper-v1`) Actor

Paste Instagram post or reel URLs, or usernames, and get one row per post: caption, likes, comments, publish time, every carousel image and video URL, owner, hashtags, mentions, tagged users. No login, no cookies. $0.09 per 1,000 posts - the cheapest Instagram post scraper on the market.

- **URL**: https://apify.com/dami\_studio/instagram-post-scraper-v1.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.09 / 1,000 post scrapeds

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/platform/actors/running/actors-in-store#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 Post Scraper

Paste Instagram post or reel URLs — or just usernames — and get one clean row per post: the full
caption, like and comment counts, the publish timestamp, every image and video URL in a carousel,
the owner, hashtags, mentions and tagged users.

No Instagram account. No cookies. No session token. No captcha to solve. No proxy of your own.

### Price

**$0.09 per 1,000 posts**, plus $0.0000125 to start a run.

This is the cheapest Instagram post scraper on the market, and the rate is flat: the same $0.09 on
the free plan as on any paid plan, with no volume tiers and no minimum spend, so it is what you pay
from your very first run.

| Posts | Cost |
|---|---|
| 100 | $0.009 |
| 1,000 | $0.09 |
| 10,000 | $0.90 |
| 100,000 | $9.00 |

#### What actually bills

- **`post-scraped` — $0.00009.** Charged once per genuine post row written to the dataset. That is
  the only per-result charge; there is no separate "filtered out" fee, no "restricted profile" fee
  and no dataset-item fee stacked on top.
- **`apify-actor-start` — $0.0000125.** Charged once per run, per GB of run memory. The Actor runs
  at 1 GB by default, so that is $0.0000125 a run.

Free, always:

- The **sample row** you get from an empty run.
- Every **diagnostic row** — rate limits, private accounts, deleted posts, bad input. If Instagram
  gives nothing back, you pay the start fee and nothing else.
- Posts dropped by your own `onlyPostsNewerThan` or `skipPinnedPosts` filters. They are fetched and
  thrown away at our expense, not billed to you.

There is no free tier and no free trial. A run that returns 40 posts costs 40 × $0.00009 = $0.0036,
plus the start fee. That is the whole of it.

### Input

Two ways in, and you can mix them in one run.

```json
{
  "username": ["nasa", "https://www.instagram.com/natgeo/"],
  "resultsLimit": 12,
  "onlyPostsNewerThan": "7 days"
}
```

```json
{
  "postUrls": [
    "https://www.instagram.com/p/DcA6fTgIJ97/",
    "https://www.instagram.com/reel/Db_sQbigDOA/",
    "DcCUKZoAS8h"
  ]
}
```

| Field | What it does |
|---|---|
| `username` | Usernames, profile URLs, or post URLs — mixed freely. The field name follows the common convention used across Instagram post Actors, so an input written for one of those normally runs here unchanged. |
| `postUrls` | Post, reel or TV URLs. A bare shortcode such as `DcCUKZoAS8h` works too. `directUrls` and `startUrls` are accepted as aliases. |
| `resultsLimit` | Posts to take from each profile. Default 20, but logged-out Instagram serves about 12 — see Limits. Ignored when you pass post URLs. |
| `maxItems` | Hard cap on charged rows for the whole run, across every input. |
| `onlyPostsNewerThan` | `2026-01-01`, a full ISO timestamp, or a relative value like `3 days`, `2 months`, `1 year`. Older posts are dropped and never charged. |
| `skipPinnedPosts` | Drop the posts a profile has pinned to the top of its grid. Detectable in profile mode only. |
| `concurrency` | Posts fetched at once. Default 6. |
| `requestDelayMs` | Optional pause after each request. Default none. |
| `proxyConfiguration` | Leave it alone unless you have a reason. Default is rotating Apify datacenter addresses, which is what works here and costs nothing per GB. |

Run it with no input at all and you get one labelled sample row, uncharged, showing every field —
so you can see the shape before spending anything.

### Output

One row per post. A real row, from a run on `natgeo` on 15 August 2026 (the signed query strings on
the CDN URLs are shortened here to `?<signed>` for readability — the real output carries them in
full):

```json
{
  "ok": true,
  "inputUrl": "https://www.instagram.com/natgeo/",
  "id": "3963424910027562875",
  "shortCode": "DcA6fTgIJ97",
  "url": "https://www.instagram.com/p/DcA6fTgIJ97/",
  "type": "Sidecar",
  "productType": "carousel_container",
  "isReel": false,
  "caption": "Photos by @joelsartore | Out of 33 species of jerboa, the long-eared jerboa wins the award for most oddly proportioned family member. ... #PhotoArk",
  "hashtags": ["PhotoArk"],
  "mentions": ["joelsartore", "moscow_zoo_official"],
  "likesCount": 42094,
  "commentsCount": 180,
  "likesAndViewCountsDisabled": false,
  "isCommentsDisabled": false,
  "timestamp": "2026-08-14T19:00:01.000Z",
  "timestampSource": "instagram",
  "displayUrl": "https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/766106700_18686094097019133_2576347057804717679_n.jpg?<signed>",
  "videoUrl": null,
  "videoDuration": null,
  "images": [
    "https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/766106700_18686094097019133_2576347057804717679_n.jpg?<signed>",
    "https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/774970374_18686094109019133_2450257321358972437_n.jpg?<signed>"
  ],
  "childPosts": [
    {
      "id": "3963424850577470287",
      "shortCode": "DcA6ecIoDNP",
      "type": "Image",
      "displayUrl": "https://scontent-atl3-1.cdninstagram.com/v/...jpg?<signed>",
      "videoUrl": null,
      "alt": "Photo by National Geographic on August 14, 2026. May be an image of rodent and text.",
      "width": 1440,
      "height": 960
    },
    {
      "id": "3963424852959856137",
      "shortCode": "DcA6eeWoIYJ",
      "type": "Image",
      "displayUrl": "https://scontent-atl3-1.cdninstagram.com/v/...jpg?<signed>",
      "videoUrl": null,
      "alt": "Photo by National Geographic on August 14, 2026. May be an image of rodent and text.",
      "width": 1440,
      "height": 960
    }
  ],
  "childPostsCount": 2,
  "dimensionsWidth": 1440,
  "dimensionsHeight": 960,
  "alt": "Photo by National Geographic on August 14, 2026. May be an image of text.",
  "ownerId": "787132",
  "ownerUsername": "natgeo",
  "ownerFullName": "National Geographic",
  "ownerIsVerified": true,
  "ownerIsPrivate": false,
  "ownerProfileUrl": "https://www.instagram.com/natgeo/",
  "coauthorProducers": [],
  "taggedUsers": [],
  "locationName": null,
  "locationId": null,
  "musicArtist": null,
  "musicTitle": null,
  "source": "post-page",
  "scrapedAt": "2026-08-15T05:43:15.443Z"
}
```

`images` is the flat list of every media URL in the post — one entry for a single image or video,
one per slide for a carousel. `childPosts` is the same thing with the per-slide detail: its own id,
shortcode, type, dimensions and alt text. A 20-slide NASA carousel came back with all 20.

`type` is `Image`, `Video` or `Sidecar` (carousel) — the conventional naming for Instagram post
data, so existing pipelines do not need remapping.

#### Field coverage, measured

From the 15 August 2026 test run over `nasa` and `natgeo` — 12 posts, 6 through each of the two
paths the Actor uses:

| Field | Present |
|---|---|
| `shortCode`, `url`, `type`, `caption`, `likesCount`, `commentsCount`, `timestamp` | 12/12 |
| `ownerUsername`, `ownerId`, `ownerProfileUrl`, `displayUrl`, `images` | 12/12 |
| `ownerFullName`, `ownerIsVerified` | 11/12 — missing on the one coauthored post owned by a third account |
| `videoUrl` on `type: "Video"` rows | 6/6 |
| `videoDuration` | video rows fetched by post URL only |
| `taggedUsers`, `locationName`, `musicTitle` | only when the post has them; `[]` or `null` otherwise |

Missing values are `null` or `[]`. Nothing is guessed.

### Which input mode should you use?

The two modes answer different questions, and the cost profile is different too.

| | `username` (profile mode) | `postUrls` (URL mode) |
|---|---|---|
| Best for | "what has this account posted lately" | "give me exactly these posts" |
| Depth | about 12 most recent posts, logged out | unlimited — as many URLs as you pass |
| Requests per post | one profile request returns the whole grid | one request per post |
| `videoDuration` | not available | returned |
| `skipPinnedPosts` | works | not detectable |
| `onlyPostsNewerThan` | works, and filtered posts are free | works, and filtered posts are free |

Profile mode is the cheaper and faster of the two per post, because a single request brings back the
whole recent grid. URL mode is the one to use for back-catalogue work, for a fixed list of posts you
already know, or when you need `videoDuration`. You can mix both in one run — pass `username` and
`postUrls` together and the results land in the same dataset.

**A worked example.** Monitoring 50 accounts once a day in profile mode is roughly 50 × 12 = 600
posts, or $0.054 a day plus the start fee — about $1.65 a month for a daily brand-monitoring feed.
Add `onlyPostsNewerThan: "1 day"` and you only pay for posts that are actually new, which for most
accounts brings that down by an order of magnitude.

### Limits

Read this before you build on it.

- **About 12 posts per profile, logged out.** Instagram's public profile response carries roughly the
  12 most recent posts and nothing more. `resultsLimit: 100` will not produce 100 — it produces the
  12 that exist publicly, and you are charged for 12. Deep back-catalogue paging requires a logged-in
  session, which this Actor deliberately does not use. If you need specific older posts, pass their
  URLs into `postUrls`; there is no depth limit that way.
- **No view or play counts.** Logged-out Instagram does not include them in either response, so the
  field is not emitted rather than filled with a guess. Any scraper showing you reel view counts is
  using an account.
- **No comment text and no liker lists.** Counts only.
- **Private accounts return a diagnostic row, not data,** and are not charged. Same for deleted posts
  and handles that do not exist.
- **Media URLs are signed and expire.** `displayUrl`, `videoUrl` and the `images` list point at
  Instagram's CDN with a signature that stops working after a few days. Download what you need soon
  after the run, or store the bytes rather than the link.
- **Rate limits are real and per address.** Instagram meters its public routes per IP. The Actor
  hops to a fresh address on every block rather than sleeping, and one of the two profiles in the
  test run needed 15 hops before it answered. On a bad minute a profile can still come back as a
  `RATE_LIMITED` diagnostic row — which is free, and re-running usually clears it.
- **`videoDuration` only on the post-URL path.** The profile response does not carry the DASH
  manifest it is read from.
- **Engagement counts drift.** Two runs a minute apart on the same NASA post returned 88,713 and
  89,083 likes. That is Instagram, not the scraper.

### FAQ

**Do I need an Instagram account, cookies, or a session ID?**
No. Nothing to log in to, nothing to paste, no account of yours to put at risk.

**What does it cost to scrape 1,000 Instagram posts?**
$0.09, plus $0.0000125 to start the run.

**Is this the cheapest Instagram post scraper?**
Yes. $0.09 per 1,000 posts is below every other Instagram post scraper on the Apify store, and below
every actor of any kind returning Instagram posts.

**Can I scrape reels?**
Yes. Reel URLs work exactly like post URLs, and reels appear in profile results with
`isReel: true`. Caption, likes, comments, the video file URL and the duration all come back.

**Does it get every image in a carousel?**
Yes — all of them, in `images` and in `childPosts`, each with its own dimensions and alt text.

**Can I filter by date?**
Yes: `onlyPostsNewerThan` takes `2026-01-01`, a full ISO timestamp, or `7 days` / `3 months`.
Posts filtered out are not charged.

**What happens if I run it with empty input?**
You get one clearly labelled sample row explaining what to fill in, the run finishes as SUCCEEDED,
and no result is charged.

**Can I export to CSV or Excel?**
Yes — Apify exports the dataset as JSON, CSV, Excel or XML, and the same data is available from a
REST endpoint.

**Is this affiliated with Instagram or Meta?**
No. It reads public post data that Instagram serves to logged-out visitors. Check Instagram's terms
and your own local rules before using the output commercially, especially for personal data.

### How it works

Instagram's JSON endpoints are walled for logged-out clients. `/api/v1/media/<id>/info/` answers with
400 KB of login HTML, `i.instagram.com` returns 403, and the public GraphQL `doc_id`s that scrapers
used for years have been retired — they now return `{"errors":[{"message":"execution error"}]}`.

What is not walled is the ordinary post page. `https://www.instagram.com/p/<shortcode>/` is server
rendered for visitors who are not logged in, and it ships the complete media object inside its
`<script type="application/json">` blocks: `taken_at`, `like_count`, `comment_count`, the full
caption, every carousel child with its own media URL, the owner, tagged users and location. This
Actor parses that. For profiles it first tries `web_profile_info`, which returns the recent grid in
one request, and falls back to reading the profile page and then each post page when that route is
rate limited — which is what happened to `natgeo` in the final test run, and it still returned all
6 requested posts.

Two engineering notes, in case they save you time on your own build:

**The transport is `impit`, not `fetch`.** Plain Node returns 401 or a login wall on the first
request from an address that works fine a second later through impit's Chrome TLS profile. It reads
like an IP block and is not one — it is the TLS handshake. Overriding the user-agent re-breaks it,
so the UA is left to match the fingerprint.

**Datacenter addresses, never residential.** A post page is about 115 KB on the wire, and
residential bandwidth is metered by the gigabyte — at that response size the egress alone would cost
more per row than a row is priced at. Rotating datacenter addresses answered every request in
testing and are not metered per GB, which is exactly what a per-IP rate limit needs and the reason
this price is possible at all.

# Actor input Schema

## `postUrls` (type: `array`):

Instagram post, reel or TV URLs. A bare shortcode such as DbtErSrlB2J also works. One row is returned per post.

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

Same field name as the market-leading Instagram post scraper, so its input works here unchanged. Accepts usernames, profile URLs and post URLs mixed together.

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

How many posts to take from each username or profile URL. Ignored when you pass post URLs. Instagram serves about 12 recent posts per profile without a login, so higher values will not return more.

## `maxItems` (type: `integer`):

Hard cap on charged rows for the whole run, across every input. Leave empty for no cap.

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

Keep only posts published on or after this moment. Accepts YYYY-MM-DD (2026-01-01), a full ISO timestamp, or a relative value such as 3 days, 2 months, 1 year. Filtered-out posts are never charged.

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

Drop posts a profile has pinned to the top of its grid. Only detectable in profile mode.

## `concurrency` (type: `integer`):

How many posts to fetch at once. 6 is a good balance; lower it if you see address rotations in the log.

## `requestDelayMs` (type: `integer`):

Optional pause after each request. Leave empty for no delay.

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

Leave empty and the actor uses rotating Apify datacenter addresses, which is what works here and costs nothing per GB. Residential is not needed and is not recommended.

## Actor input object example

```json
{
  "postUrls": [
    "https://www.instagram.com/p/DbtErSrlB2J/",
    "https://www.instagram.com/reel/DcCH2ZygIiP/"
  ],
  "username": [
    "nasa"
  ],
  "resultsLimit": 12,
  "maxItems": 50,
  "concurrency": 6,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "BUYPROXIES94952"
    ]
  }
}
```

# Actor output Schema

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

One row per post in the default dataset: id, shortCode, url, type, productType, isReel, caption, hashtags, mentions, likesCount, commentsCount, timestamp, displayUrl, videoUrl, videoDuration, images, childPosts, childPostsCount, dimensionsWidth, dimensionsHeight, alt, ownerId, ownerUsername, ownerFullName, ownerIsVerified, ownerProfileUrl, coauthorProducers, taggedUsers, locationName, locationId, musicArtist, musicTitle, source, scrapedAt. Runs with no input return a single uncharged sample row; blocked or missing posts return uncharged diagnostic rows.

# 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 = {
    "postUrls": [
        "https://www.instagram.com/p/DbtErSrlB2J/",
        "https://www.instagram.com/reel/DcCH2ZygIiP/"
    ],
    "username": [
        "nasa"
    ],
    "resultsLimit": 12,
    "maxItems": 50,
    "concurrency": 6,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "BUYPROXIES94952"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/instagram-post-scraper-v1").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 = {
    "postUrls": [
        "https://www.instagram.com/p/DbtErSrlB2J/",
        "https://www.instagram.com/reel/DcCH2ZygIiP/",
    ],
    "username": ["nasa"],
    "resultsLimit": 12,
    "maxItems": 50,
    "concurrency": 6,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["BUYPROXIES94952"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/instagram-post-scraper-v1").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 '{
  "postUrls": [
    "https://www.instagram.com/p/DbtErSrlB2J/",
    "https://www.instagram.com/reel/DcCH2ZygIiP/"
  ],
  "username": [
    "nasa"
  ],
  "resultsLimit": 12,
  "maxItems": 50,
  "concurrency": 6,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "BUYPROXIES94952"
    ]
  }
}' |
apify call dami_studio/instagram-post-scraper-v1 --silent --output-dataset

```

## MCP server setup

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

```

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/iqvqiwvTmHyrnRnEL/builds/WDmaUFhTTdMPDjvLs/openapi.json
