# Instagram Posts Scraper – Profiles, Hashtags & URLs (`khadinakbar/instagram-posts-scraper`) Actor

Extract Instagram posts from profiles, hashtags and post URLs, with caption, likes, comments, post type, media URLs, mentions, tagged users and date. No login; works with Apify MCP and the API.

- **URL**: https://apify.com/khadinakbar/instagram-posts-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Social media, MCP servers, Videos
- **Features:** Limited permissions
- **Stats:** 92 total users, 14 monthly users, 96.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 scraped posts

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?

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

### What is Instagram Posts Scraper?

Instagram Posts Scraper turns Instagram profiles, hashtags or post links into one row per post: caption, hashtags, mentions, likes, comments, post type (image, carousel, video or Reel), play counts and duration for videos, publish time, image and video links, tagged users, collab co-authors, and the account that posted it. Social media managers, brand and influencer marketers, researchers and AI agents use it to track what accounts post, how posts perform and what a hashtag looks like right now, with no Instagram login.

### What's new

- **Monitor mode.** Turn on `onlyNewSincePreviousRun` and every repeat run returns, and charges, only posts it has not saved before for that profile or hashtag.
- **Post-type filter and pinned posts.** `postTypes` keeps only photos, videos, Reels or carousels; `skipPinned` leaves out pinned posts. Skipped posts are never charged.
- **Engagement rate.** Every row from a profile now carries `owner_follower_count` and `engagement_rate` (likes plus comments as a percentage of followers).
- **Cheaper, faster Reel links.** Links that point to a Reel are read through a faster route; more profiles are read at the same time (eight instead of four).
- **`agentSummary`** in `OUTPUT` and `RUN_SUMMARY`: one plain sentence an AI agent can read to know what happened.

### Why people choose this Actor

- **Fast on many accounts.** Up to eight sources are read at the same time, so a batch of profiles finishes in seconds. One profile at 120 posts takes about a minute.
- **A backup route behind the main one.** If the main data route is unavailable or rate-limits, the Actor moves to a second and then a third route and carries on from the same profile, without duplicate rows.
- **Every post type in one run.** Images, carousels, videos and Reels, each labeled in `post_type`, with every image of a carousel in `media_urls`.
- **The same 36 fields on every row.** Fields Instagram does not provide are `null`, never missing, so a spreadsheet or schema stays stable between runs.
- **Honest results.** The run summary says whether the run is `COMPLETE`, `PARTIAL`, empty, or ended without data, and names the reason for every source that returned fewer posts than you asked for.
- **Platform usage is included.** You pay per post saved, a tiny storage event per post and a small start event. There is no separate compute or proxy bill.

### How to use it

**Latest posts from a few brands:**

```json
{
  "instagramUsernames": ["natgeo", "nasa", "https://www.instagram.com/nike/"],
  "maxPostsPerTarget": 20,
  "onlyPostsNewerThan": "30 days"
}
```

**What a hashtag looks like today, plus one specific post:**

```json
{
  "hashtags": ["#travelphotography", "streetphotography"],
  "directPostUrls": ["https://www.instagram.com/p/DdG4RIxIPyf/"],
  "maxPostsPerTarget": 10
}
```

| Input | What it controls |
|---|---|
| `instagramUsernames` | Usernames, @handles or profile URLs |
| `hashtags` | Hashtags, with or without `#` |
| `directPostUrls` | Post, Reel or carousel links |
| `maxPostsPerTarget` | Posts per profile or hashtag (default 50) |
| `onlyPostsNewerThan` | A date like `2026-01-01` or a period like `7 days` or `2 months` |
| `postTypes` | Keep only `photo`, `video`, `reel` or `carousel` posts (empty = all) |
| `skipPinned` | Leave out pinned posts at the top of a profile |
| `onlyNewSincePreviousRun` | Monitor mode: return and charge only posts not saved in an earlier run (profiles and hashtags) |

Fill in at least one of `instagramUsernames`, `hashtags` or `directPostUrls`. Repeated inputs are processed once, and a post that two sources both return is saved once.

Want the full profile (followers, bio, contact) for these accounts? Continue with **Instagram Profile Scraper** linked below.

### Output: Instagram post data you receive

A real row for a National Geographic Reel (long text and links shortened):

```json
{
  "id": "4026691767482597400",
  "shortcode": "DfhrtQEykDv",
  "url": "https://www.instagram.com/p/DfhrtQEykDv/",
  "post_type": "REEL",
  "caption": "The most extraordinary places speak for themselves.\n\nEverest: The Other Side is now playing in theaters and IMAX",
  "hashtags": [],
  "mentions": [],
  "accessibility_caption": null,
  "like_count": 25811,
  "comment_count": 109,
  "video_view_count": null,
  "video_play_count": 567513,
  "video_duration": 61.845,
  "thumbnail_url": "https://instagram.fjog3-1.fna.fbcdn.net/v/t51.82787-15/89574...",
  "display_url": "https://instagram.fjog3-1.fna.fbcdn.net/v/t51.82787-15/89574...",
  "media_urls": ["https://instagram.fjog3-1.fna.fbcdn.net/v/t51.82787-15/89574..."],
  "video_url": "https://instagram.fjog3-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQP62...",
  "is_video": true,
  "owner_username": "natgeo",
  "owner_full_name": "National Geographic",
  "owner_id": "787132",
  "owner_is_verified": true,
  "owner_follower_count": null,
  "location_name": null,
  "location_id": null,
  "tagged_users": ["disneystudios", "natgeodocs"],
  "coauthors": ["disneystudios", "natgeodocs"],
  "is_pinned": false,
  "is_sponsored": false,
  "is_reel": true,
  "comments_disabled": false,
  "timestamp": "2026-11-09T17:00:03.000Z",
  "scraped_at": "2026-11-09T21:43:13.788Z",
  "source_url": "https://www.instagram.com/natgeo/"
}
```

| Field | What it contains |
|---|---|
| `id`, `shortcode`, `url`, `post_type` | The post and its type: `IMAGE`, `CAROUSEL_ALBUM`, `VIDEO` or `REEL` |
| `caption`, `hashtags`, `mentions`, `accessibility_caption` | Post text, extracted tags and mentions, and Instagram's image description when it has one |
| `like_count`, `comment_count`, `comments_disabled` | Engagement at collection time |
| `video_view_count`, `video_play_count`, `video_duration` | Video details; Reels and videos usually carry plays and duration |
| `display_url`, `media_urls`, `thumbnail_url`, `video_url` | Image, carousel and video links |
| `owner_username`, `owner_full_name`, `owner_id`, `owner_is_verified`, `owner_profile_pic_url`, `owner_follower_count` | Who posted it; follower count is often `null` |
| `tagged_users`, `coauthors`, `is_sponsored`, `is_pinned` | Collaborations, paid partnerships and pinned posts |
| `owner_follower_count`, `engagement_rate` | Followers and (likes + comments) / followers as a percentage. Filled for profile rows and whenever the account's follower count was read in the same run; otherwise `null` |
| `is_video`, `is_reel` | Quick type flags |
| `location_name`, `location_id` | The tagged place, when the post has one |
| `timestamp`, `scraped_at`, `source_url` | Publish time, collection time and the input that found the post |

Hashtag sources return a sample of public posts indexed for the tag, not the full Instagram hashtag feed.

### If a run returns nothing, or fewer posts than you asked for

`OUTPUT` and `RUN_SUMMARY` in the default key-value store explain every run. Read `outcome` first:

| `outcome` | What it means | What to do |
|---|---|---|
| `COMPLETE` | Every source gave the requested number of posts or reached the end of its public posts | Nothing; use the dataset |
| `PARTIAL` | Some posts were saved, but a source stopped early, such as at the run timeout or the charge limit | Check `RUN_SUMMARY.sourceShortfalls`, then raise the timeout or charge limit, or request fewer sources per run |
| `VALID_EMPTY` | The sources were read and no post matched, for example everything is older than `onlyPostsNewerThan` | Widen the date filter or pick another account |
| `INVALID_INPUT` | The input could not be used: no source given, a username or post link that does not exist or is private, or a charge limit too low to save one post | Fix the input named in `message` and run again |
| `UPSTREAM_FAILED` | Every data route was unavailable and nothing could be saved. No post is charged | Run again later |
| `CONFIG_ERROR` | The Actor build has no data route configured | Open an issue; nothing was charged |

`sourceShortfalls` lists each source with `saved`, `requested` and a `reason`: `feed_ended` (fewer public posts than requested), `not_found`, `run_timeout`, `charge_limit` or `stopped_early`.

### Example workflow: a monthly competitor content report

A skincare brand tracks five competitors. A scheduled task collects each account's posts with `onlyPostsNewerThan` set to `30 days`. The team ranks posts by `like_count`, compares `post_type` counts to see who is leaning into Reels, and reads `coauthors` and `is_sponsored` to spot influencer partnerships. Then they collect the commenters on the top posts with [Instagram Post Engagers Scraper](https://apify.com/khadinakbar/instagram-post-engagers-scraper) to find engaged buyers.

### Pricing

This Actor uses **Pay per event**, and platform usage is included in the price. You pay one event per post saved, a tiny per-result storage event and a small start event, with no separate compute or proxy bill. The live Pricing tab on this page shows the current prices. Paid Apify plans get volume discounts per post.

Per-post price by Apify plan (platform usage included): **Free and Starter $1.20 per 1,000 posts, Scale $1.00, Business and higher $0.80**, plus a tiny storage event per post ($0.01 per 1,000) and a small start event per run.

- 5 accounts × 20 posts = 100 post events plus 100 storage events and one start event: about $0.12 on the Free plan.
- 1,000 posts from 20 accounts on the Free plan: about $1.21.
- A run that finds nothing, or ends without data, saves no posts and charges no post events.
- `maxPostsPerTarget` caps each profile or hashtag, and the run's maximum charge setting caps the whole run, so you control the ceiling.

### Post research with AI agents (MCP)

Connect this Actor to an AI agent through [Apify MCP](https://mcp.apify.com) and ask in plain language:

> Get NASA's Instagram posts from the last two weeks and tell me which ones got the most likes and comments.

Agent checklist:

1. Put accounts in `instagramUsernames`, tags in `hashtags`, or links in `directPostUrls`.
2. Use `onlyPostsNewerThan` for recent posts, `postTypes` to narrow the kind, `onlyNewSincePreviousRun` for repeat monitoring, and keep `maxPostsPerTarget` close to the request.
3. Read rows with `get-dataset-items`; sort by `like_count` or `comment_count`.
4. Read `OUTPUT.agentSummary` and check `OUTPUT.outcome` before answering, and share `url` so the user can open each post.

Facts an agent can plan with:

| Question | Answer |
|---|---|
| Login or credentials needed? | None. Public posts only |
| Does the run change anything on Instagram? | No. It only reads public pages |
| What does a call cost? | Post events plus storage events for the rows saved and one start event, so `sources × maxPostsPerTarget` is the ceiling |
| What if nothing is found? | `outcome` says why (`VALID_EMPTY`, `INVALID_INPUT` or `UPSTREAM_FAILED`) and no post is charged |
| Is the row shape stable? | Yes: 36 fields on every row, `null` when Instagram does not provide a value |
| Can the same call be repeated safely? | Yes. A post that appears in two sources is saved once per run |

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/khadinakbar~instagram-posts-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"instagramUsernames": ["natgeo"], "maxPostsPerTarget": 10, "onlyPostsNewerThan": "14 days"}'
```

### Which Instagram Actor for which job

| Your job | Use |
|---|---|
| Posts from profiles, post links, small hashtag samples; new-post monitoring | **This Actor** |
| Only the Reels tab of an account | [Instagram Reels Scraper](https://apify.com/khadinakbar/instagram-reels-scraper) |
| Deep hashtag research at volume | [Instagram Hashtag Scraper](https://apify.com/khadinakbar/instagram-hashtag-scraper) |
| Account facts: followers, bio, contacts | [Instagram Profile Scraper](https://apify.com/khadinakbar/instagram-profile-scraper) |
| Story highlights | [Instagram Highlights Scraper](https://apify.com/khadinakbar/instagram-highlights-scraper) |
| One MCP tool for profiles, posts, Reels and comments | [Instagram MCP Server](https://apify.com/khadinakbar/instagram-mcp-server) |

### Related Instagram tools from the same developer

| Your next step | Use | Why |
|---|---|---|
| When you then want account details | [Instagram Profile Scraper](https://apify.com/khadinakbar/instagram-profile-scraper) | Followers, bio, website and public contacts |
| When you focus on Reels | [Instagram Reels Scraper](https://apify.com/khadinakbar/instagram-reels-scraper) | Reels with video links, likes, comments and audio |
| When you want the people engaging with posts | [Instagram Post Engagers Scraper](https://apify.com/khadinakbar/instagram-post-engagers-scraper) | Commenters on any public post |
| When you need emails and phones | [Instagram Contact Scraper](https://apify.com/khadinakbar/instagram-contact-scraper) | Contacts from bio and contact button |

### Best results

| Starting situation | Helpful action | Expected outcome |
|---|---|---|
| Tracking accounts over time | Schedule a task with `onlyNewSincePreviousRun` turned on | Only posts not seen before, and only those are charged |
| Researching a trend | Add several related hashtags | A broad sample of current posts |
| Auditing one campaign | Paste the post links in `directPostUrls` | Exact stats for those posts |
| Comparing several brands | Put all usernames in one run | The accounts are read in parallel |

### Builder's note

I found that most people judge posts by likes, comments and timing first, so those fields sit near the top of every row along with the post type. I learned that brand teams care a lot about partnerships, so `coauthors`, `tagged_users` and `is_sponsored` stay in the output for spotting influencer deals. I also found that most Instagram runs that end early trace back to one data route being slow or unavailable, so the Actor keeps a second and third route ready and the run summary says exactly what each source returned.

### FAQ

#### Do I need an Instagram account?

No. The Actor reads public posts and needs no login or cookies.

#### How many posts can I get per account?

`maxPostsPerTarget` sets the number per profile or hashtag. Large accounts page through their post history until that number is reached.

#### Why are runs fast?

Profiles, hashtags and post links are read through a fast data route first, with a second route as a backup. Several sources are read at the same time. A source neither route can read is reported in the run summary instead of being guessed, so most runs finish in seconds and stay inside the default timeout.

#### Does it work with Reels and carousels?

Yes. `post_type` shows `REEL`, `CAROUSEL_ALBUM`, `VIDEO` or `IMAGE`, and `media_urls` lists every image in a carousel.

#### Does it return the comment text?

It returns `comment_count` and `comments_disabled` for every post. When you want the people who commented and what they wrote, then pass the post links to [Instagram Post Engagers Scraper](https://apify.com/khadinakbar/instagram-post-engagers-scraper).

#### Why is video_view_count empty on some posts?

Instagram shows view counts for some videos and leaves them out for others. `video_play_count` carries the play count for most Reels and videos.

#### What happens with a username that does not exist?

It returns no posts and no post charge, and `sourceShortfalls` marks it `not_found`.

### Responsible use

This Actor collects public Instagram posts, which can include personal data such as usernames and captions. Use it with a lawful basis under laws such as the GDPR and CCPA, respect creators' rights to their content, and follow Instagram's terms. Read Apify's guide on [whether web scraping is legal](https://blog.apify.com/is-web-scraping-legal/) for more context. Instagram is a trademark of Meta Platforms, Inc. This independent Actor is not affiliated with, associated with, or endorsed by Instagram or Meta. For questions or bugs, open a ticket in the Issues tab.

# Changelog

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

# Actor input Schema

## `instagramUsernames` (type: `array`):

Use this field when the user provides Instagram usernames (e.g. 'natgeo', 'nasa') or profile URLs (e.g. 'https://instagram.com/natgeo'). Do NOT use this when the user provides hashtags or post URLs — use the dedicated fields instead. One username or URL per line.

## `hashtags` (type: `array`):

Use this field when the user provides hashtags (with or without the # symbol). Scrapes recent public posts tagged with each hashtag. Do NOT use for usernames or post URLs.

## `directPostUrls` (type: `array`):

Use this field when the user provides specific Instagram post, reel, or carousel URLs (e.g. 'https://instagram.com/p/ABC123/'). Scrapes exactly those posts.

## `maxPostsPerTarget` (type: `integer`):

Maximum number of posts to scrape per profile or hashtag. For direct post URLs, each URL counts as one post regardless of this limit.

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

Skip posts older than this date. Accepts absolute dates (YYYY-MM-DD) or relative dates (e.g. '7 days', '2 months', '1 year').

## `postTypes` (type: `array`):

Use this field when the user wants only certain kinds of posts. Pick any of photo, video, reel, carousel; leave empty for all types. Posts that do not match are skipped and never charged. Applies to profiles, hashtags and post links.

## `skipPinned` (type: `boolean`):

Use this field when pinned posts at the top of a profile should be left out, for example when tracking only fresh posts. Pinned posts that are skipped are never charged.

## `onlyNewSincePreviousRun` (type: `boolean`):

Use this field for monitoring: the Actor remembers the posts it saved for each profile or hashtag and, on the next run, returns and charges only posts it has not saved before. The first run saves normally. Memory is kept in your own Apify storage (named store 'instagram-posts-monitor'). Not used for direct post links.

## Actor input object example

```json
{
  "instagramUsernames": [
    "natgeo"
  ],
  "hashtags": [],
  "directPostUrls": [],
  "maxPostsPerTarget": 12,
  "skipPinned": false,
  "onlyNewSincePreviousRun": false
}
```

# Actor output Schema

## `scrapedPosts` (type: `string`):

All scraped Instagram posts with full metadata — captions, hashtags, likes, comments, media URLs, owner profile, location, tagged users, and timestamps.

# 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 = {
    "instagramUsernames": [
        "natgeo"
    ],
    "hashtags": [],
    "directPostUrls": [],
    "maxPostsPerTarget": 12
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/instagram-posts-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 = {
    "instagramUsernames": ["natgeo"],
    "hashtags": [],
    "directPostUrls": [],
    "maxPostsPerTarget": 12,
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/instagram-posts-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 '{
  "instagramUsernames": [
    "natgeo"
  ],
  "hashtags": [],
  "directPostUrls": [],
  "maxPostsPerTarget": 12
}' |
apify call khadinakbar/instagram-posts-scraper --silent --output-dataset

```

## MCP server setup

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