# YouTube Channel Scraper - Subscribers, Videos, Email (`angaba92/youtube-channel-scraper`) Actor

Scrape YouTube channels by handle, URL or ID: subscribers, total views, video count, country, join date, about links, emails in description and latest videos with views, dates and duration. No API key. JSON, CSV, Excel. $2 per 1,000 channels.

- **URL**: https://apify.com/angaba92/youtube-channel-scraper.md
- **Developed by:** [Andres Garcia-Baquero Leon](https://apify.com/angaba92) (community)
- **Categories:** Social media, Lead generation, 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

## YouTube Channel Scraper - Subscribers, Videos, Email

Turn a list of YouTube channels into a clean table: **available subscribers, total views, video count, country, join date, about-page links (Instagram, X, websites), emails published in the description** and an optional sample from the channel's **Videos tab**, with available titles, view counts, relative publication text and durations. Fields may be missing and displayed counts may be rounded. Paste handles, URLs or channel IDs and export to JSON, CSV, Excel or via API. No user-supplied YouTube API key or login. Source rate limits and blocking still apply.

**Price: $2 per 1,000 channels** ($0.002 per channel), latest videos included. Channels that do not exist are never charged.

### Why use it

| YouTube Data API | This Actor |
|---|---|
| Needs a Google Cloud project and API key | Paste channels and run |
| Official API quota | No user API quota; upstream throttling and operational limits still apply |
| No about-page links or emails | Links and emails from the about page |
| Video list needs extra calls | Latest videos with views in the same row |

### Output example

Illustrative field excerpt, not a current live snapshot or a guarantee of field coverage.

```json
{
  "channelName": "Marques Brownlee",
  "channelId": "UCBJycsmduvYEL83R_U4JriQ",
  "handle": "@mkbhd",
  "channelUrl": "https://www.youtube.com/@mkbhd",
  "subscribers": 21300000,
  "subscribersText": "21.3M subscribers",
  "totalViews": 5689453522,
  "videoCount": 1852,
  "country": "United States",
  "joinedDate": "2008-03-21",
  "links": [{"title": "Twitter", "url": "https://twitter.com/MKBHD"}],
  "emailsInDescription": ["business@MKBHD.com"],
  "hasBusinessEmail": true,
  "latestVideosCount": 1,
  "latestAvgViews": 3800000,
  "lastUploadText": "2d ago",
  "latestVideos": [
    {"videoId": "pOX1l1edBME", "title": "The Apple Watch Has a Problem", "views": 3800000,
     "publishedText": "2d ago", "duration": "15:06", "url": "https://www.youtube.com/watch?v=pOX1l1edBME"}
  ]
}
```

### Fields

| Field | Type | Description |
|---|---|---|
| channelName / handle / channelId | string | Name, @handle and stable UC... ID |
| subscribers | integer | Subscriber count (YouTube rounds it, e.g. 21.3M) |
| totalViews | integer or null | Lifetime views parsed from source display text; may be rounded |
| videoCount | integer or null | Source-displayed video count; may be rounded |
| subscribersText / totalViewsText / videoCountText | string or null | Original display strings, retained to expose source precision |
| country / joinedDate | string | From the about page |
| description / keywords | string | Channel description and tags |
| links | array | External links shown on the channel |
| emailsInDescription | array | Emails written in the public description |
| hasBusinessEmail | boolean | Channel has a hidden business email (sign-in required to reveal; not extracted) |
| latestVideos | array | videoId, url, title, views, publishedText, duration, isLive |
| latestAvgViews | integer or null | Average over returned videos with a view count; inherits source rounding |
| lastUploadText | string or null | Publication text of the first returned video, not an exact date or guaranteed latest upload |
| videosStatus | string | ok, partial, none\_public, skipped or unavailable (see coverage below) |
| videosError | string or null | Diagnostic for reduced video coverage |

### How to use

1. Add channels: `@mkbhd`, `https://www.youtube.com/@NASA` or `UCBJycsmduvYEL83R_U4JriQ`.
2. Choose how many latest videos you want (0 for channel info only).
3. Click **Start**.
4. Export the **Channels** table to CSV, JSON, Excel or pull it via API.

### Input

| Field | Description |
|---|---|
| channels | Handles, URLs or IDs. Only an omitted channels key runs a demo; explicit empty/null/invalid targets do not. |
| maxVideosPerChannel | 0-500 operational cap, default 30; 0 skips videos. Not a source-coverage guarantee. Included in the price. |
| maxConcurrency | Parallel channels, default 3. |

```json
{ "channels": ["@mkbhd", "UCHnyfMqiRRG1u-2MsSQLbXA"], "maxVideosPerChannel": 10 }
```

### Use cases

- **Influencer marketing**: shortlist creators by real recent views (not just subscribers) and get their links and public contact emails.
- **Sponsorship sales / lead generation**: build lists of channels in a niche with country, size and contact links.
- **Competitor tracking**: schedule daily runs to follow subscribers, total views and upload cadence.
- **Research and datasets**: channel-level data for media or creator-economy analysis.

### Integrations

Zapier, Make, n8n, Google Sheets, Slack, Airbyte, LangChain and webhooks via standard Apify integrations. Use Apify Schedules for daily snapshots.

### Pricing

Pay per event: **$0.002 per channel returned** (latest videos included) plus the tiny standard Apify actor-start fee. Not-found, invalid and failed channels are not charged. Result delivery uses Apify's atomic charged push and honors the result-event budget; the actor-start fee is separate.

### FAQ

**Do I need a YouTube API key?** No.

**Are counts exact?** Displayed subscribers, total views, video counts and per-video views may be rounded. For example, `1.8K videos` is converted to `1800`, not an independently verified exact count. Original count strings are retained where available.

**Do you extract the hidden business email?** No. That email requires a signed-in account and captcha. We return emails that channels publish in their description plus the `hasBusinessEmail` flag.

**Does it include Shorts and live streams?** The video list is the channel's Videos tab (long-form). Shorts are not included in this version.

**How far back do videos go?** 500 is an operational maximum, not a tested guarantee that YouTube will expose 500 videos. Recovery is bounded to 50 pages and stops on exhausted or repeated tokens, blocked requests or missing configuration. Duplicate-only pages are followed if they have a new continuation token.

**What happens with a wrong handle?** Confirmed missing channels are listed under `notFound`; malformed inputs under `invalidInput`; blocked or unparseable channels under `failed`. Those entries incur no channel event; the actor-start fee may still apply.

### Coverage and run diagnostics

`ok` means the available/requested video sample was recovered, not a complete account history.
`partial` means some requested video data is missing; `unavailable` means the initial video
list could not be recovered. `skipped` means video recovery was disabled (or private on TikTok).
`none_public` requires a recognized empty list/message, never just absent JSON or an unknown layout;
it describes the selected source surface, not proof that the entire account has no public content.
Valid profile/channel metadata is still delivered and charged if optional videos fail.
Missing metadata is not inferred. Video IDs are deduplicated before counting and averaging.

Aliases are deduplicated by stable userId/channelId under a delivery lock before the atomic
result-event charge. Rows without a stable identity are reported as failed rather than charged.
`OUTPUT` contains `requested` (unique normalized targets), `scraped` (accepted charged rows),
`notFound`, `failed`, `invalidInput`, `duplicates` (input and identity aliases with duplicateOf),
`videoIssues`, and `chargeLimitReached`. Invalid-only input writes OUTPUT before raising;
mixed input processes valid targets and records invalid entries without substituting demos.
The default demo is used only when the target key is absent. Reruns are separate runs and can charge again.

### Troubleshooting

| Problem | Solution |
|---|---|
| Channel in `failed` | YouTube temporarily blocked the request; rerun those channels. |
| Channel in `notFound` | Check the handle; the channel may be deleted or renamed. |
| videosStatus = partial | Video recovery was interrupted after some videos; available channel metadata is retained, not guaranteed complete. See videosError. |
| Run stopped early | Your max cost per run was reached; raise it. |

Only public data is collected. You are responsible for using it in compliance with YouTube's terms and privacy laws such as GDPR.

# Actor input Schema

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

Handles (@mkbhd, mkbhd), channel URLs (https://www.youtube.com/@mkbhd) or channel IDs (UC...). One channel = one result. Invalid or non-existent channels are reported in OUTPUT and never charged. Demo targets apply only when this key is absent; explicit empty, null or invalid targets never become demo data. Aliases resolving to the same stable identity are delivered and charged once per run; the actor-start fee is separate.

## `maxVideosPerChannel` (type: `integer`):

Operational cap of 0-500 Videos-tab items (0 skips recovery), not a guaranteed source volume. Pagination is bounded to 50 pages with repeated-token protection. On video failure available channel metadata is retained and videosStatus is partial/unavailable.

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

Channels processed in parallel.

## Actor input object example

```json
{
  "channels": [
    "@mkbhd",
    "@veritasium",
    "https://www.youtube.com/@NASA"
  ],
  "maxVideosPerChannel": 30,
  "maxConcurrency": 3
}
```

# Actor output Schema

## `results` (type: `string`):

Delivered unique stable-identity rows; optional video coverage and missing values are explicit in each row.

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

OUTPUT diagnostic: requested unique normalized targets, scraped accepted charges, notFound, failed, invalidInput, duplicates (input/identity aliases and duplicateOf), videoIssues, chargeLimitReached and error when applicable. Saved before invalid-input errors; actor-start fee is separate.

# 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",
        "https://www.youtube.com/@NASA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("angaba92/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",
        "https://www.youtube.com/@NASA",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("angaba92/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",
    "https://www.youtube.com/@NASA"
  ]
}' |
apify call angaba92/youtube-channel-scraper --silent --output-dataset

```

## MCP server setup

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