# YouTube Channel Scraper & Monitor (`lyklo/youtube-channel-scraper`) Actor

Gets subscribers, total views, posting frequency and the newest videos and Shorts of any YouTube channels. Schedule it to monitor competitors: each run reports only new uploads, fast-growing videos and removed videos.

- **URL**: https://apify.com/lyklo/youtube-channel-scraper.md
- **Developed by:** [Lyklo](https://apify.com/lyklo) (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

from $0.50 / 1,000 videos

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 & Monitor

Get the numbers and the newest videos of any YouTube channels: subscribers, total views, join date, links, how often they post, typical views, and each video's title, publish date, views, views per day, likes, length, description and whether it is a Short.

Switch **What to return** to *Changes since the last run* and schedule it, and each run tells you only what moved since the previous one: new uploads, videos that are taking off, and videos that were taken down. A day with nothing new costs one row per channel.

The data comes from public YouTube pages and channel feeds. No YouTube account or API key is needed. This is an independent tool. It is not made, endorsed or sponsored by YouTube or Google.

### Who it's for

- **Brand and marketing teams** who keep an eye on competitors' channels. Put in 5 to 20 channels, run it every morning, and send the result to Slack or a spreadsheet. You see what they uploaded yesterday and which of their recent videos are growing fastest, without opening each channel.
- **Influencer and creator agencies** comparing many channels at once: subscribers, uploads in the last 7 and 28 days, and average views of the newest 10 videos, one row per channel, ready for a report.
- **Creators** comparing their channel with others in the same niche.
- **Developers and AI agents** that need a channel's video list as data through the API.

### How to use it

1. Enter the channels, one per line. Any of these work: `https://www.youtube.com/@nike`, `@nike`, `https://www.youtube.com/channel/UCUFgkRb0ZHc4Rpq15VRCICA`, or the channel ID `UCUFgkRb0ZHc4Rpq15VRCICA`.
2. Pick **What to return**:
   - *All videos*: the channel row and its newest videos, every run.
   - *Changes since the last run*: the first run records the newest videos as `baseline`. After that you get only `new`, `growing` and `removed` videos.
   - *Channels only*: one row per channel, no video rows.
3. Set **Maximum videos per channel** (30 by default). To narrow the video rows, use **Only videos published in the last (days)** or **Only videos whose title contains** (for example `review, unboxing`).
4. To watch channels, save the input as a task and add a daily schedule in Apify. Connect it to Slack, Google Sheets, a webhook or Zapier/Make with Apify's integrations.

Use a different **Watch name** for each separate list (for example one per client). Runs with the same watch name share their memory of what was seen before.

### What you get

#### One row per video

| Field | Meaning |
| --- | --- |
| `change` | `listed` in *All videos* mode. In changes mode: `new`, `growing`, `removed`, or `baseline` on the first run |
| `channelTitle`, `channelHandle`, `channelId` | The channel |
| `videoId`, `url`, `title`, `thumbnailUrl` | The video |
| `isShort` | Whether it is a Short |
| `status` | `published`, `live` (streaming now) or `upcoming` (scheduled) |
| `publishedAt` | When it was published. Exact for the 15 newest uploads; for older ones it comes from YouTube's "3 weeks ago" and `publishedAtIsApproximate` is `true` |
| `views`, `likes` | Views and likes. Exact for the 15 newest uploads (`viewsAreExact`); older videos have YouTube's rounded view count (289K) and no likes |
| `viewsPerDay` | Views divided by days since publishing, so an old hit and a new video can be compared |
| `likesPerThousandViews` | Likes per 1,000 views, when likes are known |
| `description` | The video description, for the 15 newest uploads |
| `previousViews`, `viewsGain`, `viewsGainPercent` | For `growing`: views at the last run and how much they rose |
| `durationSeconds` | Length |

#### One row per channel

`subscribers` (and YouTube's own text, such as `2.28M subscribers`), `totalViews` (exact), `videoCount`, `joinedDate`, `country`, the external `links` on the channel page (website, Instagram, TikTok…), `videosLast7Days`, `videosLast28Days`, `uploadIntervalDays` (average days between the newest uploads), `avgViewsRecent10` and `medianViewsRecent10` (typical views of the newest 10, without one big hit skewing it), `likesPerThousandViewsRecent`, the 5 newest videos, the description, keywords and avatar. In changes mode also `subscribersChange`, `newVideosSinceLastRun`, and `topGrowing`, the 5 videos that gained the most views since the last run.

#### Output example

A video row and a channel row from a run on `@nike` (shortened):

```json
{
    "type": "video",
    "change": "listed",
    "channelTitle": "Nike",
    "videoId": "5Hp1oHf0UBw",
    "url": "https://www.youtube.com/shorts/5Hp1oHf0UBw",
    "title": "Rise and shine. Ja Morant.",
    "isShort": true,
    "status": "published",
    "publishedAt": "2026-10-22T17:01:26.000Z",
    "publishedAtIsApproximate": false,
    "views": 3568,
    "viewsAreExact": true,
    "likes": 177,
    "viewsPerDay": 902.6,
    "likesPerThousandViews": 49.6,
    "durationSeconds": 16,
    "thumbnailUrl": "https://i.ytimg.com/vi/5Hp1oHf0UBw/hqdefault.jpg"
}
```

```json
{
    "type": "channel",
    "channelTitle": "Nike",
    "channelHandle": "@nike",
    "subscribers": 2280000,
    "subscribersText": "2.28M subscribers",
    "totalViews": 372296343,
    "videoCount": 649,
    "joinedDate": "2006-03-07",
    "videosLast7Days": 3,
    "videosLast28Days": 9,
    "uploadIntervalDays": 3.4,
    "avgViewsRecent10": 11921,
    "medianViewsRecent10": 6103,
    "likesPerThousandViewsRecent": 11.1,
    "links": [{ "title": "Nike.com", "url": "https://www.nike.com/membership" }],
    "url": "https://www.youtube.com/@nike"
}
```

Video rows and channel rows share one dataset. Each row has `type` (`video` or `channel`); filter on it when you export the results or connect them to a spreadsheet.

### Pricing

You pay per event.

| Event | Price |
| --- | --- |
| Run start | $0.003 per run (with the default memory) |
| Video row: *All videos* mode, or `baseline` on the first run in changes mode | $0.50 per 1,000 |
| Video change: `new`, `growing` or `removed` | $1.00 per 1,000 |
| Channel row, one per channel | $0.003 each |

For example, watching 10 competitors' channels every day costs about $0.18 for the first run and about $1 a month after that, if they post a couple of videos a day between them. Listing 200 channels with 30 videos each once a week costs about $15 a month. Set a maximum cost per run in Apify if you want a hard limit; the run stops cleanly when it is reached, and the changes it could not report come out on the next run.

### Good to know

- **Subscriber counts are rounded by YouTube** (2.28M, 15.1K). `subscribersChange` moves only when the rounded number changes.
- **"New" means published recently.** A video counts as new when it was published after the last run, or up to 3 days before it, because some videos show up in the list late. An old video that becomes public again is recorded quietly instead of being reported as new. New videos are reported even when they are beyond your maximum.
- **"Growing"** means views rose by at least the percentage you set (20% by default) and by at least 100 views since the last run. Set it to 0 to turn it off.
- **"Removed" is checked only among the 15 newest uploads.** A video counts as removed when it was one of them and YouTube now says it is unavailable (deleted or made private). Videos that drop out of your maximum because newer ones arrived are never reported as removed, and neither are videos that become unlisted.
- **Uploads in the last 7 and 28 days** come from the publish dates above. For a channel that posts a lot, the run reads past your maximum to count them (without returning or charging those extra videos). If it still can't reach 28 days back, `postingCountsAreLowerBound` is `true` and the numbers mean "at least".
- **The filters apply to video rows only.** The channel row always counts every video. In changes mode a change that doesn't match the filters is not returned or charged, and it is not reported later either.
- **Views per day** uses the publish date. For videos older than the 15 newest uploads the date comes from YouTube's "3 weeks ago", so the figure is approximate (a little high rather than low).
- **Shorts** are told apart using YouTube's Shorts list. Turning off **Include Shorts** returns regular videos only; in changes mode it starts a fresh watch for each channel.
- **One run per watch at a time.** If a run starts while another run is still using the same watch name, it stops with an error instead of reporting the same changes twice.
- **If YouTube changes its pages**, the run stops with an error instead of returning wrong or empty results. Rows returned before that point are charged as usual; nothing after it is returned or charged.
- Comments, transcripts and channel e-mail addresses are not included.

# Actor input Schema

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

One channel per line: a channel URL (youtube.com/@nike or youtube.com/channel/UC…), a handle (@nike) or a channel ID (UC…).

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

All videos: the channel row and its newest videos, every run. Changes since the last run: only new videos, fast-growing videos and removed videos since the last run with the same watch name (the first run records the newest videos). Channels only: one row per channel.

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

How many of the newest videos to return for each channel. In changes mode these are the videos compared with the last run; new videos are reported even beyond this number.

## `includeShorts` (type: `boolean`):

Turn off to get regular videos only. Changing this starts a fresh watch for each channel in changes mode.

## `watchName` (type: `string`):

Changes mode only. Keeps the memory of past runs separate for each watch, for example one per client. Letters, numbers and dashes.

## `growthThresholdPercent` (type: `integer`):

Changes mode only. A video is reported as growing when its views rose by at least this percentage (and by at least 100 views) since the last run. 0 turns it off.

## `publishedWithinDays` (type: `integer`):

Returns only videos published within this many days. Leave empty for no limit. Applies to video rows only; the channel row still counts every video. Dates older than the 15 newest uploads come from YouTube's "2 weeks ago", so the edge is approximate for them.

## `titleKeywords` (type: `array`):

Returns only videos whose title contains at least one of these words or phrases (not case-sensitive). Leave empty for all videos. Applies to video rows only.

## Actor input object example

```json
{
  "channels": [
    "@nike"
  ],
  "mode": "all",
  "maxVideosPerChannel": 10,
  "includeShorts": true,
  "watchName": "default",
  "growthThresholdPercent": 20,
  "titleKeywords": []
}
```

# Actor output Schema

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

One dataset with two kinds of rows, told apart by the type field. Rows with type "video" are videos: listed in All videos mode, or new, growing and removed in changes mode (baseline on the first run). Rows with type "channel" are one per channel: subscribers, total views, join date, uploads in the last 7 and 28 days, typical views of the newest videos and the newest uploads.

# 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": [
        "@nike"
    ],
    "maxVideosPerChannel": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("lyklo/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": ["@nike"],
    "maxVideosPerChannel": 10,
}

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

```

## MCP server setup

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