# Instagram Comments Scraper (`accountable_eel/ig-comments-scraper`) Actor

Instagram comment scraper for public posts and reels, no login. Paste post links, get one row per comment: author, user ID, text, likes, exact timestamp and whether the post's own account wrote it. Reads the newest comments Instagram shows a signed-out visitor. A post that returns nothing is free.

- **URL**: https://apify.com/accountable\_eel/ig-comments-scraper.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Social media, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.56 / 1,000 comment returneds

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

## Instagram Comments Scraper

One row per Instagram comment, without a login. Paste post or reel links and get a flat row per
comment: the author's username and numeric user ID, the comment text, its like count, the exact
time it was written, whether the post's own account wrote it, and the untouched source object.
Field names are frozen and drop straight into a Sheet, a Clay table, an n8n workflow or an AI
agent with no post-processing.

**Read this before you buy: a signed-out visitor sees only the newest 10 to 14 comments on a
post.** That is Instagram's limit, not this actor's, and no amount of paying changes it — see
"What you can and cannot get without logging in". If you need every comment on a 9,000-comment
post, this is the wrong tool and you should say so to yourself now rather than after a run.

### Who it's for

A brand watching its own posts wants to know what was said in the last hour, on a schedule,
without a person refreshing a phone — and wants owner replies flagged so it can see which
complaints have already been answered. A giveaway operator needs the entrants from the current
window as a table, with usernames and user IDs, not a screenshot. A social listening or sentiment
pipeline needs comment text with a real timestamp attached, in a shape it can feed to a model. And
an agent that already knows which posts matter needs one HTTP call that answers "what are people
saying on this post right now".

All four want the same thing: the *recent* comment stream, cheap, in a fixed shape, on a schedule.
That is what this actor is. It is a monitor, not an archive.

### Why this one

- **Newest first, always.** Instagram hands a signed-out visitor its most recent comments, in
  strict reverse-chronological order — verified on every fixture and asserted by the test suite,
  so a change in that ordering fails a test rather than quietly reordering your data. Run it every
  hour and you have a live comment feed for the posts you care about.
- **No login, no cookies, no account risk.** Nothing here logs in, stores a session, or asks you
  for credentials. There is no account of yours to get restricted.
- **The row shape is the product.** `shortcode`, `comment_id`, `parent_id`, `author`, `author_id`,
  `text`, `like_count`, `posted_at`, `is_owner_reply`, `reply_count`, `raw`. Frozen names, so a
  scheduled workflow or an agent can be pointed at this safely. Everything this actor does not
  surface as its own column is still there, untouched, in `raw`.
- **`author_id`, not just `author`.** Usernames change; numeric user IDs do not. If you are
  deduplicating entrants or matching commenters across posts over time, the ID is the join key and
  the username is a label.
- **One request per post.** The entire payload arrives in the post page itself, so there are no
  follow-up hops to pay for. That is why this is cheap.
- **Never charged for a miss.** A deleted post, a post with comments turned off, an age-gated
  post, a blocked request — each returns a row explaining what happened and costs nothing.
- **It tells you when it came up short.** Every run writes an `OUTPUT` record listing each post
  where your limit could not be filled and exactly how many comments *were* available. You are
  never left guessing whether 11 rows means "11 comments exist" or "something broke".

### What you get

One row per comment by default (turn off "Expand rows" in Advanced to get one row per post
instead, with everything nested under `comments`).

| Field | What it is |
| --- | --- |
| `shortcode` | The post the comment is on, e.g. `DdcI4o0Prsz` |
| `comment_id` | Instagram's own comment ID, as a **string** — these exceed what a spreadsheet stores as a number |
| `parent_id` | The comment this one replies to. **Always empty here** — see below |
| `author` | The commenter's username |
| `author_id` | The commenter's numeric user ID, as a string. Stable across username changes |
| `text` | The comment itself, emoji and all |
| `like_count` | Likes on the comment. Real, but usually 0 — see below |
| `posted_at` | When the comment was written, ISO-8601 UTC, to the second |
| `is_owner_reply` | `true` when the account that posted the post wrote this comment. Matched on user ID, never on username |
| `reply_count` | Instagram's count of replies under this comment. **Always empty here** — see below |
| `raw` | The untouched source object, including `is_covered`, `restricted_status`, `has_translation` and `giphy_media_info` |

In grouped mode each post also carries `commentCount` (what you were billed for), `totalComments`
(how many the post has in total, which is usually far more), and `truncated`.

### What you can and cannot get without logging in

This section exists because the honest answer is the most important thing on this page.

**You get:** the newest 10 to 14 top-level comments on any public post or reel, with every field
in the table above that is marked as filled. Measured on three real posts across two accounts:
10, 11, 13 and 14 comments per page load.

**You do not get, at any price, without a login:**

| | Why |
| --- | --- |
| Comment 15 and beyond | Instagram embeds the newest comments directly in the post page and offers a "next page" cursor alongside them. That cursor is a decoy: every signed-out request that could consume it is refused. This was tested against the real pagination query **and** against the page's own content query with the page's own variables — both come back empty for a signed-out caller, which proves the channel is closed rather than the cursor being wrong. |
| Replies to comments | Instagram's signed-out post page carries no reply threads at all. `parent_id` and `reply_count` are therefore always empty, and the "Include replies" switch cannot do anything. It is kept only so saved inputs and API calls stay valid, and turning it on writes a note into the run's `OUTPUT` record instead of pretending. |
| Realistic like counts | `like_count` is read correctly, but the comments you get are the newest ones, and a comment posted four minutes ago has no likes yet. Across 25 live rows, one had a like. **Do not sort or filter on this field.** |
| A representative sample | The newest dozen comments on a viral post are not a sample of its 9,000 comments. They are the last dozen minutes. Treat this as a live feed, not a survey. |

If that list rules out your use case, use a logged-in comment scraper instead. It will cost more
and carry account risk, and it will do the job this one cannot.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `urls` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~ig-comments-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"urls":["DdcI4o0Prsz","DdcjXAeFyr8"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

Because the reach per run is the newest comments only, **scheduling is how you get depth**. An
hourly run over the same post accumulates the full comment stream in your dataset over time, a
dozen at a time, which one-off runs cannot do.

### Input

```json
{
  "urls": [
    "DdcI4o0Prsz",
    "DdcjXAeFyr8"
  ]
}
```

One Instagram post or reel per line. A full link or just the shortcode from it both work. No login and no cookies: this reads only the comments a signed-out visitor can see, which is the newest dozen or so per post. Accepted formats: https://www.instagram.com/p/DdcI4o0Prsz/, https://www.instagram.com/reel/DdcI4o0Prsz/, DdcjXAeFyr8.

Post links, reel links, IGTV links, the `instagram.com/<name>/p/<code>/` form from a profile grid,
links with a `?img_index=` on the end, and bare shortcodes are all accepted and all normalise to
the same thing. A **profile** link is rejected with a message saying so, rather than being
quietly treated as a post.

**Most comments to return per post** is also your budget control, since you pay per comment
returned. Setting it above about 14 has no effect other than telling you, in the run's `OUTPUT`
record, that it could not be filled.

### Sample output

| query | found | status | shortcode | commentCount | totalComments | truncated | comments | comment\_id | parent\_id | author | author\_id | text | like\_count | posted\_at | is\_owner\_reply | reply\_count | raw | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| DdcI4o0Prsz | true | OK | <post shortcode> | <comments returned> | <comments on the post in total> | <more comments exist than were readable> | \<all comments found (full list)> | <comment id> | \<parent comment id (empty for top-level)> | <author username> | <author user id> | <comment text> | <likes on the comment> | <commented at> | \<written by the post's author> | <replies under this comment> | <raw source object> | 1970-01-01T00:00:00.000Z |

A real expanded row from a live run:

```json
{
  "query": "DdcjXAeFyr8",
  "found": true,
  "status": "OK",
  "shortcode": "DdcjXAeFyr8",
  "comment_id": "18073828469713536",
  "parent_id": null,
  "author": "allisonlinda511",
  "author_id": "26531589248",
  "text": "🥰",
  "like_count": 0,
  "posted_at": "2026-09-20T13:07:36.000Z",
  "is_owner_reply": false,
  "reply_count": null,
  "raw": { "…": "the untouched source object" }
}
```

And the run's `OUTPUT` record from that same run:

```json
{
  "requested": 2,
  "returned": 25,
  "errors": [],
  "caps": [
    {
      "shortcode": "DdcjXAeFyr8",
      "available": 14,
      "reason": "Instagram preloads only the newest 12 or so comments into a logged-out post page… This post has 990 comments in total; 14 were readable, so a limit of 30 cannot be filled."
    }
  ]
}
```

A miss comes back as a row with `"found": false` and is never charged.

### Pricing

- **Comment returned**: $6 per 1,000 comments

Plus a $0.00005 start fee per run. Each event above is billed independently, only when it actually returns data — misses (`found:false`) are never charged.

You are billed per **comment returned**, not per post looked up. A post that comes back empty —
deleted, comments off, age-gated, blocked — costs nothing beyond the flat start fee. A post that
returns 11 comments bills 11.

That also means the practical cost of a post is roughly a dozen comments' worth, whatever your
limit says.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~ig-comments-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"urls":["DdcI4o0Prsz","DdcjXAeFyr8"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~ig-comments-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"urls":["DdcI4o0Prsz","DdcjXAeFyr8"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~ig-comments-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"urls":["{{post}}"]}`, mapping the row's post into the `urls` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Instagram Scraper API: Comments by Post URL" — the agent will find and run this actor.

### Tips

- **Schedule it hourly against a small set of posts.** That is the design point. Each run adds the
  newest dozen comments, and over a day you accumulate the stream. A single run never will.
- **Deduplicate on `comment_id`.** Consecutive scheduled runs will overlap, because the newest
  dozen changes slowly on a quiet post. `comment_id` is stable and unique.
- **Join on `author_id`, label with `author`.** Usernames change between runs; IDs do not.
- **Do not filter on `like_count` or `reply_count`.** Both are honest fields and both carry almost
  no information on this route, for the reasons in "What you can and cannot get". `posted_at`,
  `text`, `author_id` and `is_owner_reply` are the fields worth building on.
- **Use `is_owner_reply` to find answered threads.** For a brand monitoring its own posts, it is
  the cheapest available signal for "we already responded to this".
- **Watch the `OUTPUT` record, not the row count.** `caps[]` distinguishes "this post only has 11
  comments" from "your limit of 500 was never reachable", and `errors[]` names every post that
  missed and why.
- **Keep concurrency low.** Instagram rate-limits aggressively. The default of 2 with a Residential
  proxy returned every row on the first attempt in testing; pushing it higher invites HTTP 429.

### vs. alternatives

| | This actor | A logged-in comment scraper |
| --- | --- | --- |
| Comments per post | the newest 10–14 | potentially all of them |
| Replies | no | usually yes |
| Needs an Instagram account | **no** | yes |
| Account restriction risk | **none** | yours |
| Billing | per comment returned, misses free | usually per comment too |
| Best at | monitoring recent comments on known posts, on a schedule | one-off deep exports of a whole post |

`apify/instagram-comment-scraper`, the category leader, sells at roughly **$2.30 per 1,000
comments** and does go deeper, because it does not work signed out. If you need depth, buy depth.
This actor is the cheaper, lower-risk half of the market: recent comments, no login, a frozen row
shape, and an honest statement of its ceiling.

### Data & privacy

This actor reads only pages that Instagram serves to an ordinary signed-out visitor. It never logs
in, never stores or transmits credentials, never sets or reuses a session cookie, and never touches
private accounts or content behind a follow request.

Comments are written by identifiable people. The usernames, user IDs, profile pictures and text
this actor returns are personal data under the GDPR and comparable laws, and you are the
controller of whatever you collect. Have a lawful basis, keep only what you need, honour deletion
requests, and do not use it to build profiles of individuals or to contact people who have not
asked to hear from you. Instagram's own Terms of Use apply to you as well as to us.

### FAQ

**Why did I only get 11 comments when the post has 9,000?**
Because Instagram shows a signed-out visitor the newest dozen and refuses every route past them.
The run's `OUTPUT` record says so explicitly, per post, with the count that was available. Nothing
is wrong with the actor and you were billed only for the 11.

**Can I get older comments by running it again with a different setting?**
No. There is no setting that reaches them. Running it repeatedly *over time* accumulates new
comments as they are written, which is the intended way to build depth.

**Why is `reply_count` empty on every row?**
Instagram's signed-out post page carries no reply data at all. The field is reported as empty
rather than as `0`, because "unknown" and "none" are different answers and collapsing them would
mislead you.

**Why is `like_count` zero almost everywhere?**
The comments returned are the newest ones. A comment written minutes ago genuinely has no likes.
The field is read correctly; there is just nothing to read yet.

**Does the "Include replies" switch do anything?**
No, and it says so in its own help text. It exists so that saved inputs and API calls that set it
stay valid. Turning it on adds a note to the run's `OUTPUT` record.

**Can I pass a profile link instead of a post link?**
No — you will get a `BAD_FORMAT` row telling you to paste the post link. This actor works per post.
To go from a username to its posts, run **Instagram Profile Scraper** first and feed its
`shortcode` column in here.

**What happens on a post with comments turned off?**
A `found: false` row saying "comments disabled or none yet", and no charge. Instagram does not
distinguish "the author turned comments off" from "nobody has commented" to a signed-out visitor,
so the message names both possibilities instead of guessing.

**Do I need a proxy?**
Yes, Residential. Apify's datacenter addresses are answered with HTTP 429 on this target; the
Residential group returned every row on the first attempt. It is the default here.

### Related actors

- **Instagram Profile Scraper: Posts and Reels** — usernames in, one row per post or reel out,
  with captions, hashtags, engagement and media links. Its `shortcode` column is this actor's
  input.

# Actor input Schema

## `urls` (type: `array`):

One Instagram post or reel per line. A full link or just the shortcode from it both work. No login and no cookies: this reads only the comments a signed-out visitor can see, which is the newest dozen or so per post. Accepted formats: https://www.instagram.com/p/DdcI4o0Prsz/, https://www.instagram.com/reel/DdcI4o0Prsz/, DdcjXAeFyr8. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `limit` (type: `integer`):

You pay per comment returned, so this is also your budget control. Instagram shows a signed-out visitor only the newest 10 to 14 comments on a post, so that is the real ceiling no matter what you set here — a run that could not reach your limit says so in the run's OUTPUT record.

## `includeReplies` (type: `boolean`):

Has no effect without a login, and this actor never logs in. Instagram's signed-out post page carries no reply threads at all, and every route that could load them turns a signed-out visitor away, so only top-level comments are ever returned. The switch is kept so saved inputs and API callers stay valid, and turning it on records a note in the run's OUTPUT record.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `expandRows` (type: `boolean`):

When on, each comment found gets its own row instead of being grouped under its post. You're still only charged once per post, no matter how many rows it produces.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

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

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "urls": [
    "DdcI4o0Prsz",
    "DdcjXAeFyr8"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "limit": 100,
  "includeReplies": false,
  "columns": [
    "shortcode",
    "commentCount",
    "totalComments",
    "truncated",
    "comments",
    "comment_id",
    "parent_id",
    "author",
    "author_id",
    "text",
    "like_count",
    "posted_at",
    "is_owner_reply",
    "reply_count",
    "raw"
  ],
  "expandRows": true,
  "maxConcurrency": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "urls": [
        "DdcI4o0Prsz",
        "DdcjXAeFyr8"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/ig-comments-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 = {
    "urls": [
        "DdcI4o0Prsz",
        "DdcjXAeFyr8",
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/ig-comments-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 '{
  "urls": [
    "DdcI4o0Prsz",
    "DdcjXAeFyr8"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/ig-comments-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/ig-comments-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/V0eF59idizDO0rQki/builds/WR05BxnbnGuidOT4U/openapi.json
