# YouTube Comments Scraper (`devilscrapes/youtube-comments-scraper`) Actor

Export every comment and reply from any public YouTube video into clean, structured dataset rows — author, text, like count, reply count, pinned/hearted flags, sorted by top or newest. Devil Scrapes tracks YouTube's shifting internal API so your pipeline doesn't break. Bulk export to JSON or CSV.

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

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## YouTube Comments Scraper

**💰 $0.70 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Export every comment and reply from any public YouTube video into clean, structured dataset rows — author, text, like count, reply count, pinned/hearted flags, sorted by top or newest. Devil Scrapes tracks YouTube's shifting internal API so your pipeline doesn't break.

</div>

***

### 🎯 What this scrapes

YouTube's own video page walks a two-step internal API to load comments: fetch the watch payload, pull a continuation token out of its <code>comments-section</code> engagement panel, then page through <code>/youtubei/v1/next</code> for the actual comment nodes. We do the same walk — a generic recursive search finds that token wherever YouTube's payload nests it this month, so a client-side layout shift doesn't quietly break the scrape — and turn every comment and reply into one clean dataset row, sorted by top or newest comments, with optional full reply threads.

### 🔥 What we handle for you

- 🛡️ **Browser fingerprint rotation** — `curl-cffi` impersonates real Chrome / Firefox / Safari TLS handshakes so the target sees a browser, not Python.
- 🌐 **Residential proxy rotation** via Apify Proxy — fresh session and exit IP on every block.
- 🔁 **Retries with exponential backoff** on `408 / 429 / 5xx` — up to 5 attempts per page, `Retry-After` honoured.
- 🧱 **Rate-limit-aware pacing** — when the target pushes back, we slow down instead of getting banned.
- 🧊 **Clean, typed dataset rows** — Pydantic-validated, ISO-8601 timestamps, stable IDs, JSON / CSV / Excel export straight from the Apify Console.
- 💰 **Pay-Per-Event pricing** — you only pay for results that hit your dataset. No data, no charge.

### 💡 Use cases

- **Social listening** — pull every comment on a video the moment it lands and feed sentiment/topic models in near real time.
- **Brand monitoring** — track what viewers say under a brand's own uploads or under mentions in a competitor's videos.
- **Creator analytics** — give creators and their teams a clean export of audience reaction, sorted top or newest, without babysitting the YouTube UI.
- **NLP pipelines** — bulk comment/reply text with stable IDs for training or evaluating classifiers, topic models, and toxicity filters.

### ⚙️ How to use it

1. Click **Try for free** at the top of the page.
2. Fill in the input form — most fields have sensible defaults.
3. Click **Start**. Output streams into the run's dataset.
4. Export from **Storage → Dataset** as JSON, CSV, or Excel — or fetch via the API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `videoUrls` | `array` | **yes** | \['https://www.youtube.com/watch?v=dQw4w9WgXcQ'] | YouTube video URLs, short URLs (<code>youtu.be/...</code>), Shorts URLs, or bare 11-character video IDs. Mix and match… |
| `maxComments` | `integer` | no | 100 | Stop paging once this many top-level comments (and replies, if included) have been emitted for a single video… |
| `sortBy` | `string` | no | 'top' | Comment order YouTube returns — matches the sort control on the video page itself. |
| `includeReplies` | `boolean` | no | False | Follow each top-level comment's reply thread too. Multiplies request volume and cost — leave off unless you need full… |
| `hl` | `string` | no | 'en' | YouTube UI language passthrough — affects relative-timestamp text like "1 year ago". Example: <code>en</code>. |
| `gl` | `string` | no | 'US' | YouTube UI country/region passthrough. Example: <code>US</code>. |
| `proxyConfiguration` | `object` | no | {'useApifyProxy': True} | Apify Proxy configuration. Recon confirmed byte-identical comment payloads across datacenter and residential exits —… |

#### Example input

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxComments": 45,
  "sortBy": "top",
  "includeReplies": false,
  "hl": "en",
  "gl": "US",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

### 📤 Output

Every row is one dataset item.

| Field | Type | Notes |
|---|---|---|
| `video_id` | `string` | 11-character YouTube video ID this comment belongs to. |
| `comment_id` | `string` | YouTube's opaque comment/reply ID. |
| `author` | `string` | Commenter's display name at scrape time. |
| `author_channel_id` | `string` | Commenter's channel ID. Null when YouTube omits it (e.g. deleted account). |
| `text` | `string` | Comment or reply body, plain text. |
| `published_time` | `string` | YouTube's relative publish string (e.g. '1 year ago'), verbatim — YouTube does not expose an absolute timestamp on this… |
| `like_count` | `integer` | Like count at scrape time. Defaults to 0 when absent. |
| `reply_count` | `integer` | Number of replies under this top-level comment. 0 for replies themselves. |
| `is_pinned` | `boolean` | True when the video owner pinned this comment. |
| `is_hearted` | `boolean` | True when the video owner hearted this comment. |
| `is_reply` | `boolean` | True for a reply row, false for a top-level comment. |
| `parent_comment_id` | `string` | The top-level comment\_id this row replies to. Null for top-level comments. |

#### Example output

```json
{
  "video_id": "dQw4w9WgXcQ",
  "comment_id": "Ugzge340dBgB75hWBm54AaABAg",
  "author": "SomeUser",
  "author_channel_id": "UCxxxxxxxxxxxxxxxxxxxxxx",
  "text": "can confirm: he never gave us up",
  "published_time": "1 year ago",
  "like_count": 1204,
  "reply_count": 3,
  "is_pinned": false,
  "is_hearted": false,
  "is_reply": false,
  "parent_comment_id": null
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.2 | One-off warm-up charge per run |
| `comment-scraped` | $0.0005 | PPE event |

Example: 1 000 results at the rates above ≈ **$0.70**. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.

### 🚧 Limitations

Public comments only — no OAuth, no moderator tools, no comment-author metadata beyond what YouTube's own comments UI exposes. YouTube's internal payload shape can shift between web-client releases; we walk it with a generic recursive search rather than a fixed path, but a structural change may still need a same-day patch. Enabling replies multiplies request volume per video — factor that into cost expectations for reply-heavy videos.

### ❓ FAQ

**Does this scrape live chat?**

No — this is the standard comments section under a video, not live-stream chat. For live chat you want a different endpoint entirely.

**Why are replies not included by default?**

Following every reply thread multiplies request volume (and cost) per video. Turn on <code>includeReplies</code> when you need full threads; leave it off for a fast top-level-only pull.

**Why is <code>published\_time</code> a string like '1 year ago' instead of a date?**

That's what YouTube's own comments endpoint returns — it does not expose an absolute timestamp here. We pass it through verbatim rather than guessing at a conversion.

**Why did one video return zero comments?**

Comments can be disabled by the uploader, or the video may have none yet. The run still succeeds — it reports how many videos had no comments in the final status message rather than failing the whole run.

**What happens if a video ID is invalid or private?**

That single video is skipped and counted as failed in the run summary; every other video in the same run still completes.

### 💬 Your feedback

Spotted a bug, hit a weird edge case, or need a new field? Open an
issue on the Actor's **Issues** tab on Apify Console — we ship
fixes weekly and we read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

# Actor input Schema

## `videoUrls` (type: `array`):

YouTube video URLs, short URLs (<code>youtu.be/...</code>), Shorts URLs, or bare 11-character video IDs. Mix and match — each is normalised to its video ID.

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

Stop paging once this many top-level comments (and replies, if included) have been emitted for a single video (1–10000). Set above ~20 to force a second continuation page.

## `sortBy` (type: `string`):

Comment order YouTube returns — matches the sort control on the video page itself.

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

Follow each top-level comment's reply thread too. Multiplies request volume and cost — leave off unless you need full threads.

## `hl` (type: `string`):

YouTube UI language passthrough — affects relative-timestamp text like "1 year ago". Example: <code>en</code>.

## `gl` (type: `string`):

YouTube UI country/region passthrough. Example: <code>US</code>.

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

Apify Proxy configuration. Recon confirmed byte-identical comment payloads across datacenter and residential exits — default proxy is enough, no residential group needed.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxComments": 45,
  "sortBy": "top",
  "includeReplies": false,
  "hl": "en",
  "gl": "US",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ],
    "maxComments": 45,
    "sortBy": "top",
    "includeReplies": false,
    "hl": "en",
    "gl": "US",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    ],
    "maxComments": 45,
    "sortBy": "top",
    "includeReplies": False,
    "hl": "en",
    "gl": "US",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/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 '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxComments": 45,
  "sortBy": "top",
  "includeReplies": false,
  "hl": "en",
  "gl": "US",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call devilscrapes/youtube-comments-scraper --silent --output-dataset

```

## MCP server setup

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