# YouTube Comments Scraper — Replies & New Comment Monitor (`foxlabs/youtube-comments-scraper`) Actor

The comments of YouTube videos — or of a channel's latest videos — newest or top first, with replies, likes, pins, creator hearts, the author's channel and the video's exact comment count. Monitoring mode brings only new comments and carries a burst over to the next run. No login, no API key.

- **URL**: https://apify.com/foxlabs/youtube-comments-scraper.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (community)
- **Categories:** Social media, Videos
- **Stats:** 15 total users, 13 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 comments

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

## YouTube Comments Scraper — Replies & New Comment Monitor

**foXLabs YouTube series:** [Channel Email Scraper](https://apify.com/foxlabs/youtube-channel-email-scraper) · [Channel Finder](https://apify.com/foxlabs/youtube-channel-finder) · [Channel Videos Scraper](https://apify.com/foxlabs/youtube-channel-videos-scraper) · [Search Scraper](https://apify.com/foxlabs/youtube-search-scraper)

💬 Get the comments under YouTube videos — for the videos you name, or for a channel's latest uploads — with replies, likes, pins, creator hearts, each author's channel and the video's exact comment count. Schedule it in **monitoring mode** — newest first, without filters — and every run brings only what was written since: a burst of comments too large for one run is carried over to the next run, not skipped.
Built for community and brand monitoring, audience research, and comment datasets for analysis and AI. No YouTube API key, no login, no cookies.

### 🔍 What is YouTube Comments Scraper — and when should you use it?

Give this Actor **video URLs or IDs** (watch, youtu.be, Shorts and live addresses) or **channels** — it then takes each channel's latest videos — and it returns one row per comment or reply, read live from the comment list YouTube shows to every visitor: 20 comments a request, **newest first** (every comment) or **top first** (YouTube's own selection).

**Use it when you need:** every comment of a video with its thread structure; the new comments under your own or a competitor's videos since the last run; the most-liked comments and the conversations under them; the comments that mention a word — a price, a bug, a brand name.

**Use something else when:** you need exact timestamps for old comments (YouTube shows "3 weeks ago", not a date — a row carries that text and the range of days it can mean); live-chat messages, Community-post comments, or the comments of private and members-only videos (YouTube does not show them to a visitor); or videos found by keyword — find those with [YouTube Search Scraper](https://apify.com/foxlabs/youtube-search-scraper), or list a channel's uploads with [YouTube Channel Videos Scraper](https://apify.com/foxlabs/youtube-channel-videos-scraper), and pass the URLs here.

### 🤖 Use with AI agents

**Already on the Apify MCP server?** Ask for this Actor by name: `foxlabs/youtube-comments-scraper`. Checked on 2026-10-08: the server lists it as the tool `foxlabs--youtube-comments-scraper`, and a call through it returned five comments (the run took 2.8 seconds).

**Your agent can pay for its own runs.** The Actor is pay-per-event only, runs with limited permissions and does not use Standby — Apify's conditions for agentic payments — and Apify Store lists it as eligible for them (read from the Store on 2026-10-08). For an agent with a wallet and no human to sign in, Apify sells a prepaid, spend-capped API token over **x402** at `agi.apify.com`; the terms of that path are Apify's own (see `apify.com/agents.md`). Billing is the same either way: per delivered comment or reply; status rows are free.

Otherwise paste this into Claude, ChatGPT, Cursor or any MCP-enabled assistant:

```
I want YouTube comments from the Apify Actor `foxlabs/youtube-comments-scraper`.
Input: `videos` is a list of video URLs or IDs; `channels` is a list of channel URLs or @handles (the Actor reads the comments of each channel's latest `maxVideosPerChannel` videos). `maxCommentsPerVideo` caps the rows per video and `maxComments` the rows in total. `sort` is "newest" (every comment) or "top" (YouTube's selection). `maxRepliesPerComment` adds replies. `onlyNewComments: true` with a `listName` delivers only the comments that list has not delivered before.
Start with: {"videos":["https://www.youtube.com/watch?v=e1q-TuHdc4Y"],"maxCommentsPerVideo":100}
Ask me which videos or channels, run the Actor, then summarise the comments: the main topics, the questions people ask, and the most-liked ones.
```

The machine-readable API, MCP config and OpenAPI definition live at `apify.com/foxlabs/youtube-comments-scraper.md`.

### 📋 Overview

Everything you need to turn YouTube comment sections into structured rows — in one Actor, with no login, cookies or API key.

**Why pick this Actor:**

- 🧾 **Every comment, not YouTube's selection** — "Newest first" (the default) lists all of a video's comments. YouTube's "Top comments" order is a selection: for five videos read in both orders it held 19% to 99% of them. Both orders are here, and the run tells you when a list ended short of the number YouTube counts.
- 🔁 **Monitoring that carries a burst over** — a named list remembers what was delivered. Newest first and without filters, each run brings a video's new comments oldest first, so what does not fit into one run is delivered by the next one instead of being skipped.
- ↩️ **Replies where you want them** — as many per comment as you ask for, each with the ID of the comment it answers. A thread that showed 782 replies delivered 782.
- 🔢 **Numbers as YouTube has them** — the video's comment count to the last digit (9,548, not "9.5K"), a flag where YouTube shortens a like count ("25K"), and `0` — not an empty field — for a comment nobody liked.
- 📺 **Channels as input** — a channel line reads the comments of its latest videos and, when you ask for them, of its Shorts and of the entries of its Live tab (`videoTypes`).
- 🗓 **No invented dates** — YouTube shows "3 weeks ago". A row carries that text and the range of days it can mean, and a date only where the day is certain.
- 🧭 **Never empty without a reason** — comments turned off, a private or members-only video, a mistyped ID: each gets a free status row that says which.
- 💰 **Pay per row** — $0.40 per 1,000 comments or replies. Status rows and comments your filters remove are free.
- 🤖 **Agent-ready** — on the Apify MCP server and open to agentic payments (x402).

### ✨ Features

- 🎯 **Two ways in** — `videos` (URLs or IDs, up to 10,000 lines) and `channels` (URLs, @handles or IDs, up to 1,000): each channel's latest `maxVideosPerChannel` videos of the kinds in `videoTypes` (`videos`, `shorts`, `live`).
- 🔀 **Two orders** — `sort: "newest"` for the complete list, `"top"` for what YouTube's page shows first.
- 🧵 **Threads** — `maxRepliesPerComment` puts each comment's replies right after it, oldest first, with `parentCommentId`.
- 🧹 **Filters** — `publishedWithinDays`, `minLikes`, `textContains` (words or phrases, any of them). What a filter removes is not delivered and not charged.
- 🔁 **Only new** — `onlyNewComments` with a `listName`: scheduled runs deliver what the list has not delivered before, and with replies on also the undelivered replies under comments you already have, while those comments are among a video's first `maxCommentsPerVideo`.
- 📊 **Video context on every row** — title, channel, @handle and the video's comment count, so a row stands on its own in a spreadsheet.
- 🧾 **A last word in every run** — the status message says what was delivered and what the run knows was not: filtered out, waiting for the next run, cut by a limit, refused by YouTube.

### 🎬 Quick Start

```bash
curl -X POST "https://api.apify.com/v2/acts/foxlabs~youtube-comments-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"videos":["https://www.youtube.com/watch?v=e1q-TuHdc4Y"],"maxCommentsPerVideo":100}'
```

The answer is the dataset itself: 100 comment rows, in about three seconds.

### 🚀 Getting Started (3 steps)

1. **Choose your videos** — paste video addresses into **Videos**, or channels into **Channels** to read the comments of their latest uploads.
2. **Set the limits** — `maxCommentsPerVideo` (default 100) and `maxComments` (default 1,000) cap the rows; add `maxRepliesPerComment` for replies, or switch `sort` to `top`.
3. **Run and export** — download the dataset as JSON, CSV, Excel, HTML or XML. To keep watching, tick **Only comments not delivered before**, give the list a name and put the run on a schedule.

### 📥 Input

```json
{"videos":["https://www.youtube.com/watch?v=e1q-TuHdc4Y"],"maxCommentsPerVideo":100}
```

| Field | Type | Description |
|---|---|---|
| `videos` | array | Video URLs or IDs, one per line: `watch?v=…`, `youtu.be/…`, `/shorts/…`, `/live/…`, or the 11-character ID. |
| `channels` | array | Channel URLs, @handles or channel IDs: the comments of each channel's latest videos. Give `videos`, `channels`, or both. |
| `maxVideosPerChannel` | integer | For channel lines: how many of the channel's latest videos, per kind (default 5, up to 500). Members-only videos are left out. |
| `videoTypes` | array | For channel lines: `videos`, `shorts`, `live` — which of the channel's lists to take the latest from (default `["videos"]`). |
| `maxCommentsPerVideo` | integer | Rows per video — comments and replies together (default 100). |
| `maxComments` | integer | Rows in total (default 1,000). You are charged only for delivered rows. |
| `sort` | string | `newest` (default): every comment, latest first. `top`: YouTube's "Top comments" — its own selection. |
| `maxRepliesPerComment` | integer | `0` (default) reads no replies. Otherwise each comment is followed by up to this many of its replies, oldest first. |
| `publishedWithinDays` | integer | Only comments from the last N days. A comment is left out only when it is certainly older ("3 weeks ago" passes a limit of 25 days). |
| `minLikes` | integer | Only comments and replies with at least this many likes. |
| `textContains` | array | Only comments and replies whose text contains at least one of these words or phrases (up to 100; upper and lower case are the same; "cost" also finds "Costco"). |
| `onlyNewComments` | boolean | Remember delivered comments under `listName` and deliver only what is new (see *Monitoring*). Default off. |
| `listName` | string | Name of that memory: letters, digits and hyphens, up to 40 characters (default `default`). One name per project. |
| `proxyConfiguration` | object | Nothing to change for normal use: requests go out directly, and a kind of request YouTube refuses is moved to Apify Proxy by the Actor itself. |

**More recipes**

- Every comment of a video with every reply: `{"videos":["https://www.youtube.com/watch?v=rN4hGxTE2hg"],"maxCommentsPerVideo":5000,"maxComments":5000,"maxRepliesPerComment":100}` — 328 rows for the video in our test, the number YouTube shows for it.
- The top comments and the conversations under them: `{"videos":["https://www.youtube.com/watch?v=DHuVm_ol2mo"],"sort":"top","maxCommentsPerVideo":300,"maxRepliesPerComment":5}`
- Watch a channel's comments — schedule it: `{"channels":["@mkbhd"],"maxVideosPerChannel":5,"maxCommentsPerVideo":100,"onlyNewComments":true,"listName":"mkbhd-comments"}` — each run delivers only what the list has not delivered before; add `"maxRepliesPerComment":5` for new replies too.
- Comments that talk about the price: `{"videos":["https://www.youtube.com/watch?v=DHuVm_ol2mo","https://www.youtube.com/watch?v=rN4hGxTE2hg"],"textContains":["price","cost","how much"],"maxCommentsPerVideo":100}`
- The last two days on a channel's three latest videos, with replies — up to 300 rows a video (the status message says when a video reached that number before the two days were through): `{"channels":["@mkbhd"],"maxVideosPerChannel":3,"publishedWithinDays":2,"maxRepliesPerComment":10,"maxCommentsPerVideo":300}`
- The latest 100 comments of a channel's five latest Shorts: `{"channels":["@mkbhd"],"videoTypes":["shorts"],"maxVideosPerChannel":5,"maxCommentsPerVideo":100}`

### 📤 Output

One row per comment or reply (`type: "comment"` or `"reply"`). A line that is not a video, a video without a comment section and the like come back as a free row with `type: "status"` and the reason in `error`, so nothing disappears silently. Every row has the same 33 columns.

| Field | Description |
|---|---|
| `type` | `comment`, `reply`, or `status` |
| `input` | The line of your input the row belongs to: the video address, or the channel whose list named the video |
| `videoId` / `videoUrl` / `videoTitle` | The video the comment stands under |
| `videoChannelId` / `videoChannelTitle` / `videoChannelHandle` / `videoChannelUrl` | The channel the video belongs to |
| `videoCommentsCount` | The number YouTube shows above the comments — comments and replies together, to the last digit; read when the run opens the video's comments |
| `commentId` / `commentUrl` | The comment's ID (the stable key; a reply's ID begins with the ID of its comment) and its own link (`watch?v=…&lc=…`, the form YouTube puts behind a comment's age) |
| `parentCommentId` | On a reply: the comment it answers; `null` on a comment |
| `threadPosition` | The comment's place in the video's list in the order asked for (1 = first; a pinned comment is first). A reply carries the place of its comment |
| `text` | The comment as written, line breaks included |
| `publishedText` | What YouTube shows: "3 weeks ago", "7 hours ago" (English: the Actor asks YouTube in English) |
| `ageDaysMin` / `ageDaysMax` | The range of days `publishedText` can mean at the time of the run: "3 weeks ago" is 21 to 27. For months and years it holds whether YouTube counts 30-day or calendar months ("1 month ago" is 28 to 61) |
| `publishedDate` | Only for a comment less than a day old whose hour lies within one UTC day; `null` for every older comment — YouTube shows no date for it |
| `isEdited` | YouTube marks the comment "(edited)" |
| `likes` / `likesAreRounded` | The like count: exact below 1,000; from 1,000 on YouTube shows "1.2K" and the row has 1200 with `likesAreRounded: true`. `0` when nobody liked the comment |
| `replyCount` | The number of replies YouTube shows for the comment; `null` on a reply (replies are one level deep) |
| `isPinned` / `isHearted` | Pinned by the video's owner · given a heart by the video's owner |
| `authorName` / `authorHandle` / `authorChannelId` / `authorChannelUrl` | The comment's author as YouTube shows them |
| `authorIsVerified` / `authorIsCreator` | The author's channel carries YouTube's verified badge · the author is the owner of the video |
| `scrapedAt` | When the row was made |
| `error` | `null` on comment and reply rows; the reason on status rows |

Sample — a row as the Actor returned it on 2026-10-07, every column, nothing shortened:

```json
{
  "type": "comment",
  "input": "jjgJhYZXX1U",
  "videoId": "jjgJhYZXX1U",
  "videoUrl": "https://www.youtube.com/watch?v=jjgJhYZXX1U",
  "videoTitle": "SkyRover X1 Drone Review",
  "videoChannelId": "UCtzigO8afjz8vqvXNs_9TwA",
  "videoChannelTitle": "Colibri FPV",
  "videoChannelHandle": "@colibrifpv",
  "videoChannelUrl": "https://www.youtube.com/channel/UCtzigO8afjz8vqvXNs_9TwA",
  "videoCommentsCount": 87,
  "commentId": "Ugyb2xCw4FPXOuTVLxV4AaABAg",
  "commentUrl": "https://www.youtube.com/watch?v=jjgJhYZXX1U&lc=Ugyb2xCw4FPXOuTVLxV4AaABAg",
  "parentCommentId": null,
  "threadPosition": 34,
  "text": "Wow, awesomely stunning footage and scenery. Those waterfalls... Truly amazing!!!\nNice small, lightweight photography aircraft. Looks very capable and utilitarian.",
  "publishedText": "7 months ago",
  "publishedDate": null,
  "ageDaysMin": 208,
  "ageDaysMax": 247,
  "isEdited": true,
  "likes": 1,
  "likesAreRounded": false,
  "replyCount": 1,
  "isPinned": false,
  "isHearted": true,
  "authorName": "@MaxDelEcuador",
  "authorHandle": "@MaxDelEcuador",
  "authorChannelId": "UCpFT2mb4U5D_VxoC99GprUA",
  "authorChannelUrl": "https://www.youtube.com/channel/UCpFT2mb4U5D_VxoC99GprUA",
  "authorIsVerified": false,
  "authorIsCreator": false,
  "scrapedAt": "2026-10-07T22:44:18.308Z",
  "error": null
}
```

### 🧠 How it works

The Actor asks YouTube for a video's comments the way its own page does when you scroll: 20 comments a request, in the order you chose. Videos are read side by side, eight at a time. Within a video's rows the order is the order of its list — a comment, then its replies. (An "only new" list that follows a video writes its comments oldest first instead: see *Monitoring*.) The rows of different videos are written mixed, as they arrive: sort by `videoId` and `threadPosition` to have each video's comments together.

**Two orders, and they are not the same set.** We read five videos to the end in both orders. "Newest first" listed 226, 924, 2,187, 3,790 and 6,563 comments; "Top comments" listed 222, 913, 660, 1,106 and 1,250 of them — every one also in the newest list. Read again the same evening from another address, the Top list of the third video held 1,076. So Top is YouTube's own selection, large for small videos and a fraction for big ones, and not the same from one reading to the next. When a list that was followed to its end holds more than 5% — and more than 20 comments — fewer than the number YouTube shows for the video, the status message says so.

**The count.** `videoCommentsCount` is the number in the header above the comments, which counts comments and replies together. For ten videos followed newest-first to the end, the comments and the replies they show added up to it: exactly for 87, 97, 328, 1,165 and 9,548 in runs on the platform and for 2,683, 4,605 and 8,007 in a probe through a proxy; 10,250 of 10,251 in one more platform run; and 119,548 of 119,554 for the largest, read through a proxy over 99 minutes while the video went on getting comments. A comment's `replyCount` depends on the order: read 90 seconds apart, one comment showed 248 replies in "Top comments" and 349 in "Newest first", and of 1,000 comments 25 differed, Top lower every time. The newest-first counters are the ones that add up to the video's number.

**Replies.** With `maxRepliesPerComment` above 0, each comment that has replies costs one or more extra requests (YouTube gives 10 to 50 replies a request). YouTube gives replies oldest first only, so with a limit of 5 you get a comment's first five replies. Replies count toward `maxCommentsPerVideo`. In the runs that read every reply, each thread delivered exactly the number of replies it showed: 61 threads of one video (the largest 14 replies) and 18 of another (the largest 782).

**Dates.** YouTube shows a comment's age rounded down — "3 weeks ago" is 21 to 27 days — and no date. The row keeps the text and gives `ageDaysMin` / `ageDaysMax`. A comment less than a day old gets a `publishedDate` when the hour YouTube names lies within one UTC day; about one in twenty-four such comments does not, because that hour holds midnight. Newest first, ages never went backwards in our runs (44,534 comments checked), which is what lets `publishedWithinDays` stop reading a video at the first comments that are certainly older; a pinned comment is first whatever its age.

**The video's name.** For a channel line the video's title and channel come from the channel's own list, at no extra request. For a video you give by its address the Actor asks YouTube's public embed answer (one small request per video) and, once per channel, which channel the @handle belongs to. A video whose owner has turned embedding off has no embed answer: its own page names it and its channel instead — two such videos in our test, one of them with comments, which came with the video's title, channel and @handle like any others.

**Videos without comments.** YouTube's comment answer for a video without a comment section is the same empty answer whatever the reason, so the Actor reads the video's own page to tell them apart, and the status row says which it is: the page says "Comments are turned off" (a video made for kids, an ordinary video, a stream on air and one that had not started, in our test), the video is for the channel's members only, or the video is private. The page is read for the first 25 such videos of a run; beyond that a row names the reasons there can be.

**When YouTube refuses.** Requests go out directly from the run's own address, and YouTube refuses the comment requests of some runs (HTTP 403): 20 of our first 100 test runs that sent requests — 18 of them at one of the run's first ten requests, two only after 380 and 385 requests. Why YouTube turns some runs away from the start is not something we could establish. The Actor then sends the run's comment requests through Apify Proxy and goes on. All 20 runs ended complete, but slower: an early refusal takes YouTube 5 to 10 seconds to send, and through the proxy a request took 0.2 to 3.2 seconds (0.5 in the median run), against 0.03 to 0.3 directly.

### 🔁 Monitoring: only new comments

Tick `onlyNewComments`, give the list a name, and put the run on a schedule. The list is kept in a key-value store of your account.

**Newest first, without filters — the list follows each video.**

- **The first run** reads each video's first `maxCommentsPerVideo` comments — its first look — and delivers them **oldest first**. The list notes where that look ends; what lies below it is never delivered.
- **Every later run** reads from the newest comment down to where the delivered ones begin and delivers what is new, **oldest first**. A video with nothing new costs one or two comment requests.
- **Nothing is cut off by a limit.** A run delivers at most `maxCommentsPerVideo` rows for a video. When more are due — more new comments than that number, a first look whose comments and replies together do not fit into it, or a run that stopped at `maxComments`, at its spending limit or at a timeout — the run has delivered the oldest of them, and the rest are still above the delivered ones, where the next run finds them. The status message says how many are waiting: *"35 new comments of 1 video wait for the next run of the list …"*.
- **With replies on**, a comment comes with its replies, oldest first, up to `maxRepliesPerComment`; replies are rows and count toward `maxCommentsPerVideo`. Each run also looks at the video's first `maxCommentsPerVideo` comments again: a delivered comment whose replies were not all delivered, or whose reply count has changed, has its undelivered replies delivered — the oldest of them first, up to `maxRepliesPerComment` a run, so a thread longer than that number is read again in every run until it is exhausted. A reply under a comment that has moved out of those first comments is not seen.
- A first look at more than 20,000 comments (`maxCommentsPerVideo` above that) is handed over as it is read, newest first, and is carried on only when one run delivers all of it: give such a run the limits and the time to finish.

Measured on the platform on 2026-10-08, on a video that was getting three to four comments a minute (builds 0.1.9 and 0.1.10, without replies): the first run delivered its 30 newest comments; a run nine minutes later found 35 new ones, delivered the 30 oldest and said 5 were waiting; the next three runs delivered those 5, then 2, then 3. A plain reading of the video's 200 newest comments right afterwards held the 70 delivered comments, none of them twice and none missing between them — and one more above them, written about when the last of those runs was made and not in YouTube's list yet at that moment: the next run's. The same list half an hour later: 127 comments were new, and with one more that was written meanwhile they came in runs of 30, 30 and 68 — 128 different comments, none twice and none missing.

**With a filter, or in "top" order — every run looks at a window.** Each run looks at each video's first `maxCommentsPerVideo` comments and delivers what the list has not delivered: a comment that passes a filter now and did not before is delivered now. A window cannot tell whether new comments fell outside it: when a video gets more comments between two runs than the window holds, the older of them are not seen, and the status message can only say so when the video filled its number. Pick a number well above what a video gets between two runs — or follow the video without filters and filter the rows afterwards.

**What a list does not see.**

- YouTube's list shows a comment some time after it is written. In 20 readings of one busy video, about 40 seconds apart, the newest comment shown was between 5 seconds and more than a minute old, and twice the list stood still for over a minute before 14 and 19 comments came in at once. A comment written just before a run comes with the next run.
- The Actor looks for new comments above the delivered ones. In those readings all 76 comments that appeared came in at the top. A comment that YouTube adds to its list late, at its own older place — one that was held back, say — is delivered only if fewer than 20 delivered comments stand above it and it is not more than about a quarter of an hour older than the list's last complete reading of the video; otherwise a run that follows the video does not find it. How often YouTube does that we have not measured.
- A video that got more than 20,000 new comments since the list last read it: the oldest of them are not reached, and the status message says so.
- A list keeps its newest 150,000 entries. When it lets older ones go the status message says how many.

A run that finds nothing new ends with an empty dataset and the status message "No new comments, no rows charged: …".

### 🧪 Data quality — measured

Measured on the Apify platform on 2026-10-07 (build 0.1.6; the row format has not changed since).

| | One video, its 1,000 newest comments | Ten videos, 100 newest each (945 rows) | One video to its end (8,047 rows) | One video to its end with every reply (328 rows) |
|---|---|---|---|---|
| Text | 1,000 of 1,000 | 945 of 945 | 8,047 of 8,047 | 328 of 328 |
| Author's channel ID | 1,000 of 1,000 | 945 of 945 | 8,047 of 8,047 | 328 of 328 |
| Author's name and @handle | 1,000 of 1,000 | 945 of 945 | 8,046 of 8,047 | 328 of 328 |
| Like count | 1,000 of 1,000 | 945 of 945 | 8,047 of 8,047 | 328 of 328 |
| Reply count (comments) | 1,000 of 1,000 | 945 of 945 | 8,047 of 8,047 | 226 of 226 |
| Age in days | 1,000 of 1,000 | 945 of 945 | 8,047 of 8,047 | 328 of 328 |
| Date, of the comments under a day old | 30 of 32 | 203 of 210 | 30 of 32 | none under a day |
| Video title and channel | 1,000 of 1,000 | 945 of 945 | 8,047 of 8,047 | 328 of 328 |
| Unique comment IDs | 1,000 of 1,000 | 945 of 945 | 8,047 of 8,047 | 328 of 328 |

- **Dates:** the comments under a day old that got no date all read "22 hours ago" at 22:23 to 22:27 UTC — an hour that holds midnight.
- **Ten videos, 945 rows:** nine videos gave 100 each; the tenth has 45 comments (its 97 on YouTube include 52 replies), and the status message said so.
- **To the end:** the 8,047 comments of the third column show 1,501 replies between them — together the 9,548 YouTube displayed for the video. A larger video, read on 2026-10-08 with a like filter so that few rows were written: 8,539 comments showing 1,711 replies, 10,250 of the 10,251 YouTube counted. And the largest list we have followed to its end, the same day: a video whose oldest comment was four days old and for which YouTube counted 119,554 comments when the reading began. Read newest first with the Actor's own reader — from our own machine through Apify Proxy, not in a platform run — it gave 111,169 comments showing 8,379 replies, 119,548 together, 6 short of that count; one comment came twice. (The video went on getting comments during the reading, so at any one moment the gap may have been larger.) That took 5,571 requests, none refused or retried, and 99 minutes at the proxy's pace.
- **Zero is a value:** 7,091 of those 8,047 comments had no like; each has `likes: 0`.
- **With every reply:** 226 comments and 102 replies, 328 rows for a video YouTube shows 328 for. Every reply stood directly after its comment, and each of the 61 threads delivered the number of replies it showed. The same input with every request through Apify Proxy gave the same 328 rows in the same order.
- **A large thread:** 1,000 rows of another video were 167 comments and 833 replies; its 18 threads each delivered the number shown, the largest 782.
- **Kinds of video (2026-10-08, one run of eight lines):** a video with embedding turned off delivered its comments; a made-for-kids video, another with embedding off, a stream on air and a stream that had not started each got "the video's page says "Comments are turned off""; a members-only video and a private video each got a row that says so; an ID YouTube does not know got "YouTube knows no video with the ID …". A Short gives comments like any video. The three streams we saw on air showed no comment section; a stream that had ended (it was streamed in 2022) gave its comments like any video.

### ⏱️ Performance — measured

On the platform with the default 1,024 MB, evening of 2026-10-07.

- 100 comments of one video (the prefilled input): **2 to 5 seconds**; 10 to 15 seconds in the runs YouTube refused first.
- 1,000 newest comments of one video: **8 to 12 seconds** (53 requests); its 1,000 top comments: **16 seconds**. One video's comments are paged one request after the other; a page took 0.1 to 0.2 seconds newest first and about 0.3 seconds for Top.
- 945 comments of ten videos, read side by side: **4 to 5 seconds**. 80 comments of a channel's 40 latest videos: **3 seconds**.
- A video's 8,047 comments to the end: **55 to 76 seconds** in three runs. The 10,000 newest comments of a video with 659,559: **104 seconds**.
- 328 rows with every reply: **7 seconds** directly (77 requests), **13 seconds** with every request through the proxy, **24 seconds** in a run YouTube refused after 8 seconds. 1,000 rows with a thread of 782 replies, through the proxy: **57 seconds**.
- Monitoring a busy video (2026-10-08, 256 MB, nine runs of one list): **4 to 12 seconds** and 5 to 11 requests when YouTube answered directly — the more comments were new, the more requests — and **13 to 27 seconds** in the two runs it refused first.
- 49 comments that mention a price, out of 1,150 read: **7 seconds**.

A run the platform restarts continues from the rows it already delivered. We restarted three test runs on purpose: they ended with 1,246, 1,500 and 3,000 different comments and no row twice. An "only new" run that was restarted with 525 rows written and none of them in its list yet took them into the list, and the list's next run delivered nothing. A run that timed out after 2,650 comments with 2,000 of them in its list was followed by a run that read the other 650 back from the dead run's dataset and delivered none of them twice.

### 💼 Use cases

**1. Community and brand monitoring** — know what is written under your videos, or a competitor's, without re-reading everything.
*Input:* your channels with `onlyNewComments` on a schedule. *Output:* each run's new comments, oldest first, with the video they stand under. *Use:* a daily digest, an alert on a keyword, a support queue.

**2. Audience research** — what viewers ask for, praise and complain about.
*Input:* the videos of a topic with `sort: "newest"` and limits high enough for every comment. *Output:* the full comment set with likes and reply counts to weigh it. *Use:* topic and sentiment analysis, FAQ mining, content planning.

**3. Product and launch feedback** — the comments that mention your product, price or a problem.
*Input:* review videos with `textContains: ["price", "battery", "broke"]`. *Output:* only the matching comments (and, with `maxRepliesPerComment` set, the matching replies). *Use:* a feedback board per product, counted by word.

**4. Creator workflows** — what to answer first.
*Input:* your latest videos with `maxRepliesPerComment`. *Output:* threads with `isHearted`, `isPinned`, `authorIsCreator` and reply counts. *Use:* find questions without an answer from you, and the comments worth a pin.

**5. Datasets for analysis and AI agents** — comments with their thread structure.
*Input:* any list of videos. *Output:* one row per comment or reply with `parentCommentId`, author and video context. *Use:* training and evaluation sets, summarisation, retrieval.

### 🔗 Integration

**JavaScript / Node.js**

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('foxlabs/youtube-comments-scraper').call({"videos":["https://www.youtube.com/watch?v=e1q-TuHdc4Y"],"maxCommentsPerVideo":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items.filter((r) => r.type !== 'status')) console.log(row.threadPosition, row.likes, row.authorName, row.text);
```

**Python**

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('foxlabs/youtube-comments-scraper').call(run_input={"videos": ["https://www.youtube.com/watch?v=e1q-TuHdc4Y"], "maxCommentsPerVideo": 100})
for row in client.dataset(run.default_dataset_id).iterate_items():
    if row['type'] != 'status':
        print(row['threadPosition'], row['likes'], row['authorName'], row['text'])
```

Written for `apify-client` 3 for Python, where `call()` returns a run object; with version 2 it returns a dict — write `run['defaultDatasetId']` there.

**Automation (n8n / Zapier / Make):** schedule or webhook → run the Actor with your `channels` and `onlyNewComments` → handle the JSON dataset → push new comments to a sheet, Slack, a CRM or a dashboard.

### 📊 Pricing

Pay-per-event: **$0.0004 per delivered comment or reply** ($0.40 per 1,000 rows), plus Apify's run-start event ($0.00005 for a run with up to 1 GB of memory — the default — also for a run that delivers nothing; Apify counts this event per GB of run memory, so a run you start with more is charged more of them: we measured 256 MB and 1 GB, one event each). [Current pricing](https://apify.com/foxlabs/youtube-comments-scraper).

**Not charged:** status rows, comments that were read but did not pass your filters, and comments an "only new" list had delivered before.

**Spending limit.** Set a maximum cost per run in the run options. Before every row the Actor checks that it still fits; when the next row would not, it stops delivering, keeps what it has delivered, and says so in its status message. In an "only new" run what was left comes with the next run.

### ❓ FAQ

**Do I get all comments of a video?** With `sort: "newest"` (the default) and limits high enough, yes — see *The count* above: for every video we followed to its end, the comments and the replies they show added up to the number YouTube shows: exactly for the smaller ones, to within one comment for a video of 10,251 and to within 6 for one of 119,554. One video is paged one request after the other: about 100 to 150 comments a second in our fastest runs (1,024 MB, YouTube answering directly), 40 a second in a 256 MB run that YouTube refused part-way, 19 a second through the proxy from our own machine — so a video with several hundred thousand comments does not fit into the default run timeout of one hour. "Top comments" is a selection YouTube makes; for a large video it is a fraction of the comments.

**Why is there no date?** YouTube shows only "3 weeks ago". The row gives that text and the range of days it can mean; a date is given only for comments less than a day old, and not for the few whose hour holds midnight. If you run the Actor every day with `onlyNewComments`, most new comments arrive while YouTube still shows their age in hours.

**Why does `likes` say 25000 and not the exact number?** From 1,000 likes on YouTube shows "25K". `likesAreRounded` tells you which counts are shortened.

**Are replies included?** When you set `maxRepliesPerComment`. They count toward `maxCommentsPerVideo`, and come oldest first — the only order YouTube gives them in.

**What happens when a video gets more new comments than `maxCommentsPerVideo` between two runs?** Newest first and without filters, the run delivers the oldest of them and its status message says how many are waiting; the following runs deliver the rest, oldest first. Nothing has to be re-read by hand. Raise the number to get more of them in one run. With a filter or in "top" order nothing is carried over: a run looks at the video's first `maxCommentsPerVideo` comments and no further (see *Monitoring*).

**Does "only new comments" also find new replies?** Yes, with `maxRepliesPerComment` set, for the comments within each video's first `maxCommentsPerVideo`: a delivered comment whose replies were not all delivered, or whose reply count has changed, is read again and its undelivered replies are delivered, the oldest first, up to `maxRepliesPerComment` a run. A reply under a comment that has moved out of those first comments is not seen.

**Where is a list kept, and how do I start it over?** In a key-value store named `youtube-comments-<listName>` in your Apify account. Delete that store, or use another `listName`, to start from nothing. A list keeps its newest 150,000 entries — one for each delivered comment and reply, one for each reply count at which a thread's replies were read to the end, and one note per video it follows.

**Does it need a proxy or an API key?** No API key, and nothing to set up. Requests go out directly; when YouTube refuses a run's comment requests the Actor sends them through Apify Proxy by itself. Our tests ran on an account with 30 datacenter proxy addresses (read from the account on 2026-10-08); how the switch behaves on a plan with fewer we have not measured. On an account that cannot use Apify Proxy there is nothing to switch to: a video YouTube refuses then ends as "YouTube did not answer".

**Can I export to CSV / Excel / JSON?** Yes — directly from the Apify dataset.

### 🐛 Troubleshooting

- **A status row instead of comments** — the `error` column says why: "YouTube lists no comments for …: the video's page says "Comments are turned off"", "… is a members-only video …", "… is private …", "… has no comments yet", "YouTube knows no video with the ID …", "None of the N comments read for … passed your filters", or "… is not a YouTube video: …" for a line that is something else.
- **"N new comments of N videos wait for the next run of the list …"** — an "only new" run found more new comments than it delivered. They are not lost: run the list again, or raise `maxCommentsPerVideo` / `maxComments` to get more of them in one run.
- **"YouTube's "Top comments" list held only part of the comments of N videos (…)"** — Top is YouTube's selection; use `sort: "newest"` for every comment.
- **"YouTube counts N comments for … but listed none"**, or **"YouTube's page shows a comment section for …, but its comment list came back empty"** — YouTube answered with an empty list for a video that has comments. It is rare: in 80 readings of one busy video's first page none was empty, in 20 longer readings one was. The Actor asks such a first page once more before it believes it, and an "only new" list does not take the empty answer for a look at the video. Nothing is charged; run it again.
- **"N videos have fewer comments than maxCommentsPerVideo"** — those lists ended. With "(replies are not read: maxRepliesPerComment is 0)" the rest of the video's count are replies.
- **"Stopped after reading N comments: fewer than one in a hundred passed your filters"** — loosen the filters, or filter the full set yourself. **"…: a run reads at most 50 comments for each row of maxComments (N), and at least 300 …"** is the other limit on reading: raise `maxComments`.
- **"No new comments, no rows charged …"** — nothing the list has not delivered before stood in the part of the videos' lists the run looked at.
- **"N videos reached maxCommentsPerVideo (…): with a filter or in "top" order a run looks at a video's first N comments …"** — raise `maxCommentsPerVideo` to look further.
- **"N comments came without their replies, which YouTube did not give"** — the comments are delivered; run again for the replies.
- **"The comments of N videos stopped answering before their end (…)"** — what came is delivered; run those videos again for the rest.
- **"… another run of the list … is in progress, or was cut off in the last three minutes"** — two runs of the same `listName` would deliver the same comments; start the second when the first has finished.
- **A run that fails at once with "listName may contain letters, digits and "-" …" or "onlyNewComments: this input looks at up to N comments in every run …"** — the input was refused before any request; the message says what to change.

### 🚧 What it does not do

- No exact timestamps and no dates for comments older than a day: YouTube does not show them.
- No unrounded like counts from 1,000 on: YouTube shows "1.2K".
- Replies come oldest first only, and are one level deep (that is how YouTube keeps them).
- No playlists and no search as input: videos and channels.
- No comments of members-only or private videos, no Community-post comments, no live-chat messages. An age-restricted video was not among our tests: we could not confirm one without signing in.
- No profile pictures, and nothing about a comment's author beyond the public name and channel.
- No translation: a comment comes in the language it was written in.
- After a restart by the platform the lists are asked again, so `threadPosition` of rows written after the restart belongs to the new reading.

### ⚖️ Is it legal to scrape YouTube comments?

The Actor reads only what YouTube shows to every visitor who is not signed in: no login, no cookies, no CAPTCHA solving. Comments, author names and channel IDs are public, and they are personal data: use them for a lawful purpose under the law that applies to you (the GDPR in the EU, for instance), do not build profiles of individuals with them, and check that your use fits YouTube's terms. YouTube is a trademark of Google LLC; this Actor is not affiliated with, endorsed or sponsored by YouTube or Google.

### 🤝 Support & contact

- 🌐 **Website:** [data.foxlabs.com.tr](https://data.foxlabs.com.tr)
- 📧 **Email:** info@foxlabs.com.tr
- 🐛 **Issues:** open a ticket in the Actor's **Issues** tab
- 🧰 **More data actors:** [Fox Labs on Apify](https://apify.com/foxlabs)

### Changelog

#### 0.1.13 — 2026-10-08

- The note "N new comments … wait for the next run" counts a video's pinned comment too while that has not been delivered; it said one too few. What a run delivers did not change.

#### 0.1.12 — 2026-10-08

- An "only new" list's first look at a video is carried on by the next runs when one run cannot deliver it whole (replies count as rows; a run can stop at a limit): it is delivered oldest first, and nothing of it is skipped while new comments arrive. Before, such a look was started again and could lose its older part without a word.
- A run that delivers nothing now says "no rows charged" (it said "nothing charged"; the run-start event applies to every run). This README was corrected after an independent fact-check — the list is in the Changelog tab.

#### 0.1.9 to 0.1.11 — 2026-10-08

- "Only new comments" delivers a video's new comments oldest first and carries a burst over to the next run; a video without a comment section gets a status row that says why.

#### 0.1 — 2026-10-07 — first version

- The comments of videos, or of a channel's latest videos: newest first (every comment) or top comments (YouTube's selection), replies on request, 33 columns a row.
- "Only new comments": a named list follows each video and delivers its new comments oldest first, so a burst larger than one run is carried over to the next runs. With a filter or in "top" order a run looks at each video's first comments instead.
- A video without a comment section gets a status row that says why: comments turned off, members only, private.
- Filters by age, likes and words; a spending limit that is exact; runs that continue after a restart. The full list is in the Changelog tab.

# Changelog

This Actor's version history is a separate document: https://apify.com/foxlabs/youtube-comments-scraper/changelog.md

# Actor input Schema

## `videos` (type: `array`):

YouTube video URLs or video IDs, one per line. Every form of the address works: watch?v=…, youtu.be/…, /shorts/…, /live/…, or the 11-character ID alone.

## `channels` (type: `array`):

Channel URLs, @handles or channel IDs, one per line. For each channel the Actor takes its latest videos (as many as "Latest videos per channel" says) and reads their comments. Use it alone or together with "Videos".

## `maxVideosPerChannel` (type: `integer`):

For channel lines: how many of the channel's latest videos to read comments from. Videos for the channel's paying members are left out.

## `videoTypes` (type: `array`):

For channel lines: which of the channel's lists to take the latest videos from.

## `maxCommentsPerVideo` (type: `integer`):

How many rows to deliver for each video — comments and replies together. YouTube gives 20 comments a request, so 1,000 comments of one video take about 50 requests.

## `maxComments` (type: `integer`):

The run stops when this many rows have been delivered. You are charged only for delivered rows.

## `sort` (type: `string`):

YouTube's own two orders. "Newest first" lists every comment of a video, latest first — the order for a complete set and for monitoring. "Top comments" is what YouTube's page shows first: its own selection of the most engaging comments, which for a video with thousands of comments is only a part of them (the run's status message says when a Top list ended short). A pinned comment stands first in both.

## `maxRepliesPerComment` (type: `integer`):

0 (the default) reads no replies. With a number, each comment is followed in its video's rows by up to that many of its replies, oldest first — the only order YouTube gives replies in. Reading replies takes one or more extra requests per comment that has them.

## `publishedWithinDays` (type: `integer`):

Only comments from the last N days. YouTube shows a comment's age rounded down ("3 weeks ago" is 21 to 27 days), so a comment is left out only when it is certainly older. With "Newest first" the Actor stops reading a video once it reaches older comments.

## `minLikes` (type: `integer`):

Only comments and replies with at least this many likes. Leave empty (or 0) for no minimum.

## `textContains` (type: `array`):

Only comments and replies whose text contains at least one of these words or phrases, one per line (up to 100). Upper and lower case are the same. A word is found anywhere in the text, also inside a longer word: "cost" also finds "Costco".

## `onlyNewComments` (type: `boolean`):

Remember delivered comments under the list name below and skip them in later runs. Schedule the same videos or channels with "Newest first" and no filters, and every run brings what was written since: the first run reads each video's first comments up to "Maximum rows per video", every later run its new comments, and both deliver oldest first. When more is due than a run delivers — more new comments than that number, replies that count as rows, a run that stopped early — the rest is not lost: the next run delivers it, and the status message says how many comments are waiting. With a filter or "Top comments", every run looks at each video's first comments up to that number and delivers what the list has not delivered; what falls outside that window is not seen.

## `listName` (type: `string`):

Name of the memory used by "Only comments not delivered before". Use a different name for each project. Letters, digits and hyphens (a hyphen not first or last), up to 40 characters; capitals are read as lower case.

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

Nothing to change for normal use: requests go out directly, and a kind of request YouTube refuses (or keeps leaving unanswered) is moved to Apify Proxy by the Actor itself. Turn a proxy on here only if you want every request to use it.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=e1q-TuHdc4Y"
  ],
  "maxVideosPerChannel": 5,
  "videoTypes": [
    "videos"
  ],
  "maxCommentsPerVideo": 100,
  "maxComments": 1000,
  "sort": "newest",
  "maxRepliesPerComment": 0,
  "onlyNewComments": false,
  "listName": "default",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "videos": [
        "https://www.youtube.com/watch?v=e1q-TuHdc4Y"
    ],
    "maxCommentsPerVideo": 100,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/youtube-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 = {
    "videos": ["https://www.youtube.com/watch?v=e1q-TuHdc4Y"],
    "maxCommentsPerVideo": 100,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/youtube-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 '{
  "videos": [
    "https://www.youtube.com/watch?v=e1q-TuHdc4Y"
  ],
  "maxCommentsPerVideo": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call foxlabs/youtube-comments-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foxlabs/youtube-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/PAHYeDWZzNo7iMlIF/builds/ymhb4VUGSyQqwWlnd/openapi.json
