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

Scrapes a YouTube channel's videos, Shorts and live streams (title, views, length, date, thumbnail, live flags) plus channel details: subscribers, total views, video count, joined date, country and links. Latest, popular or oldest first. No login or API key.

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

## Pricing

$0.80 / 1,000 rows

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

### What does YouTube Channel Scraper do?

**YouTube Channel Scraper** collects **every video, Short and live stream** of
any [YouTube](https://www.youtube.com) channel, together with the **channel's
own details**. For each video you get the **title, link, view count (exact
for videos and past streams), length, upload date, thumbnail and live or
upcoming status**. Upload dates are estimated from the age YouTube shows
(such as `3 weeks ago`) unless you turn on **exact dates**, which also adds
likes, descriptions, and exact views and lengths for Shorts. For each
channel you get its **name, handle, description, subscriber count, total
views, video count, joined date, country, links, avatar, banner and verified
badge**. Paste a channel link, an @handle, a channel ID or any video of the
channel, and pick **Latest**, **Popular** or **Oldest** first.

Try it with the prefilled channel: open the Actor, click **Start**, and NASA's
30 latest videos plus its channel details arrive in a few seconds. It needs
**no login, no API key and no YouTube Data API quota**. Because it runs on the
Apify platform, you also get API access, scheduled runs, integrations (Google
Sheets, Make, Zapier, webhooks), run monitoring and automatic retries through
Apify Proxy when YouTube refuses a request.

### Why scrape YouTube channels?

- **Competitor and creator research**: see what a channel publishes, how
  often, and which videos get the most views.
- **Influencer marketing**: compare subscribers, total views and recent
  performance of many channels in one table before you reach out.
- **Content strategy**: sort by **Popular** to find a channel's best
  performers, or by **Oldest** to study how it started.
- **Monitoring**: schedule a daily run to catch new uploads, Shorts,
  upcoming premieres and live streams as soon as they appear.
- **Datasets and analytics**: export clean JSON or CSV with numbers as
  numbers and dates as ISO 8601, ready for spreadsheets, BI tools or AI.

### How to scrape a YouTube channel

1. Copy a channel's link from YouTube, such as
   `https://www.youtube.com/@NASA`. An @handle, a channel ID (`UC…`) or the
   link of any video of the channel works too.
2. Paste it into **YouTube channels**, one channel per line.
3. Choose **What to scrape**: Videos, Shorts, Live streams or several.
4. Pick the order (**Latest**, **Popular** or **Oldest** first) and set **Max
   videos per channel**.
5. Turn on **Add exact dates, likes and descriptions** if you need them.
6. Click **Start**, then download the results when the run finishes.

### Input

Everything is set on the Input tab. In JSON, the input looks like this:

```json
{
    "channels": [
        "https://www.youtube.com/@NASA",
        "@MrBeast",
        "UCsXVk37bltHxD1rDPwtNM8Q",
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ],
    "content_types": ["videos", "shorts", "live"],
    "sort": "popular",
    "max_videos_per_channel": 100,
    "include_channel_details": true,
    "include_video_details": false
}
```

| Field | What it does |
|---|---|
| `channels` | Channels, one per line: links (`/@handle`, `/channel/UC…`, `/c/…`, `/user/…`, or a tab such as `/@NASA/videos`), bare @handles, channel IDs, or links to any video or Short of the channel. With or without `https://`; API callers can also send `{"url": "..."}` objects. |
| `content_types` | Which tabs to read: `videos`, `shorts`, `live` (past streams, live now and upcoming). Default: `["videos"]`. |
| `sort` | `latest`, `popular` or `oldest`, like the buttons above a channel's videos. Default: `latest`. |
| `max_videos_per_channel` | Stops after this many for each channel, counted separately per content type. Default: 100. Set `0` to save only channel details. |
| `include_channel_details` | Adds one row per channel with its details. Default: on. |
| `include_video_details` | Looks up each video once more for its exact publish time, likes, exact views, length, description, category and tags. Slower: one extra request per video. Default: off. |
| `proxyConfiguration` | Optional; leave it empty. YouTube answers the Actor directly, and the Actor switches to Apify Proxy by itself whenever YouTube refuses a request. |

### Output

Each channel gives one `channel` row (first), then one row per video, Short
or live stream. Here is a real channel row from the prefilled run, shortened:

```json
{
    "type": "channel",
    "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
    "channelName": "NASA",
    "channelHandle": "@NASA",
    "channelUrl": "https://www.youtube.com/@NASA",
    "description": "NASA's mission is to pioneer the future in space exploration, scientific discovery, and aeronautics research.\n\nTo do tha…",
    "subscriberCount": 15100000,
    "videoCount": 6187,
    "totalViewCount": 1127806724,
    "joinedDate": "2008-06-03",
    "country": null,
    "links": [
        { "title": "nasa.gov", "url": "https://www.nasa.gov" },
        { "title": "X", "url": "https://x.com/nasa" }
    ],
    "avatarUrl": "https://yt3.googleusercontent.com/eIf5fNPcIcj9ig-wZBeq4stFy1lgjWTW1nLT5dYlFkHZprZ03QBiMcbpwNMB6XSBjrSFGtAGQg=s900-c-k-c0x00ffffff-no-rj",
    "bannerUrl": "https://yt3.googleusercontent.com/y8Srh-oCcilLvBO8jAPI546m1bn0puA5DIvQiBZ1cSxc_rZfKxKSFlTCtoCQCtB6viUf1NGsaQ=w2560-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
    "isVerified": true,
    "keywords": ["nasa", "space", "iss", "shuttle", "50th"],
    "scrapedAt": "2026-10-10T07:21:00.259644+00:00"
}
```

And a video row from the same run:

```json
{
    "type": "video",
    "id": "ErAqN6gXqZQ",
    "url": "https://www.youtube.com/watch?v=ErAqN6gXqZQ",
    "title": "NASA's SpaceX Crew-13 Mission (Official NASA Trailer)",
    "viewCount": 99485,
    "durationSeconds": 62,
    "publishedTimeText": "9 days ago",
    "publishedAt": "2026-10-01T07:21:00+00:00",
    "publishedAtIsEstimate": true,
    "isLive": false,
    "isUpcoming": false,
    "scheduledStartAt": null,
    "thumbnailUrl": "https://i.ytimg.com/vi/ErAqN6gXqZQ/hq720.jpg?sqp=-oaymwEcCK4FEIIDSEbyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBdCgImHQMLNSlEXi3avww63RniAQ",
    "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
    "channelName": "NASA",
    "channelHandle": "@NASA",
    "channelUrl": "https://www.youtube.com/@NASA",
    "scrapedAt": "2026-10-10T07:21:00.395650+00:00"
}
```

With **Add exact dates, likes and descriptions** on, each row also has
`likeCount`, `description`, `category` and `tags`, and `publishedAt` becomes
YouTube's exact publish time. A real Short with details:

```json
{
    "type": "short",
    "id": "b1EMX3wyqCY",
    "url": "https://www.youtube.com/shorts/b1EMX3wyqCY",
    "title": "Your Brain on Magic Mushrooms",
    "viewCount": 900904,
    "durationSeconds": 71,
    "publishedTimeText": null,
    "publishedAt": "2026-09-28T14:00:34+00:00",
    "publishedAtIsEstimate": false,
    "isLive": false,
    "isUpcoming": false,
    "scheduledStartAt": null,
    "thumbnailUrl": "https://i.ytimg.com/vi/b1EMX3wyqCY/sardefault.jpg?sqp=-oaymwEdCJUDENAFSEaQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLCmGZk1G7Ds9HIK_keXYSunxLRoFA",
    "likeCount": 34977,
    "description": "Magic mushrooms temporarily change how different parts of your brain communicate…",
    "category": "Education",
    "tags": ["kurzgezagt", "kurzgesatg", "kurzgsagt"],
    "channelId": "UCsXVk37bltHxD1rDPwtNM8Q",
    "channelName": "Kurzgesagt – In a Nutshell",
    "channelHandle": "@kurzgesagt",
    "channelUrl": "https://www.youtube.com/@kurzgesagt",
    "scrapedAt": "2026-10-10T07:26:01.118585+00:00"
}
```

The Output tab has a **Videos** table and a **Channels** table. You can
download the dataset in various formats such as JSON, HTML, CSV, or Excel.

### Data fields

Video, Short and live stream rows:

| Field | Description |
|---|---|
| `type` | `video`, `short` or `live`: the channel tab it was listed on. |
| `id`, `url` | YouTube's video ID and its link (`/shorts/…` links for Shorts). |
| `title` | The title, as YouTube shows it to English-language visitors. |
| `viewCount` | Views, exact for videos and past streams. Shorts' counts are rounded as YouTube shows them (38K → 38000) unless video details are on. `null` for upcoming streams, and for streams that are live now unless video details are on (then it is the stream's total views so far). |
| `durationSeconds` | Length in seconds. `null` for Shorts (unless video details are on) and for live or upcoming streams. |
| `publishedTimeText` | The age YouTube shows, such as `9 days ago` or `Streamed 2 days ago`. `null` for Shorts and for streams that are live or upcoming. |
| `publishedAt` | Upload date and time in UTC (ISO 8601). With video details on, YouTube's exact publish time. Otherwise estimated as the scrape time minus `publishedTimeText`: YouTube rounds ages down, so the real date can be up to one unit earlier (up to a year for `5 years ago`). `null` when YouTube shows no age, and always for upcoming streams, which are not published yet (see `scheduledStartAt`). |
| `publishedAtIsEstimate` | `true` when `publishedAt` is estimated from the age, `false` when it is YouTube's exact publish time, `null` when there is no date. |
| `isLive` | `true` while the stream is live. |
| `isUpcoming` | `true` for a scheduled stream or premiere that has not started. |
| `scheduledStartAt` | For upcoming streams and premieres, the exact scheduled start in UTC. |
| `thumbnailUrl` | The largest thumbnail YouTube lists. |
| `likeCount`, `description`, `category`, `tags` | Only with video details on. `likeCount` is `null` when the channel hides likes. |
| `channelId`, `channelName`, `channelHandle`, `channelUrl` | The channel the video belongs to. |
| `scrapedAt` | When the row was saved. |

Channel rows (`type` = `channel`):

| Field | Description |
|---|---|
| `channelId`, `channelName`, `channelHandle`, `channelUrl` | The channel's ID (`UC…`), name, @handle and link. |
| `description` | The channel description. |
| `subscriberCount` | Subscribers. YouTube itself rounds this publicly (15.1M → 15100000); `null` when the channel hides it. |
| `videoCount` | Number of public videos, exact. |
| `totalViewCount` | Total views of the channel, exact. |
| `joinedDate` | When the channel was created (`YYYY-MM-DD`). |
| `country` | The country the channel lists, or `null` when it lists none. |
| `links` | The links the channel lists in its About panel, each with `title` and `url`. |
| `avatarUrl`, `bannerUrl` | Profile picture and banner image. |
| `isVerified` | `true` for verified channels and official artist channels. |
| `keywords` | The channel's keywords. |
| `scrapedAt` | When the row was saved. |

### How much does it cost to scrape a YouTube channel?

YouTube Channel Scraper uses **pay-per-event** pricing: you pay only for the
rows you get, one video, Short, live stream or channel row each. See the
**Pricing** tab for the exact price per 1,000 rows. Apify's free plan includes
monthly credit you can use to try it.

Channels that return nothing (removed, terminated or nonexistent) and links
that are not channels are never saved as rows, so you are not charged for
them. A channel without any videos of the kinds you asked for gives only its
channel row (one row) when channel details are on, and nothing otherwise. If you set a maximum cost for
a run, the Actor stops as soon as it is reached.

### Tips for scraping YouTube channels

- **Speed**: about 1,000 videos in 5 to 10 seconds; three channels are read
  at the same time. Video details add one request per video: expect about
  10 videos a second with them on.
- **The order picks the videos**: with a limit, **Latest** gives the newest,
  **Popular** the most viewed and **Oldest** the first uploads.
- **Only channel details**: set **Max videos per channel** to `0` to get one
  row per channel, for example to compare subscriber counts of many channels.
- **Everything**: set **Max videos per channel** to a large number such as
  `100000` to read a whole tab.
- **Monitoring**: schedule a daily run with **Latest** and a small limit, and
  keep only rows whose `id` you have not seen before.
- Each video is saved only once per run, even when YouTube lists it twice or
  you give the same channel twice in different forms.
- If the run's time limit is close, the Actor stops paging and finishes
  cleanly with everything collected so far.

### FAQ, disclaimers and support

**Which channels work?** Any public channel. Private and unlisted videos are
not listed on a channel's tabs, so they are not included. Some channels have
no Shorts or Live tab; for those, only the tabs they have are read. If a
channel has none of the kinds you asked for, you get only its channel row
(when channel details are on), and the `FAILED_INPUTS` record says so.

**Why are some dates estimates?** A channel's tabs only show ages such as
`3 weeks ago`, so `publishedAt` is estimated from them, and
`publishedAtIsEstimate` is `true`. Turn on **Add exact dates, likes and
descriptions** for YouTube's exact publish time.

**Why are titles sometimes in English for a non-English channel?** Titles are
read as YouTube shows them to an English-language visitor, so where a creator
added an English translation of a title, you get the translation.

**What happens to links that return nothing?** They are not saved as rows,
so you are not charged for them. Each one is listed with its reason (for
example `YouTube says: This channel does not exist.`) in the `FAILED_INPUTS`
record of the run's key-value store, together with channels that stopped
early or had none of the videos asked for, and links that repeat a channel
given earlier (its rows are saved once). The run's status message gives the
counts. A run fails only when every channel failed because YouTube could not
be reached.

**Is it legal to scrape YouTube channels?** This Actor collects only data
that YouTube shows publicly to anyone without logging in. Channel pages can
contain personal data, such as a creator's name, picture and links, so you
are responsible for making sure your use complies with YouTube's Terms of
Service and with data protection laws such as the GDPR. Collect only what
you need, and if in doubt, ask a lawyer.

**Found a problem or need a custom solution?** Open an issue on the
**Issues** tab and we will look into it. Custom scrapers and data feeds are
available on request.

# Actor input Schema

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

One channel per line. Channel links (https://www.youtube.com/@NASA, /channel/UC…, /c/…, /user/… or a tab such as /@NASA/videos), bare @handles (@NASA), channel IDs (UCLA_DiR1FfKNvjuUpBHmylQ) and links to any of the channel's videos or Shorts all work, with or without https://. Links that are not channels (playlists, other sites) are listed in the FAILED_INPUTS record.

## `content_types` (type: `array`):

Which of the channel's tabs to read. Each one gives its own rows, marked with type "video", "short" or "live".

## `sort` (type: `string`):

The order YouTube lists them in, as in the Latest / Popular / Oldest buttons above a channel's videos. Together with the limit below, this picks which videos you get: the newest, the most viewed or the first ones the channel uploaded.

## `max_videos_per_channel` (type: `integer`):

Stops after this many videos for each channel, counted separately for videos, Shorts and live streams. To get everything, set a large number such as 100000. Set 0 to save only the channel details.

## `include_channel_details` (type: `boolean`):

Adds one row per channel (type "channel") with its name, handle, description, subscriber count, total views, video count, joined date, country, links, avatar, banner and verified badge.

## `include_video_details` (type: `boolean`):

Looks up each video once more to add its exact publish date and time, like count, exact view count, length, description, category and tags. Without this, dates are estimated from the age YouTube shows ("3 weeks ago") and Shorts have no date or length. It makes runs slower: one extra request per video.

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

Leave this empty: YouTube answers the Actor directly, and if it ever refuses, the Actor retries through Apify's proxies on its own. Choose a proxy here only to send every request through it.

## Actor input object example

```json
{
  "channels": [
    "https://www.youtube.com/@NASA"
  ],
  "content_types": [
    "videos"
  ],
  "sort": "latest",
  "max_videos_per_channel": 30,
  "include_channel_details": true,
  "include_video_details": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

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

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

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

```

## MCP server setup

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