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

Scrape the comments of one or MANY YouTube videos in a single run. Returns text, author, likes, thread structure (parent/depth) and the source video itself. Top or newest-first order, optional replies. No login, no API quota.

- **URL**: https://apify.com/outspoken\_strategy/youtube-comments-scraper.md
- **Developed by:** [code craker](https://apify.com/outspoken_strategy) (community)
- **Categories:** Social media, Videos, E-commerce
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

Apify actor that scrapes the comments of one or **many** YouTube videos in a single
run and exports them as structured data — no login, no cookies, no YouTube API quota.

It reads the entry token from the video's watch page and pages through YouTube's own
InnerTube `/youtubei/v1/next` API (the same requests the site itself makes). Requests
go over plain HTTP first (fast and cheap); if YouTube blocks that, the actor
automatically escalates to a real Chrome browser for the rest of the run — API
requests then run inside the page as same-origin fetches.

### Features

- **Batched**: pass many video URLs and they are all scraped in ONE actor run
  (`maxComments` applies per video). Results are pushed after each video, so an
  abort or timeout keeps everything collected so far.
- Any video reference works: `youtube.com/watch?v=...`, `youtu.be/...` short links,
  `/shorts/...` links, or a bare 11-character video id.
- `top` (YouTube's ranked order) or `newest` first.
- Replies included with full thread structure (`parentCommentId` + `depth`), or
  top-level comments only (`includeReplies: false`). Top-level comments are
  collected first; replies fill the remaining budget.
- The source video itself is emitted too (flagged `is_source_post: true`) with
  title, channel, views, exact publish date and total comment count; disable with
  `includeSourcePost: false`. Videos with disabled comments are flagged
  (`commentsDisabled: true`) instead of failing.
- Automatic block recovery: fresh proxy IP on each retry, plain HTTP → real Chrome
  escalation, EU consent interstitial bypassed via cookies.
- When a run ends with 0 results, the last page fetched is saved as `DEBUG_HTML`
  in the run's key-value store.

### Input

```json
{
    "urls": [
        "https://www.youtube.com/watch?v=XXaUd0fGpOs",
        "https://youtu.be/SSE4M0gcmvE"
    ],
    "maxComments": 100,
    "commentsSort": "top",
    "includeReplies": true,
    "includeSourcePost": true,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US"
    }
}
```

`startUrls` (`[{ "url": ... }]`) is accepted as an alternative to `urls` and merged
with it. `resultsLimit` is accepted as an alias for `maxComments`.

### Output

One dataset item per comment. Every item carries `postId` / `postUrl` / `inputUrl`,
so comments from a multi-video run can always be attributed back to their source
video.

```json
{
    "id": "Ugw4obgfNOpuN6L96qF4AaABAg",
    "url": "https://www.youtube.com/watch?v=XXaUd0fGpOs&lc=Ugw4obgfNOpuN6L96qF4AaABAg",
    "text": "Full text of the comment...",
    "author": "@some_user",
    "authorChannelId": "UCQLsJdT08EKD_7boRxYusGw",
    "authorProfileUrl": "https://www.youtube.com/channel/UCQLsJdT08EKD_7boRxYusGw",
    "authorAvatar": "https://yt3.ggpht.com/...",
    "isCreator": false,
    "isVerified": false,
    "likeCount": 52,
    "repliesCount": 3,
    "edited": false,
    "parentCommentId": null,
    "depth": 0,
    "publishedTimeText": "14 hours ago",
    "created_at": "2026-07-08T18:53:19.009Z",
    "postId": "XXaUd0fGpOs",
    "postUrl": "https://www.youtube.com/watch?v=XXaUd0fGpOs",
    "inputUrl": "https://www.youtube.com/watch?v=XXaUd0fGpOs",
    "commentsSort": "top",
    "is_source_post": false
}
```

- `parentCommentId` is `null` for a top-level comment; otherwise the id of the
  comment it replies to (YouTube threads are exactly two levels deep). A reply's
  own id is `parentId.replyId`.
- `created_at` is APPROXIMATE: YouTube only exposes relative publish times for
  comments ("2 weeks ago"); the raw value is kept in `publishedTimeText`.
- `isCreator` marks the video's own channel commenting (YouTube's Creator badge) —
  the equivalent of Reddit's `isSubmitter`/OP.
- The source video item has `is_source_post: true` with `title`, `description`,
  `channelName`, `viewCount`, `numComments` and an EXACT `created_at` (watch pages
  expose the real publish date).

### Integration (scraping-tool)

Batched, like the other comment scrapers in `scrapeCommentsForPostsBatched`.
Add a branch to `scrapingService.scrapeCommentsBatch`:

```js
} else if (actor === 'outspoken_strategy/youtube-comments-scraper') {
    delete input.startUrls;
    input.urls = urls;
    input.maxComments = cappedComments(input.maxComments ?? resultsLimit);
    input.includeSourcePost = input.includeSourcePost ?? false;
    input.includeReplies = input.includeReplies ?? false; // match FB: top-level only
    input.proxyConfiguration = input.proxyConfiguration ?? { useApifyProxy: true, apifyProxyGroups: ['RESIDENTIAL'], apifyProxyCountry: 'US' };
}
```

Attribution in `_matchCommentToPost` (platform `youtube`): match on `item.postUrl`,
`item.inputUrl` or `item.postId` — a good `_postMatchKey` is the video id extracted
with `/[?&]v=([A-Za-z0-9_-]{11})/` (plus the `youtu.be`/`shorts` variants).

Normalization hints: `likesCount` ← `likeCount`, `repliesCount` is already named,
`createdTime` ← `created_at` (approximate), `parentCommentId` is already named, and
author fields are flat (`author`, `authorChannelId`, `authorProfileUrl`).

### Local development

```bash
npm install
echo '{ "urls": ["https://www.youtube.com/watch?v=XXaUd0fGpOs"], "maxComments": 25, "proxyConfiguration": { "useApifyProxy": false } }' > storage/key_value_stores/default/INPUT.json
npm start
```

Deploy with `apify push`.

# Actor input Schema

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

URLs of the YouTube videos whose comments you want to scrape — one or MANY videos per run. Any form works: youtube.com/watch?v=..., youtu.be/... short links, /shorts/... links, or a bare 11-character video id. De-duplicated by video.

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

Alternative way to provide the video URLs as a request list of { "url": ... } objects. Merged with "urls" and de-duplicated by video.

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

Maximum number of comments to fetch PER video (replies count towards this limit). The source video itself is also emitted (flagged is\_source\_post) and does not count.

## `commentsSort` (type: `string`):

Which comments YouTube puts first — matters when a video has more comments than maxComments. "top" (YouTube's ranked order) or "newest" (newest first).

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

Also fetch the replies under each comment (with parentCommentId/depth set). Top-level comments are collected first; replies fill the remaining maxComments budget. Uncheck to keep only top-level comments.

## `includeSourcePost` (type: `boolean`):

Also emit one dataset item for the video itself (flagged is\_source\_post: true) with its title, channel, views, publish date and total comment count. Uncheck to get comments only.

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

Proxy to route traffic through. Residential proxies are recommended — YouTube captchas or blocks flagged datacenter IPs.

## `headless` (type: `boolean`):

The actor fetches over plain HTTP and only falls back to a Chrome browser when YouTube blocks it. Uncheck to run that fallback browser headed (useful only for local debugging).

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=XXaUd0fGpOs"
  ],
  "startUrls": [],
  "maxComments": 100,
  "commentsSort": "top",
  "includeReplies": true,
  "includeSourcePost": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "headless": true
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.youtube.com/watch?v=XXaUd0fGpOs"
    ],
    "startUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("outspoken_strategy/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 = {
    "urls": ["https://www.youtube.com/watch?v=XXaUd0fGpOs"],
    "startUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("outspoken_strategy/youtube-comments-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.youtube.com/watch?v=XXaUd0fGpOs"
  ],
  "startUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call outspoken_strategy/youtube-comments-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=outspoken_strategy/youtube-comments-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/qxZpdBUfuhhZHbQU1/builds/SO2hZwxRBtdgKHCNI/openapi.json
