# Instagram Post Scraper (`herus13/instagram-post-scraper`) Actor

Extract public Instagram posts and reels by username or by direct post URL. Every row carries post.shortcode, post.url, post.caption, the image and video URLs and the owner's handle, plus post.likes, post.comments and post.taken_at where Instagram shows them. Export JSON, CSV or Excel.

- **URL**: https://apify.com/herus13/instagram-post-scraper.md
- **Developed by:** [herus13](https://apify.com/herus13) (community)
- **Categories:** Social media, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 8 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 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/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

The Instagram Post Scraper extracts public posts and reels from Instagram, by username or by a direct post URL. Each record carries the shortcode, permalink, post type, caption, like and comment counts, publish timestamp, image and video URLs, and the owner's handle and display name.

### What you get

One row per post, exactly as a JSON export returns it.

```json
{
  "post": {
    "shortcode": "Dbdo4gTupuE",
    "url": "https://www.instagram.com/p/Dbdo4gTupuE/",
    "typename": "GraphVideo",
    "is_video": true,
    "caption": "The game looks different from here.",
    "likes": 175374,
    "comments": 1148,
    "taken_at": "2026-07-31T16:00:05+00:00",
    "product_type": "clips",
    "media_type": 2,
    "is_pinned": false,
    "is_paid_partnership": false,
    "usertags": ["a_tagged_account"],
    "coauthors": [],
    "location": null,
    "thumbnail_url": "https://instagram.fbaq6-1.fna.fbcdn.net/v/...",
    "display_url": "https://instagram.fbaq6-1.fna.fbcdn.net/v/...",
    "video_url": "https://instagram.fbaq6-1.fna.fbcdn.net/o1/v/..."
  },
  "owner_username": "nike",
  "owner_full_name": "Nike",
  "source": "timeline",
  "scraped_at": "2026-06-19T08:30:00+00:00",
  "_meta": {"author": "https://apify.com/herus13", "status": "ok", "message": null}
}
```

`_meta` is run metadata, not scraped data. A target that fails appears as a row with only `_meta` (status `failed`, the reason in `message`); those rows are never charged.

The table and CSV views flatten `post` into columns of their own — `post.shortcode`, `post.caption`, `post.likes` and the rest — so a spreadsheet gets one flat row per post. `source` says where the row came from: `timeline` for a username, `embed` for a direct post URL. `video_url` is filled for reels and videos only. `is_pinned` marks posts pinned to the top of the profile, which come first and can be older than the rest; `taken_at`, `usertags`, `coauthors`, `is_paid_partnership` and `location` are filled for posts found by username, and empty or `null` for a direct post URL, whose page does not carry them.

For a username, some runs get a shorter post record: when Instagram limits the post list for a session, `taken_at`, `likes`, `comments`, `usertags`, `coauthors`, `is_paid_partnership` and `location` come back empty (`null`) on those rows, and `is_pinned` can be `null` too. The rest of the row, including the shortcode, link, caption, media URLs and owner, is unaffected, and the run log says when this happened. Posts are still charged as usual.

Changed on 2026-09-24: `typename` for posts found by username is now `GraphImage`, `GraphVideo` or `GraphSidecar`, the same values a direct post URL gives (it was `XIGPolarisImageMedia`, `XIGPolarisVideoMedia` or `XIGPolarisCarouselMedia`).

Export the dataset as JSON, CSV or Excel, or read it from the [Apify API](https://docs.apify.com/api/v2).

### What it costs

| Event | Price |
|---|---|
| Actor Start (`apify-actor-start`) | $0.005 |
| Post scraped (`post-scraped`) | $0.002 |

You pay per post delivered, not for time. One post from a direct URL costs about $0.007, a hundred posts about $0.21, and a thousand posts about $2.01. The residential proxy is included in those prices, and a target that returns nothing is never charged.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `targets` | array | yes | — | What to scrape, one entry per line. A username (nike) returns that profile's recent posts, as many as max_posts allows. A direct post or reel URL (https://instagram.com/p/ABC123/) returns that single post, and so does the bare id from that URL (DddlDOVRGLj) on its own. Both forms can be mixed in one run, and every post returned becomes one dataset row. Required. |
| `max_posts` | integer | no | `12` | How many recent posts to return for each username in targets, from 1 to 500. Default 12. A direct post or reel URL always returns exactly that post, so this setting does not change it. On a very large back-catalogue a run can return fewer posts than the number asked for. |
| `proxyUrls` | array | no | — | Leave empty and the run uses the residential proxy that comes included in the price of the run. To route the run through your own provider instead, add one gateway URL per entry, for example http://user:pass@host:port. Any provider that issues URLs works. When set, the run uses these first and switches to the included proxy if they stop working, so a broken proxy never fails the run. |

### How to run it

1. Open the actor on Apify Console.
2. Put your usernames or post and reel URLs into **Usernames or Post URLs**.
3. Set **Max Posts Per Username** — `12` by default, up to `500`.
4. Run. Export JSON, CSV or Excel from the dataset.

```json
{
  "targets": ["nike", "https://www.instagram.com/p/Dbdo4gTupuE/"],
  "max_posts": 12
}
```

Minimal input:

```json
{
  "targets": ["https://www.instagram.com/p/Dbdo4gTupuE/"]
}
```

Available as the MCP tool `herus13--instagram-post-scraper` on mcp.apify.com and through the Apify API; send the same JSON.

### Why this scraper

- **Two ways in, one input list.** A handle collects a profile's recent posts and a link collects exactly that post or reel, and both can sit in the same run.
- **Reels come with their media.** Image, thumbnail and video URLs are on every row, ready to download, embed or hand to a model.
- **Nothing to sign in with.** No Instagram account and no cookie are needed, so nothing of yours is exposed to the run.
- **A stable, flat field set.** The same columns every run, safe to append to a spreadsheet or load into a warehouse without reshaping.
- **Residential proxy included** in the price of the run, with your own gateway URLs accepted when you prefer them.

### Use cases

- **Content datasets and archives** — one structured record per post, including the reels that are otherwise awkward to keep, makes a durable archive of an account's output with the media links intact.
- **Posting cadence and competitor tracking** — timestamps across a profile's recent posts show how often a competitor publishes and at what time of day, and the engagement counts show which of those choices actually worked.
- **Engagement modelling** — likes and comments alongside post type and caption give the feature set for a model of what performs on an account, built from that account's own history rather than a generic benchmark.
- **Influencer vetting** — pull a creator's recent posts before a partnership and check that engagement is consistent and that the content matches the media kit.
- **Campaign and launch monitoring** — re-run a handle list on a schedule and catch new posts, with captions and counts, the day they go up.

### FAQ

**Do I need an Instagram account?** No. The actor returns only what a public post shows to anyone, so there is nothing to sign in with; private accounts and private content are never returned.

**Why are some fields empty on a post found by username?** When Instagram limits the post list for a session, the date, like and comment counts, tags, co-authors, paid-partnership flag and location are not available for that run and come back `null`; the rest of the row is complete. A direct post URL is not affected for likes and comments. Run again later to get them.

**Can I scrape one specific post or reel?** Yes. Put the post or reel URL in **Usernames or Post URLs** and that single post comes back. **Max Posts Per Username** does not apply to a direct URL.

**How many posts can I get per username?** Up to `500`. On a very large back-catalogue a run can return fewer than the number you asked for; you are only charged for the posts that arrive.

**Am I charged for targets that fail?** No. A target that returns nothing — missing, unavailable or private — is never charged and gets only a `_meta` row; the run log names it and why. You pay per post delivered.

**Is a proxy included?** A residential proxy is included in the price of the run. To use your own, paste gateway URLs into **Your own proxy URLs**.

**What happens if my input is wrong?** The run stops before it scrapes anything, so no results are charged. Its status and log name each field that is wrong and say what to change — fix the input and run again.

**Is scraping Instagram legal?** This actor collects only publicly available post data. You are responsible for complying with Instagram's terms and applicable laws, and for not collecting personal data without a lawful basis.

### Related actors

- **[Instagram Profile Scraper](https://apify.com/herus13/instagram-profile-scraper)** — public profiles with biography, follower and following counts, verified and business flags, and recent posts.
- **[Instagram Comment Scraper](https://apify.com/herus13/instagram-comment-scraper)** — comments on any public post, with author, text, likes and replies.
- **[TikTok Profile Scraper](https://apify.com/herus13/tiktok-profile-scraper)** — public TikTok profiles by handle, with stats, bio and metadata.

# Actor input Schema

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

What to scrape, one entry per line. A username (nike) returns that profile's recent posts, as many as max_posts allows. A direct post or reel URL (https://instagram.com/p/ABC123/) returns that single post, and so does the bare id from that URL (DddlDOVRGLj) on its own. Both forms can be mixed in one run, and every post returned becomes one dataset row. Required.

## `max_posts` (type: `integer`):

How many recent posts to return for each username in targets, from 1 to 500. Default 12. A direct post or reel URL always returns exactly that post, so this setting does not change it. On a very large back-catalogue a run can return fewer posts than the number asked for.

## `proxyUrls` (type: `array`):

Leave empty and the run uses the residential proxy that comes included in the price of the run. To route the run through your own provider instead, add one gateway URL per entry, for example http://user:pass@host:port. Any provider that issues URLs works. When set, the run uses these first and switches to the included proxy if they stop working, so a broken proxy never fails the run.

## Actor input object example

```json
{
  "targets": [
    "https://www.instagram.com/p/Dbdo4gTupuE/"
  ],
  "max_posts": 12
}
```

# Actor output Schema

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

Instagram posts with caption, likes, comments, media URLs, and owner. Each row also carries \_meta: run metadata, not scraped data.

# 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": [
        "https://www.instagram.com/p/Dbdo4gTupuE/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("herus13/instagram-post-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": ["https://www.instagram.com/p/Dbdo4gTupuE/"] }

# Run the Actor and wait for it to finish
run = client.actor("herus13/instagram-post-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": [
    "https://www.instagram.com/p/Dbdo4gTupuE/"
  ]
}' |
apify call herus13/instagram-post-scraper --silent --output-dataset

```

## MCP server setup

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