# YouTube Comments Scraper 💬 Bulk, No API Key (`cleanfeed/youtube-comments-downloader`) Actor

Scrape every comment from any YouTube video. Pass video URLs or IDs; get comment text, author handle, like and reply counts, publish date and reply flag, plus the video title and channel, as JSON or CSV. No YouTube API key, no quota, no cap on comments.

- **URL**: https://apify.com/cleanfeed/youtube-comments-downloader.md
- **Developed by:** [Yaniv van der Stigchel](https://apify.com/cleanfeed) (community)
- **Categories:** AI, Social media, Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 comment returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## YouTube Comments Scraper — real numbers, no API key

Every comment from any YouTube video. Text, author, like and reply counts, and
relative post time. No API key, no daily quota, no 100-comment cap.

### What it does

- Scrape YouTube comments to JSON or CSV
- Export all comments from a YouTube video
- Get YouTube comment sentiment data in bulk
- Download YouTube comments without an API key
- Analyse audience feedback across many videos

Pass many videos in one run. Every comment is tagged with its source video.

### Counts are numbers, not "309K"

YouTube returns engagement as abbreviated strings — `309K`, `1.2M`, `963`. Most
scrapers hand those straight through, so you have to parse them yourself before
you can sort or sum anything.

This one returns **integers**: `309000`, `1200000`, `963`. Sortable and
summable the moment you get them.

### Honest failure reporting

| `reason` | Meaning | Charged |
|---|---|---|
| — (`ok: true`) | Comments returned | Yes |
| `comments-disabled` | Video fine, comments off or none posted | No |
| `video-unavailable` | Deleted, private, or region-locked | No |
| `unparseable-input` | Not a recognisable video reference | No |

Feeding a large list? Dead videos and comment-less ones cost you nothing.

### Why it keeps working

The extraction route uses the client version the page itself declares rather
than a pinned one, and retries on a fresh IP when a residential exit node gets
blocked — the two things that most often break YouTube scrapers silently.

### Input

| Field | Required | Description |
|---|---|---|
| `videos` | yes | Watch URLs, youtu.be links, shorts, or bare IDs |
| `maxCommentsPerVideo` | no | Default 100. Your cost ceiling. |
| `maxConcurrency` | no | 1–20, default 5 |
| `proxy` | no | Defaults to residential, which YouTube requires |

### Output

Every row has the same fields whether it succeeded or failed, so you can
select columns without branching. Failed rows are never charged.

| Field | Type | Description |
|---|---|---|
| `success` | boolean | True when this row carries data. Failed rows are never charged. |
| `videoId` | string | YouTube's 11-character video identifier. |
| `videoUrl` | string | Canonical watch URL for the video. |
| `videoTitle` | string | Title of the video the comment is on. |
| `channel` | string | Display name of the channel that published the video. |
| `commentId` | string | YouTube's identifier for this comment. |
| `text` | string | The comment body as posted. |
| `authorHandle` | string | The commenter's @handle. |
| `authorChannelId` | string | The commenter's channel identifier. |
| `likeCount` | integer | Likes on this comment. |
| `replyCount` | integer | Replies to this comment. Always 0 for a reply. |
| `publishedText` | string | How long ago the comment was posted. YouTube exposes no absolute date here. |
| `isReply` | boolean | True when this is a reply rather than a top-level comment. |
| `errorCode` | string | Machine-readable failure reason. Null on success. |
| `errorMessage` | string | Human-readable explanation of the failure. Null on success. |

#### Example — success

```json
{
  "success": true,
  "videoId": "jNQXAC9IVRw",
  "videoUrl": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "videoTitle": "Me at the zoo",
  "channel": "jawed",
  "commentId": "UgxKREWxIgDrw8w2e_x4AaABAg",
  "text": "This is where it all started. Wild to think how far the platform has come.",
  "authorHandle": "@some_viewer",
  "authorChannelId": "UCq3B8s7Qk2AbCdEfGhIjKl",
  "likeCount": 4127,
  "replyCount": 38,
  "publishedText": "1 year ago",
  "isReply": false,
  "errorCode": null,
  "errorMessage": null
}
```

#### Example — failure

A failure carries the same fields, so nothing downstream has to branch.

```json
{
  "success": false,
  "videoId": "s1CFmzZzO4c",
  "videoUrl": "https://www.youtube.com/watch?v=s1CFmzZzO4c",
  "videoTitle": "Board meeting recording",
  "channel": "Example Corp",
  "commentId": null,
  "text": null,
  "authorHandle": null,
  "authorChannelId": null,
  "likeCount": null,
  "replyCount": null,
  "publishedText": null,
  "isReply": null,
  "errorCode": "comments-disabled",
  "errorMessage": "The uploader has turned comments off for this video."
}
```

#### Error codes

- `comments-disabled`
- `video-unavailable`
- `blocked`
- `unparseable-input`
- `error`

### A note on dates

YouTube exposes only relative time for comments ("1 year ago"), not a timestamp.
The field is named `publishedText` rather than `publishedAt` so it is clear it is
text, not a parseable date. No scraper can give you an exact date here.

### Use it for

- **YouTube comments export** — every comment on a video, as JSON or CSV
- **YouTube video comments** in bulk, across many videos per run
- Audience research, sentiment and feedback analysis
- Community management and moderation review
- Creator research and training datasets

### Related actors

| If you need | Use |
|---|---|
| The spoken text of a video | [YouTube Transcript Scraper](https://apify.com/cleanfeed/youtube-transcript-downloader) |
| Every transcript on a channel | [YouTube Channel Transcript Scraper](https://apify.com/cleanfeed/youtube-channel-transcript-downloader) |
| Every transcript in a playlist | [YouTube Playlist Transcript Scraper](https://apify.com/cleanfeed/youtube-playlist-transcript-downloader) |
| A channel's Shorts | [YouTube Shorts Transcript Scraper](https://apify.com/cleanfeed/youtube-shorts-transcript-downloader) |
| Transcripts from a keyword search | [YouTube Search to Transcripts](https://apify.com/cleanfeed/youtube-search-transcript-downloader) |

### Use it from an AI agent (MCP)

This Actor is callable as a tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so Claude, ChatGPT, Cursor and VS Code can run it directly.

Add the server to your MCP client:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com",
      "headers": {
        "Authorization": "Bearer <YOUR_APIFY_TOKEN>"
      }
    }
  }
}
```

Then ask for what you want in plain language — for example *“get the comments on this YouTube video”* — and the agent calls `cleanfeed/youtube-comments-downloader` with the right input. Every output field is described in the dataset schema, so the agent knows what it is getting back before it runs anything.

### Call it from code

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("cleanfeed/youtube-comments-downloader").call(run_input={
    "videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["success"]:
        print(item)
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const run = await client.actor('cleanfeed/youtube-comments-downloader').call({
    videos: ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.success));
```

#### cURL

```bash
curl -X POST "https://api.apify.com/v2/acts/cleanfeed~youtube-comments-downloader/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{"videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"]}'
```

### Limitations

- **A residential proxy is required.** YouTube serves `playabilityStatus: ERROR` to datacenter IP ranges while the identical request succeeds from a home connection — measured 12/12 success on residential against 0/12 on a cloud host with no proxy, and 7/12 through a datacenter proxy. The input defaults to residential; changing it will break most runs. Full method and per-environment figures are published in the [reliability benchmark](https://publicapidata.com/benchmarks/youtube-transcript-reliability/).
- **Comments can be disabled.** Videos with comments turned off return `errorCode: comments-disabled` and are never charged.
- **Publish dates are relative.** YouTube exposes `"1 year ago"` rather than a timestamp on this surface, so `publishedText` is a string, not a date.
- Reply threads are included and flagged with `isReply`, but very deep threads may be truncated by `maxCommentsPerVideo`.

### FAQ

#### Do I need a YouTube API key?

No. No key, no OAuth, and none of the Data API's daily quota.

#### Are replies included?

Yes. Replies come back alongside top-level comments and are marked with `isReply: true`.

#### Why is the publish date a string like "1 year ago"?

That is what YouTube exposes on this surface. There is no absolute timestamp available, so `publishedText` reports it verbatim rather than inventing a date.

#### What if comments are turned off?

The row returns `errorCode: comments-disabled` and is never charged.

#### Can I get the transcript instead?

Yes — [YouTube Transcript Scraper](https://apify.com/cleanfeed/youtube-transcript-downloader) returns the spoken text of the video.

### Notes

Only publicly visible comments are collected. No login, no private data.

# Actor input Schema

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

YouTube videos whose comments to fetch: watch URLs, youtu.be links or bare 11-character video IDs. Returns comments, not transcripts. For the spoken text of a video use cleanfeed/youtube-transcript-downloader.

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

Top comments first. You are charged per comment returned, so this is your cost ceiling.

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

Videos processed in parallel.

## `proxy` (type: `object`):

YouTube blocks datacenter IPs. Residential proxy is required for reliable results.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "maxCommentsPerVideo": 100,
  "maxConcurrency": 5,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `comments` (type: `string`):

One row per comment: text, author handle and channel id, like and reply counts as numbers, and relative published time.

## `summary` (type: `string`):

Videos processed, comments delivered, and counts per failure type.

# 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=dQw4w9WgXcQ"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("cleanfeed/youtube-comments-downloader").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=dQw4w9WgXcQ"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("cleanfeed/youtube-comments-downloader").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=dQw4w9WgXcQ"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call cleanfeed/youtube-comments-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cleanfeed/youtube-comments-downloader"
        }
    }
}

```

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/0evIK9PpM4nrJOeeE/builds/bdY25NBSA9H9j9p4v/openapi.json
