# YouTube Channel Videos Scraper (`devilscrapes/youtube-channel-scraper`) Actor

Export every uploaded video from any public YouTube channel into clean, structured dataset rows — video ID, title, view count, published time, duration, thumbnail. Devil Scrapes walks the channel's own paginated video grid so your pipeline doesn't break when YouTube reshuffles its layout.

- **URL**: https://apify.com/devilscrapes/youtube-channel-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?

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

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

## YouTube Channel Videos Scraper

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

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

Export every uploaded video from any public YouTube channel into clean, structured dataset rows — video ID, title, view count, published time, duration, thumbnail. Devil Scrapes walks the channel's own paginated video grid so your pipeline doesn't break when YouTube reshuffles its layout.

</div>

***

### 🎯 What this scrapes

A YouTube channel's <code>/videos</code> tab ships its first ~30 uploads inline in a <code>ytInitialData</code> blob, then hides the rest behind a continuation token buried in the page — the same token shape that moves every few WEB-client releases. We find that token wherever YouTube nests it this month with a generic recursive search, page through <code>/youtubei/v1/browse</code> for the rest, and turn every upload into one clean dataset row with title, views, duration, publish time, and the highest-resolution thumbnail.

### 🔥 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

- **Competitor/content research** — pull a competitor channel's full upload history to study cadence, titling, and view performance over time.
- **Creator analytics** — build per-channel dashboards of view counts and publish cadence without babysitting the YouTube UI.
- **Video-inventory pipelines** — feed a downstream catalog, recommender, or archive with a channel's canonical video list, IDs, and thumbnails.
- **Trend tracking** — snapshot a set of channels on a schedule to watch view-count growth and publishing frequency shift over time.

### ⚙️ 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 |
|---|---|:--:|---|---|
| `channels` | `array` | **yes** | \['@mkbhd'] | YouTube channel handles, bare handles, or full channel URLs (e.g. <code>@mkbhd</code>, <code>mkbhd</code>,… |
| `maxVideos` | `integer` | no | 30 | Stop paging once this many videos have been emitted for a single channel (1–5000). Set above ~30 to force a second… |
| `hl` | `string` | no | 'en' | YouTube UI language passthrough — affects relative-timestamp text like "3 days 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 channel-page payloads across datacenter and residential exits… |

#### Example input

```json
{
  "channels": [
    "@mkbhd",
    "@veritasium"
  ],
  "maxVideos": 45,
  "hl": "en",
  "gl": "US",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

### 📤 Output

Every row is one dataset item.

| Field | Type | Notes |
|---|---|---|
| `channel_handle` | `string` | Normalised @handle of the channel this video belongs to. |
| `video_id` | `string` | 11-character YouTube video ID. |
| `title` | `string` | Video title at scrape time. |
| `view_count` | `integer` | Parsed view count. 0 when absent or unparseable. |
| `published_time` | `string` | YouTube's relative publish string (e.g. '3 days ago'), verbatim — no absolute-timestamp resolution. |
| `duration_text` | `string` | Verbatim duration text (e.g. '12:34'). Null for live streams/premieres. |
| `duration_seconds` | `integer` | Duration parsed to seconds. Null alongside duration\_text for live/premiere videos. |
| `thumbnail_url` | `string` | Highest-resolution thumbnail URL available. |

#### Example output

```json
{
  "channel_handle": "@mkbhd",
  "video_id": "dQw4w9WgXcQ",
  "title": "This Changes Everything",
  "view_count": 4213098,
  "published_time": "3 days ago",
  "duration_text": "12:34",
  "duration_seconds": 754,
  "thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"
}
```

### 💰 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 |
| `video-scraped` | $0.005 | PPE event |

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

### 🚧 Limitations

Public channels only — the <code>/videos</code> tab, not Shorts or Live. No channel-metadata row (subscribers, banner, description) — video rows only. No absolute-timestamp resolution for publish dates. 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.

### ❓ FAQ

**Does this scrape Shorts or Live tabs?**

No — v1 covers the standard <code>/videos</code> tab only (regular uploads). Shorts and Live listings are out of scope for this release.

**Does it return channel-level metadata like subscriber count?**

No — this Actor returns video rows only, not a channel-metadata row (subscribers, banner, description).

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

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

**What happens if a channel has zero videos?**

The run still succeeds — a legitimately empty channel returns zero rows for that channel and the final status message reports it separately from a failed/degraded fetch.

**What happens if a channel handle is invalid or the channel is private?**

That single channel is skipped and counted as failed in the run summary; every other channel 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>

# Changelog

This Actor's version history is a separate document: https://apify.com/devilscrapes/youtube-channel-scraper/changelog.md

# Actor input Schema

## `channels` (type: `array`):

YouTube channel handles, bare handles, or full channel URLs (e.g. <code>@mkbhd</code>, <code>mkbhd</code>, <code>https://www.youtube.com/@mkbhd/videos</code>). Each is normalised to <code>@handle</code>.

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

Stop paging once this many videos have been emitted for a single channel (1–5000). Set above ~30 to force a second continuation page.

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

YouTube UI language passthrough — affects relative-timestamp text like "3 days 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 channel-page payloads across datacenter and residential exits — default proxy is enough, no residential group needed.

## Actor input object example

```json
{
  "channels": [
    "@mkbhd",
    "@veritasium"
  ],
  "maxVideos": 45,
  "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 = {
    "channels": [
        "@mkbhd",
        "@veritasium"
    ],
    "maxVideos": 45,
    "hl": "en",
    "gl": "US",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/youtube-channel-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 = {
    "channels": [
        "@mkbhd",
        "@veritasium",
    ],
    "maxVideos": 45,
    "hl": "en",
    "gl": "US",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/youtube-channel-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 '{
  "channels": [
    "@mkbhd",
    "@veritasium"
  ],
  "maxVideos": 45,
  "hl": "en",
  "gl": "US",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call devilscrapes/youtube-channel-scraper --silent --output-dataset

```

## MCP server setup

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