# YouTube Scraper — Videos, Channels, Playlists, Comments (`scrapyx/youtube-data-scraper`) Actor

Scrapes YouTube search results, video details, channels, playlists and comments — views, likes, descriptions, durations, subscriber counts and full comment threads. No login or API key. Does not download video or transcripts (YouTube gates those behind a browser-only token).

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

## Pricing

from $1.26 / 1,000 results

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?

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 Scraper — Videos, Channels, Playlists, Comments

Pull structured YouTube data without an API key, an account, or a quota.

Five things in one actor, chosen with a **mode** setting:

| Mode | You give it | You get back |
|---|---|---|
| **Search results** | search terms | matching videos, ranked as YouTube ranks them |
| **Video details** | video IDs or URLs | one rich row each — views, likes, description, channel |
| **Channel** | channel IDs, `@handles` or URLs | the channel profile + its videos, shorts or live streams |
| **Playlist** | playlist IDs or URLs | the playlist + every video in it |
| **Video comments** | video IDs or URLs | comment threads with authors, likes and reply counts |

### Why use this actor

- **No API key and no quota.** The official YouTube Data API costs you 10,000
  units a day and makes you register a project; this needs neither.
- **Exact numbers, not rounded ones.** View counts come back as
  `1810223610`, not `"1.8B"` — and like counts are read from the precise
  source rather than the abbreviated button label.
- **Handles, IDs or URLs — all fine.** `@fireship`, `UCsBjURrPoezykLs9EqgamOA`,
  a `/c/` legacy URL, a `youtu.be/` short link, a `/shorts/` link. They all resolve.
- **Real pagination.** Follows YouTube's own "load more" chain, with a
  duplicate-detecting stop condition so it ends when the data ends.
- **Honest about gaps.** Every run writes a summary row saying how many pages it
  walked and flagging anything YouTube itself doesn't provide.

### What it does NOT do — please read before running

**No video downloads, no stream URLs, and no transcripts or subtitles.**

That is not an omission we can fix by trying harder. YouTube gates the player
data behind a token that can only be produced by running Google's own
anti-bot JavaScript in a real browser. Metadata is open; playable media and
caption *content* are not. Any tool offering HTTP-only YouTube downloads is
either running a browser somewhere or about to break.

The dangerous part, and the reason this is stated so plainly: a caption request
returns **HTTP 200 with an empty body**, which looks like "this video has no
subtitles" rather than "you were blocked". A tool that doesn't know this will
quietly hand you empty transcripts. This actor omits the feature instead.

### How it works

1. You choose a mode and give it a list of queries, URLs or IDs.
2. The actor reads YouTube's own internal data for each one, paging through
   results the same way the website does as you scroll.
3. Rows land in your dataset, ready to download as JSON, CSV or Excel.

### Input

```json
{
  "mode": "channel",
  "queries": ["@fireship"],
  "maxItems": 50,
  "channelTab": "videos",
  "language": "en",
  "country": "US"
}
```

| Field | Type | Description |
|---|---|---|
| `mode` | string | `search`, `video`, `channel`, `playlist` or `comments`. |
| `queries` | array | One entry per thing to scrape — meaning depends on the mode. |
| `maxItems` | integer | Videos (or comments) per query. `0` = as many as YouTube will page. Default `50`. |
| `channelTab` | string | `videos`, `shorts` or `streams`. Channel mode only. |
| `searchFilter` | string | `video`, `channel`, `playlist`, `movie`, `today`, `this_week`, `this_month`. Search mode only. |
| `commentSort` | string | `top` or `newest`. Comments mode only. |
| `includeChannelVideos` | boolean | Off = fetch only the channel profile row, in one request. Default `true`. |
| `language` / `country` | string | `hl` / `gl` codes, e.g. `en` / `US`. Affects localised text and relevance. |
| `maxConcurrency` | integer | Requests in flight. Default `4` — keep it modest. |
| `minRequestInterval` | number | Seconds between request starts. Default `0.25`. |

### Output

Rows share one envelope and are told apart by `recordType`:
`VIDEO`, `CHANNEL`, `PLAYLIST`, `COMMENT`, `QUERY_SUMMARY`, `ERROR`.

#### `VIDEO` — video details mode

```json
{
  "_input": "dQw4w9WgXcQ",
  "recordType": "VIDEO",
  "queryMode": "video",
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
  "channelName": "Rick Astley",
  "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
  "channelSubscriberText": "4.54M subscribers",
  "viewCount": 1810223610,
  "viewCountText": "1,810,223,610 views",
  "likeCount": 19365088,
  "publishedAt": "2009-10-24T00:00:00Z",
  "publishedText": "Oct 24, 2009",
  "description": "The official video for “Never Gonna Give You Up” by Rick Astley ...",
  "thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"
}
```

#### `CHANNEL` — channel mode

```json
{
  "recordType": "CHANNEL",
  "channelId": "UCsBjURrPoezykLs9EqgamOA",
  "title": "Fireship",
  "subscriberCount": 4250000,
  "subscriberText": "4.25M subscribers",
  "videoCount": 835,
  "vanityUrl": "http://www.youtube.com/@Fireship",
  "rssUrl": "https://www.youtube.com/feeds/videos.xml?channel_id=UCsBjURrPoezykLs9EqgamOA",
  "avatarUrl": "https://yt3.googleusercontent.com/...",
  "bannerUrl": "https://yt3.googleusercontent.com/..."
}
```

Followed by one `VIDEO` row per upload:

```json
{
  "recordType": "VIDEO", "queryMode": "channel", "resultRank": 1,
  "title": "The most expensive software bug in history...",
  "viewCount": 1000000, "durationSeconds": 319,
  "publishedText": "3 days ago", "publishedAtApprox": "2026-08-28T16:41:00Z"
}
```

#### `COMMENT` — comments mode

```json
{
  "recordType": "COMMENT", "resultRank": 1,
  "videoId": "dQw4w9WgXcQ",
  "commentId": "Ugzge340dBgB75hWBm54AaABAg",
  "text": "can confirm: he never gave us up",
  "authorName": "@YouTube",
  "authorChannelId": "UCBR8-60-B28hp2BmDPdntcQ",
  "authorIsVerified": true,
  "likeCount": 309000,
  "replyCount": 963,
  "publishedText": "1 year ago",
  "isReply": false
}
```

| Field | Type | Description |
|---|---|---|
| `videoId` / `url` / `title` | string | The video. |
| `channelName` / `channelId` | string | Uploader. |
| `viewCount` / `likeCount` | integer | Exact where YouTube exposes it, else `null` — never a guessed `0`. |
| `durationSeconds` / `durationText` | int / string | Length. Null on Shorts. |
| `publishedAt` | string | Exact UTC date. Video-details mode only. |
| `publishedAtApprox` | string | Derived from "3 days ago" on list surfaces. **Approximate** — the name says so. |
| `description` | string | Full description in video mode; a snippet in search. |
| `subscriberCount` / `videoCount` | integer | Channel rows. |
| `commentId` / `text` / `authorName` | string | Comment rows. |
| `likeCount` / `replyCount` | integer | Comment engagement. |
| `notes` | array | Honesty flags on the summary row. |

### Known limits

- **No downloads, streams, or transcripts.** See above — it's a hard gate, not a setting.
- **Shorts have no publish date and no duration.** YouTube's shorts shelf simply
  omits them. Those columns come back null and the summary row says why.
- **`publishedAtApprox` is approximate.** List surfaces give "2 years ago"; only
  the video-details mode returns an exact date. Both are separate columns so you
  always know which one you have.
- **Comment replies aren't expanded.** Top-level comments are returned; opening
  every "N replies" thread is a separate request per comment.
- **Age-restricted and private videos return an error row**, not a partial one.
- **Rate limiting is real.** YouTube answers a hot IP with an interstitial rather
  than a clean error. The defaults are gentle; raise `minRequestInterval` before
  raising `maxConcurrency`.

# Actor input Schema

## `mode` (type: `string`):

Decides what your `queries` mean and what rows you get back.

• **Search** — search terms → video results
• **Video details** — video IDs/URLs → one rich row each (views, likes, description, channel)
• **Channel** — channel IDs/@handles/URLs → a channel row plus its videos, shorts or live streams
• **Playlist** — playlist IDs/URLs → a playlist row plus its videos
• **Comments** — video IDs/URLs → comment rows

## `queries` (type: `array`):

One entry per thing to scrape. What goes here depends on the mode:

• **Search** — plain search terms, e.g. `python tutorial`
• **Video details / Comments** — an 11-character video ID or any video URL (`watch?v=`, `youtu.be/`, `/shorts/`, `/embed/`, `/live/`)
• **Channel** — a `UC…` channel ID, an `@handle`, or a channel URL (legacy `/c/` and `/user/` URLs work too)
• **Playlist** — a playlist ID (`PL…`, `UU…`, …) or any URL with a `list=` parameter

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

Videos per search/channel/playlist, or comments per video. Set to 0 for as many as YouTube will page through. Results are paged in chunks (search ~12–14, channel 30, comments 20), so the actual count lands on a chunk boundary at or above your number.

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

Which tab to read in Channel mode. Note that **Shorts carry no publish date and no duration** — YouTube's shorts shelf simply doesn't include them, so those fields come back empty (and the run says so in its summary row).

## `searchFilter` (type: `string`):

Optional filter for Search mode, matching YouTube's own filter chips. Leave blank for unfiltered results.

## `commentSort` (type: `string`):

Order for Comments mode: YouTube's own "Top comments" ranking, or newest first.

## `includeChannelVideos` (type: `boolean`):

In Channel mode, also page through the selected tab. Turn this off to fetch only the channel profile row (title, description, subscriber count, avatar, banner) — one request instead of many.

## `language` (type: `string`):

Interface language for titles and metadata YouTube localises, as an ISO code — `en`, `es`, `id`, `de`.

## `country` (type: `string`):

Country context as a two-letter code — `US`, `GB`, `ID`. Affects which results YouTube considers relevant.

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

Upper bound on requests in flight at once, across all queries. Keep this modest: YouTube answers a hot IP with a rate-limit interstitial rather than a clean error.

## `minRequestInterval` (type: `number`):

The honest speed control — it paces request starts without tying up a worker. Raise it (0.5–2) if the log shows rate-limit warnings; once this cap binds, extra concurrency buys nothing.

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

Off by default — these endpoints answer a datacenter IP fine. Enable it if you scrape heavily and start seeing rate-limit warnings, or if you need results from a specific country.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "python tutorial"
  ],
  "maxItems": 50,
  "channelTab": "videos",
  "searchFilter": "",
  "commentSort": "top",
  "includeChannelVideos": true,
  "language": "en",
  "country": "US",
  "maxConcurrency": 4,
  "minRequestInterval": 0.25,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "queries": [
        "python tutorial"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/youtube-data-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 = { "queries": ["python tutorial"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/youtube-data-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 '{
  "queries": [
    "python tutorial"
  ]
}' |
apify call scrapyx/youtube-data-scraper --silent --output-dataset

```

## MCP server setup

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