# YouTube Comments Scraper: Replies, Shorts & Video Comments (`hyperbach/youtube-comments-scraper`) Actor

Scrape YouTube comments and replies, no login or API key: a YouTube comment scraper and comments extractor for video, Shorts and community post URLs, a channel, playlist or search. Full text, author, likes, reply count, date, pinned and hearted flags, the exact comment count. Newest or top.

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

## Pricing

from $0.90 / 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.
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

## YouTube Comments Scraper: Replies, Shorts & Video Comments

**YouTube comments as data, logged out: video, Shorts and community-post URLs in — or a channel's latest videos, a playlist or a keyword search — and one row per comment out, with its replies on request.** Each comment has its whole text with every link unwrapped, its @mentions and time links, the author's @handle, channel id and avatar, the creator, verified and artist flags, the likes (exact below 1,000, YouTube's own rounding past it, never invented), the reply count, the published time as YouTube writes it with an approximate date and its range, and the edited, pinned and hearted-by-creator flags read from that comment itself. Every row carries its video's title, channel and **exact comment count** ("2,460,185", where the page says "2.4M"). Every video and post gets a free summary row: what was delivered, the coverage against the count, and a verdict — COMPLETE, TRUNCATED (YouTube's own list ended early), CAPPED (your limit) or PARTIAL. Comments turned off, a missing video, a premiere, a members-only video, a live stream: free rows that say which. Replies are capped and billed apart from comments. No login, no YouTube account, no cookie, no API key, proxies handled for you.

### Why this YouTube comments scraper

- **Every comment of a video, with a proof.** Newest first walks YouTube's whole list to its own end: on our test video 247 comments and 113 replies, 360 of the 362 its header counts (YouTube counts held or removed comments it does not show). The video's summary row says how many were delivered against the exact count and gives a verdict, so a short dataset is never silent. YouTube's Top order is a ranking that ends at about 1,000–1,200 comments on a famous video (1,199 of 2.46 million on our test): the row says TRUNCATED with the count, and Newest reads the rest.
- **Pinned and hearted, right.** The pinned flag is YouTube's own pin on that comment and the heart is read from that comment's own toolbar, never guessed from the page: on our test video the creator's pinned comment and the hearted ones came out as YouTube shows them, where 6 and 7 rivals got these two flags wrong on the same video.
- **Likes and counts that do not lie.** Below 1,000 the likes and reply counts are exact; past it YouTube itself only shows a rounded text ("1.6K", "568K"), so the row gives that number, the text beside it and `likes_exact` false — never digits YouTube does not show. The comment count is the exact one from YouTube's comments header, on every row.
- **Replies apart, capped apart.** Replies come on request (`includeReplies`), each its own row with its parent's id, up to `maxRepliesPerComment`; `maxComments` counts comments only. A thread is read 181–191 replies a call, so a 963-reply thread takes 5 calls.
- **A date cut-off that holds.** `oldestCommentDate` (a date or "7 days") reads newest first and stops at the first comment older than the date; the pinned comment at the top of the list is not taken as the newest. YouTube shows a comment's age in words ("3 weeks ago"), and every row carries the text, an approximate date and the range the text allows.
- **More than video URLs.** Shorts, live and embed links, community posts, a channel's latest videos, Shorts or posts, a playlist, or the top videos of a keyword search from the country you pick — each video's comments with the same columns, and one row and one charge per comment whatever inputs overlap.
- **Fast, plain HTTP.** YouTube's own JSON answers, masked to the fields a row needs: no browser, no cookie, no API key. On our test one video's 360 comments and replies took 6 seconds, and the 300 newest of a 2.46-million-comment video with two more videos beside them 7 seconds.

### Who it's for

- **Marketers and brand teams** — what viewers say under a campaign's or a competitor's videos, newest first with a date cut-off, the creator's pinned and hearted picks marked, on a schedule with onlyNew.
- **Researchers and analysts** — every comment of a video with its replies and the coverage against YouTube's exact count, for sentiment, topic or discourse analysis; a keyword search's top videos read from a chosen country.
- **Creators and channel managers** — a channel's latest videos, Shorts or community posts and their comments, with likes, reply counts and the questions viewers ask; keyword and likes filters to find them.
- **Developers and data teams** — typed rows keyed by comment_id with parent_id for the thread, the video's exact comment count on each, a free summary row per video, a run summary to check a pipeline against.

### Quick start

**The 300 newest comments of a video (the leader's default job)**

```json
{
  "startUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "maxComments": 300
}
```

**Every comment and reply of a video**

```json
{
  "startUrls": [
    "https://www.youtube.com/watch?v=krsuaUp__pM"
  ],
  "maxComments": 0,
  "includeReplies": true
}
```

**The top 50 comments of a Short and a community post**

```json
{
  "startUrls": [
    "https://www.youtube.com/shorts/cEXeBA42_3c",
    "https://www.youtube.com/post/Ugkx2lkCYp85jak0n5z7-bq_4ug2Rntk9iWT"
  ],
  "maxComments": 50,
  "sortCommentsBy": "TOP_COMMENTS"
}
```

**The last 30 days of comments on a channel's 5 latest videos**

```json
{
  "startUrls": [
    "https://www.youtube.com/@hello_interview"
  ],
  "maxVideos": 5,
  "maxComments": 0,
  "oldestCommentDate": "30 days"
}
```

**Comments that mention a word, under a keyword search's top videos**

```json
{
  "searchQueries": [
    "web scraping"
  ],
  "maxVideos": 3,
  "maxComments": 0,
  "includeKeywords": [
    "python"
  ]
}
```

### Output

One row per comment and one per reply (and one free summary row per video, post, channel, playlist or search):

| field | meaning |
|---|---|
| `type` | `comment` (a top-level comment, charged), `reply` (a reply to a comment, charged as a reply), or a free summary row: `video` or `post` (one per video or community post: its status, what was read, the exact count, the verdict), `list` (one per channel, playlist or search input), `input` (a value that names nothing). |
| `status` | A comment or reply: `ok`. A video or post row: `ok` (its comments were read; see `verdict`), `comments_off` (the creator turned them off), `not_found` (no video with that id: two reads agree), `upcoming` (a premiere or stream not started), `members_only`, `live` (a live stream: its chat is not comments), `no_comments`, `failed` (YouTube served the video and no comments list, or refused it), `not_read` (maxItems, the charge limit or the time limit came first). A list row: `ok`, `not_found`, `failed`, `not_read`. An input row: `invalid`. |
| `comment_id` | YouTube's comment id (a reply's id is its parent's id, a dot and its own); the de-duplication key: one row and one charge per comment a run. |
| `comment_url` | The comment on YouTube: the video's URL with `&lc=<comment id>` (YouTube opens the page with this comment on top), or the post's `?lc=`. |
| `parent_id` | On a reply: the id of the comment it answers; null on a comment. |
| `reply_level` | 0 for a comment, 1 for a reply (YouTube nests one level). |
| `position` | 1-based place in the video's list in the order read (`sort`); a reply's place in its thread. |
| `text` | The whole comment as YouTube shows it, emoji and line breaks kept. |
| `author_handle` | The author's @handle (YouTube shows no other name on a comment). |
| `author_channel_id` | The author's channel id (`UC…`). |
| `author_channel_url` | The author's channel on YouTube. |
| `author_avatar_url` | The author's avatar image URL (not downloaded). |
| `author_is_creator` | True when the video's (or post's) own channel wrote it, read from YouTube's own flag on the comment. |
| `author_is_verified` | True when the author has YouTube's verified badge. |
| `author_is_artist` | True when the author is an official artist channel. |
| `likes` | The likes as a number: exact below 1,000; past it YouTube shows only a rounded text ("1.6K"), read as 1,600 with `likes_exact` false. 0 when YouTube shows none. |
| `likes_text` | The likes exactly as YouTube writes them ("285", "1.6K", "568K"); null for 0. |
| `likes_exact` | False when YouTube rounded the likes (1,000 and more). |
| `reply_count` | The replies YouTube says the comment has: exact below 1,000, rounded past it ("1K"). |
| `reply_count_text` | The reply count as YouTube writes it; null for none. |
| `reply_count_exact` | False when YouTube rounded the reply count. |
| `published_text` | When it was posted, as YouTube writes it logged out ("3 weeks ago", "1 year ago (edited)"); YouTube serves no exact time without an account. |
| `published_at_approx` | The approximate time from the text and the read time: the read time minus the stated age ("3 weeks ago" = 21 days before the read); null when the text is not in English. |
| `published_after` | The earliest time the text allows ("3 weeks ago": 28 days before the read). |
| `published_before` | The latest time the text allows ("3 weeks ago": 21 days before the read). |
| `published_at_is_estimated` | True on every row with a computed date: it comes from YouTube's relative text, not from an exact time. |
| `is_edited` | True when YouTube marks the comment "(edited)". |
| `is_pinned` | True on the comment the creator pinned (YouTube's own pin on that comment). |
| `is_hearted` | True when the creator gave the comment a heart, read from that comment's own toolbar. |
| `links` | Every link in the text, as its real URL (YouTube's redirect unwrapped). |
| `mentions` | Every channel the text @mentions: {handle, channel_id, url}. |
| `timestamps` | The video times the text links to, in seconds (a "2:15" in a comment). |
| `video_id` | The video's 11-character id; null on a post's rows. |
| `video_url` | The video on YouTube. |
| `video_title` | The video's title as YouTube shows it. |
| `video_channel_id` | The channel that published the video (or the channel of a channel input). |
| `video_channel_name` | The publishing channel's name. |
| `video_published_text` | The video's date as YouTube's watch page shows it ("Jun 16, 2024"). |
| `video_comment_count` | The video's (or post's) exact comment count from YouTube's own comments header ("2,460,185 Comments"; the page rounds it to "2.4M"). It counts replies too. |
| `post_id` | The community post's id, on a post's rows. |
| `post_url` | The community post on YouTube. |
| `sort` | `newest` or `top`: the order the comments were read in. |
| `verdict` | On a video or post row: `COMPLETE` (the list's end), `TRUNCATED` (YouTube's own list ended well before the count, as Top does on a famous video), `CAPPED` (maxComments, maxItems, the date cut-off or onlyNew came first), `PARTIAL` (stopped by an error or the time limit). On a list row: `COMPLETE` or `CAPPED` at maxVideos. |
| `comments_read` | On a video or post row: the comments delivered from it. |
| `replies_read` | On a video or post row: the replies delivered from it (null when Replies is off). |
| `coverage` | On a video or post row: (comments + replies delivered) ÷ the exact count. |
| `pages_read` | On a video or post row: the comment pages read (20 comments a page). |
| `videos_found` | On a list row: the videos (and posts) the channel, playlist or search gave. |
| `input` | The value you gave that led to this row. |
| `input_type` | `video`, `post`, `channel`, `playlist`, `search` or `invalid`. |
| `search_country` | On rows from a keyword search: the country the search was read from (YouTube's own answer names it; a search served from another country is asked again on a fresh exit, then a free failed row). |
| `note` | Why, in words: the reason of a free row, what a walk did not read and why. |
| `scraped_at` | When the row was read (UTC). |

Example record:

```json
{
  "type": "comment",
  "status": "ok",
  "comment_id": "UgxwgNrhdItM14Pg_BF4AaABAg",
  "comment_url": "https://www.youtube.com/watch?v=krsuaUp__pM&lc=UgxwgNrhdItM14Pg_BF4AaABAg",
  "parent_id": null,
  "reply_level": 0,
  "position": 1,
  "text": "I had to cut a ton to keep this short enough. The full written breakdown has the deep dives I couldn't fit (including the diagrams):\n→ https://hellointerview.com/youtube/web-crawler/pinned-comment\n\nAnd honestly, watching me solve it is way less useful than trying it yourself. You can practice the exact problem with instant feedback step-by-step here:\n→ https://hellointerview.com/practice/youtube",
  "author_handle": "@hello_interview",
  "author_channel_id": "UC3kf-QFT6FZzDk9JsPg8Svg",
  "author_channel_url": "https://www.youtube.com/@hello_interview",
  "author_avatar_url": "https://yt3.ggpht.com/EF0G_E_LsoqwayBpT8S4WheoMyMQHOhL6TMeM3sHZlew4Kmv7l7sTigvFv8MIudBSoOIbRyS=s88-c-k-c0x00ffffff-no-rj",
  "author_is_creator": true,
  "author_is_verified": false,
  "author_is_artist": false,
  "likes": 6,
  "likes_text": "6",
  "likes_exact": true,
  "reply_count": 1,
  "reply_count_text": "1",
  "reply_count_exact": true,
  "published_text": "1 year ago (edited)",
  "published_at_approx": "2025-10-21T03:57:15Z",
  "published_after": "2024-10-21T03:57:15Z",
  "published_before": "2025-10-21T03:57:15Z",
  "published_at_is_estimated": true,
  "is_edited": true,
  "is_pinned": true,
  "is_hearted": false,
  "links": [
    "https://hellointerview.com/youtube/web-crawler/pinned-comment",
    "https://hellointerview.com/practice/youtube"
  ],
  "mentions": [],
  "timestamps": [],
  "video_id": "krsuaUp__pM",
  "video_url": "https://www.youtube.com/watch?v=krsuaUp__pM",
  "video_title": "Design a Web Crawler System Design Interview w/ a Ex-Meta Staff Engineer",
  "video_channel_id": "UC3kf-QFT6FZzDk9JsPg8Svg",
  "video_channel_name": "Hello Interview",
  "video_published_text": "Jun 16, 2024",
  "video_comment_count": 362,
  "post_id": null,
  "post_url": null,
  "sort": "top",
  "verdict": null,
  "comments_read": null,
  "replies_read": null,
  "coverage": null,
  "pages_read": null,
  "videos_found": null,
  "input": "https://www.youtube.com/watch?v=krsuaUp__pM",
  "input_type": "video",
  "search_country": null,
  "note": null,
  "scraped_at": "2026-10-21T03:57:15Z"
}
```

### Pricing

**Pay for the comments and replies a run delivers**, plus one run start; no platform usage is billed to you. The row prices fall with your Apify plan.

| event | unit | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
| Comment (`comment`) | per 1,000 | $2.00 | $1.50 | $1.20 | $0.90 |
| Reply (`reply`) | per 1,000 | $2.00 | $1.50 | $1.20 | $0.90 |
| Run start (`start`) | per run | $0.0015 | $0.0015 | $0.0015 | $0.0015 |

A comment is **$2.00 per 1,000** on the Free plan ($0.002 a comment) and $1.50 on Starter; a reply, only when you switch `includeReplies` on, $2.00 per 1,000 on Free and $1.50 on Starter, capped apart from the comments by `maxRepliesPerComment`. Each run that delivers a comment or a reply also pays the **run start, $0.0015 once**; a run that delivers none pays nothing at all. So the 300 newest comments of a video cost $0.6015 on the Free plan and $0.4515 on Starter; every comment and reply of our 362-comment test video (247 comments, 113 replies) $0.7215 and $0.5415; 1,000 comments $2.0015 and $1.5015; several videos in one run share one start. Free: every summary row (a video's or a post's status, counts and verdict; a channel's, playlist's or search's list row), comments turned off, a missing, upcoming, members-only or live video, a value that names nothing, an input left unread at a cap or the time limit, a comment a keyword or likes filter leaves out, and a comment `onlyNew` skips. A comment is charged once a run whatever inputs overlap; a run that stops early (a timeout, an abort) is charged for the rows it delivered. `maxItems` counts the charged rows. Before the first request the log names the price of each row at your plan, the run start and, when a cap bounds the run (`maxComments` with `maxRepliesPerComment` when replies are on, `maxVideos` for a list, `maxItems`), the most it can cost, the cap filled with the dearer rows first. With `maxComments` 0 (every comment) and no `maxItems` a run has no bound but YouTube's own lists, which run from a few comments to millions: the log names YouTube's count for each video when its first page is read and what reading it costs (5 ordinary videos of one channel: 343 rows against their counted 344; a famous music video: 2.46 million). Set `maxComments`, `maxItems` or a charge limit for a lower maximum. Enterprise plans have their own rates — the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **What to give** — `startUrls`: videos and Shorts (watch, youtu.be, /shorts/, /live/, /embed/ links, or an 11-character id), community posts (/post/ URLs), channels (URL, @handle or UC… id), playlists (URL or PL… id), search URLs; the leader's `{"url": …}` objects work too. `searchQueries`: keywords, each the search for its top `maxVideos` videos. A value repeated is read once; a value that names nothing is a free `invalid` row.
- **How many comments** — `maxComments` (100 by default; 0 = all) caps the comments of each video or post, in the order of `sortCommentsBy`. Replies are read with `includeReplies` and capped by `maxRepliesPerComment` (0 = all), apart from `maxComments`. `maxItems` caps the charged rows (comments and replies) of the whole run. A video's summary row says whether its list ended (COMPLETE), YouTube's own list ended well before the count (TRUNCATED: the Top order on a famous video) or a limit came first (CAPPED).
- **Newest or top** — `NEWEST_FIRST` (the default) is YouTube's chronological list: it holds every comment the video shows, so it is the order for all comments, a date cut-off and onlyNew. `TOP_COMMENTS` is YouTube's ranking (the order the page shows first); on a famous video it ends at about 1,000–1,200 comments, and on a small video it can leave some out. The creator's pinned comment heads both lists; newest first it is delivered at its own date.
- **Channels, playlists and searches** — A channel reads its `maxVideos` newest videos (10 by default), or its Shorts or both (`channelTab`), and with `maxPosts` its latest community posts; a channel URL that ends in /shorts, /streams or /posts reads that tab. A playlist reads its first `maxVideos` videos in its order, a keyword search its top `maxVideos` videos from `searchCountry`. Each list gets a free summary row with the videos it found; each video its own summary row.
- **Scheduled runs: only the new comments** — Save a task with `onlyNew` (and a `stateStoreName` to name the memory) and schedule it: a comment an earlier run of that memory delivered is skipped free, and newest first a video's walk stops at the first comment delivered before. When more new comments came than `maxComments`, the oldest new ones are delivered first and the summary row says how many wait for the next run, so none is lost. The memory keeps the newest 20,000 comment ids.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `startUrls` | `array` | `[]` | Videos and Shorts (watch, youtu.be, /shorts/, /live/, /embed/ links or an 11-character id), community posts (/post/<id>), channels (URL, @handle or UC… id: their latest videos, see maxVideos and channelTab; a URL that ends in /shorts, /streams or /posts reads that tab), playlists (URL or PL… id) or search URLs. The leader's {"url": …} objects are read too. A value that names nothing is a free row that says why. |
| `searchQueries` | `array` | `[]` | Keywords: the top maxVideos videos of each YouTube search are read, from the country in searchCountry (YouTube shows each country a different search). |
| `maxComments` | `integer` | `100` | Comments read per video or post, in the order of sortCommentsBy; 0 = every comment YouTube lists. Replies are not counted in it (see maxRepliesPerComment). The summary row of each video says whether its list ended (COMPLETE), YouTube's list ended early (TRUNCATED) or this cap came first (CAPPED). |
| `sortCommentsBy` | `NEWEST_FIRST` / `TOP_COMMENTS` | `"NEWEST_FIRST"` | YouTube's own two orders. Newest first walks every comment; Top comments is YouTube's ranking, which ends at about 1,000–1,200 comments on a famous video. |
| `oldestCommentDate` | `string` |  | Only comments published on or after this date: YYYY-MM-DD, or a number of days ("1 day" = today's comments, "7 days" = the last week), weeks, months or years. It reads newest first. YouTube shows a comment's age in words ("3 weeks ago"), so the cut-off works to that precision. |
| `includeReplies` | `boolean` | `false` | Read the replies of every comment delivered, each its own row with the parent comment's id (charged as a reply). |
| `maxRepliesPerComment` | `integer` | `0` | Replies read per comment when Replies is on; 0 = every reply. |
| `includeKeywords` | `array` | `[]` | Keep a comment only when its text contains one of these words or phrases as a whole word (any case: "art" does not keep "start"). A comment left out is free and does not count toward maxComments; a kept comment comes with all its replies. |
| `excludeKeywords` | `array` | `[]` | Leave out a comment whose text contains one of these words or phrases as a whole word (any case). Free. |
| `minLikes` | `integer` | `0` | Keep a comment only with at least this many likes (YouTube rounds likes past 999: "1.6K" counts as 1,600). 0 = every comment. |
| `maxVideos` | `integer` | `10` | Videos taken from each channel (newest first), playlist (in its order) or search (top results). |
| `channelTab` | `videos` / `shorts` / `both` | `"videos"` | Which of a channel's lists maxVideos takes from. |
| `maxPosts` | `integer` | `0` | A channel's latest community posts whose comments are read too; 0 = none. |
| `searchCountry` | `US` / `GB` / `CA` / `AU` / `NZ` / `IE` / `IN` / `PK` / `BD` / `PH` / `ID` / `MY` / `SG` / `TH` / `VN` / `JP` / `KR` / `TW` / `HK` / `DE` / `AT` / `CH` / `FR` / `BE` / `NL` / `ES` / `PT` / `IT` / `PL` / `CZ` / `RO` / `HU` / `GR` / `SE` / `NO` / `DK` / `FI` / `TR` / `UA` / `IL` / `AE` / `SA` / `EG` / `MA` / `NG` / `KE` / `ZA` / `BR` / `MX` / `AR` / `CO` / `CL` / `PE` / `LK` / `KZ` / `NP` | `"US"` | Where each keyword search is read from (YouTube shows each country a different search). A video's comments are the same everywhere. |
| `onlyNew` | `boolean` | `false` | Skip every comment an earlier run with the same memory delivered (free); newest first, a video's walk stops at the first comment delivered before. For a scheduled task. |
| `stateStoreName` | `string` | `""` | The name of the memory onlyNew keeps (letters, digits, '.', '\_', '-'); empty = a name derived from the inputs. |
| `maxItems` | `integer` | `0` | The most charged rows (comments and replies) of the whole run; 0 = no cap beyond maxComments and your charge limit. |
| `proxyUrl` | `string` |  | Your own proxy (scheme://user:pass@host:port) for every call instead of ours. Leave empty: the Actor brings its own connections at no cost to you. |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing, and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

A video with comments turned off, a missing video, a premiere, a members-only video or a live stream is not an error: it gets one free summary row with its `status`, and the run goes on with the others. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Does it get every comment?**

Every comment YouTube lists logged out, newest first, to the list's own end: 360 of the 362 our test video counts (the header counts held or removed comments it does not show). The video's summary row gives the exact count, what was delivered and the coverage. The Top order is YouTube's ranking and ends earlier on famous videos (1,199 of 2.46 million on our test); use Newest for all.

**Is the comment date exact?**

No exact time is shown logged out: YouTube writes "3 weeks ago" on every client and page. Each row has that text (`published_text`), an approximate time computed from it and the moment of the read (`published_at_approx`), and the range the text allows (`published_after` to `published_before`); `published_at_is_estimated` is true. A few sellers get an exact time from YouTube's Data API with their own key; this Actor needs no key.

**Are likes exact?**

Below 1,000, yes. Past it YouTube itself shows only a rounded text ("1.6K") to every viewer; the row gives 1,600 with `likes_exact` false and YouTube's text in `likes_text`. Comment dislikes are not shown by YouTube.

**What are the free rows?**

One summary row per video or post (`type` video or post): `ok` with the verdict and the counts, or why it has no comments — `comments_off`, `not_found` (two reads agree), `upcoming`, `members_only`, `live` (a live stream's chat is not comments), `no_comments`, `failed`, `not_read`. One row per channel, playlist or search (`type` list) and per value that names nothing (`type` input, `invalid`). Only comments and replies are charged. The summary rows share the dataset with the comments, so the default view and an export show them too: for comments alone keep the rows whose `type` is `comment` or `reply` (a filter in the spreadsheet, or one line in your code).

**Does it need a YouTube account, cookies or an API key?**

No. Everything is read from YouTube's own public data, logged out, with a plain HTTP client; nothing of yours is sent to YouTube. No YouTube Data API key and its quota.

**I move from another YouTube comments Actor: which column is which?**

The keys most YouTube comments Actors use map one to one: `cid` → `comment_id`, `replyToCid` → `parent_id`, `type` → `type`, `publishedTimeText` → `published_text`, `comment` → `text`, `author` → `author_handle`, `authorIsChannelOwner` → `author_is_creator`, `replyCount` → `reply_count`, `voteCount` → `likes`, `hasCreatorHeart` → `is_hearted`, `videoId` → `video_id`, `pageUrl` → `video_url`, `commentsCount` → `video_comment_count`, `title` → `video_title`. The input takes the same `startUrls` (with `{"url": …}` objects), `maxComments`, `sortCommentsBy` and `oldestCommentDate`.

**Can it read live chat or translate comments?**

Not in this version: a live stream's chat is not its comments (the video's row says `live`), and translation and sentiment are not built. The text comes as the commenter wrote it.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/youtube-comments-scraper').call({"startUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"], "maxComments": 300});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/youtube-comments-scraper').call(run_input={'startUrls': ['https://www.youtube.com/watch?v=dQw4w9WgXcQ'], 'maxComments': 300})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/youtube-comments-scraper --input '{"startUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"], "maxComments": 300}'
```

#### REST

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

### Support

apify@hyperbach.com

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

## `startUrls` (type: `array`):

Videos and Shorts (watch, youtu.be, /shorts/, /live/, /embed/ links or an 11-character id), community posts (/post/<id>), channels (URL, @handle or UC… id: their latest videos, see maxVideos and channelTab; a URL that ends in /shorts, /streams or /posts reads that tab), playlists (URL or PL… id) or search URLs. The leader's {"url": …} objects are read too. A value that names nothing is a free row that says why.

## `searchQueries` (type: `array`):

Keywords: the top maxVideos videos of each YouTube search are read, from the country in searchCountry (YouTube shows each country a different search).

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

Comments read per video or post, in the order of sortCommentsBy; 0 = every comment YouTube lists. Replies are not counted in it (see maxRepliesPerComment). The summary row of each video says whether its list ended (COMPLETE), YouTube's list ended early (TRUNCATED) or this cap came first (CAPPED).

## `sortCommentsBy` (type: `string`):

YouTube's own two orders. Newest first walks every comment; Top comments is YouTube's ranking, which ends at about 1,000–1,200 comments on a famous video.

## `oldestCommentDate` (type: `string`):

Only comments published on or after this date: YYYY-MM-DD, or a number of days ("1 day" = today's comments, "7 days" = the last week), weeks, months or years. It reads newest first. YouTube shows a comment's age in words ("3 weeks ago"), so the cut-off works to that precision.

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

Read the replies of every comment delivered, each its own row with the parent comment's id (charged as a reply).

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

Replies read per comment when Replies is on; 0 = every reply.

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

Keep a comment only when its text contains one of these words or phrases as a whole word (any case: "art" does not keep "start"). A comment left out is free and does not count toward maxComments; a kept comment comes with all its replies.

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

Leave out a comment whose text contains one of these words or phrases as a whole word (any case). Free.

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

Keep a comment only with at least this many likes (YouTube rounds likes past 999: "1.6K" counts as 1,600). 0 = every comment.

## `maxVideos` (type: `integer`):

Videos taken from each channel (newest first), playlist (in its order) or search (top results).

## `channelTab` (type: `string`):

Which of a channel's lists maxVideos takes from.

## `maxPosts` (type: `integer`):

A channel's latest community posts whose comments are read too; 0 = none.

## `searchCountry` (type: `string`):

Where each keyword search is read from (YouTube shows each country a different search). A video's comments are the same everywhere.

## `onlyNew` (type: `boolean`):

Skip every comment an earlier run with the same memory delivered (free); newest first, a video's walk stops at the first comment delivered before. For a scheduled task.

## `stateStoreName` (type: `string`):

The name of the memory onlyNew keeps (letters, digits, '.', '\_', '-'); empty = a name derived from the inputs.

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

The most charged rows (comments and replies) of the whole run; 0 = no cap beyond maxComments and your charge limit.

## `proxyUrl` (type: `string`):

Your own proxy (scheme://user:pass@host:port) for every call instead of ours. Leave empty: the Actor brings its own connections at no cost to you.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.youtube.com/watch?v=krsuaUp__pM"
  ],
  "searchQueries": [],
  "maxComments": 20,
  "sortCommentsBy": "NEWEST_FIRST",
  "includeReplies": false,
  "maxRepliesPerComment": 0,
  "includeKeywords": [],
  "excludeKeywords": [],
  "minLikes": 0,
  "maxVideos": 10,
  "channelTab": "videos",
  "maxPosts": 0,
  "searchCountry": "US",
  "onlyNew": false,
  "stateStoreName": "",
  "maxItems": 0
}
```

# Actor output Schema

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

All scraped records in the default dataset. One row per comment and one per reply (and one free summary row per video, post, channel, playlist or search):

## `runSummary` (type: `string`):

How the run went: comments and replies charged, free rows by status, each video's walk (the exact count, delivered, pages, the end and the verdict), the source counters (requests, exits and why one was dropped, rotations, slow answers, mask drift, bytes), the proxy chains and the memory watch.

# 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 = {
    "startUrls": [
        "https://www.youtube.com/watch?v=krsuaUp__pM"
    ],
    "maxComments": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("hyperbach/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 = {
    "startUrls": ["https://www.youtube.com/watch?v=krsuaUp__pM"],
    "maxComments": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("hyperbach/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 '{
  "startUrls": [
    "https://www.youtube.com/watch?v=krsuaUp__pM"
  ],
  "maxComments": 20
}' |
apify call hyperbach/youtube-comments-scraper --silent --output-dataset

```

## MCP server setup

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