# YouTube Subscriber Count Scraper - Channel Statistics Monitor (`neverempty/youtube-subscriber-count-scraper`) Actor

Subscribers, total views, video count, join date and country for any YouTube channel from its About panel. Total views and video count are unrounded; the subscriber count is flagged as rounded. Monitoring mode returns a channel only when a count moves, with the change since last run.

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

## Pricing

from $2.92 / 1,000 channel returneds

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 Subscriber Count Scraper & Channel Statistics Monitor

Subscriber count, total views, video count, join date and country for any YouTube channel - and, with monitoring on, **a channel comes back only when one of its counts actually moves**, with the change since the last run. Unchanged channels are not returned as rows; each channel checked costs a small check fee.

Paste channels in any form, mixed in one list: `@handle`, a plain handle, a `UC...` channel id, a channel URL (`/@name`, `/channel/UC...`, `/c/Name`, `/user/Name`), or **a video URL** - for a video, the channel that uploaded it is read.

### What the numbers are, exactly

All numbers come from the channel's public **About** panel (`/@handle/about`), the same panel you see when you click "more" on a channel page.

| Column | What YouTube publishes | Rounded? |
|---|---|---|
| `subscriberCount` | "21.3M subscribers" | **Yes**, above 1,000 subscribers |
| `totalViews` | "5,633,505,202 views" | No |
| `videoCount` | "1,850 videos" | No |

YouTube does not publish an exact subscriber count on the page. Above 1,000 subscribers it shows three significant figures (21.3M, 517M, 6.58M); below that it shows the full number (726). This Actor does not pretend otherwise:

- `subscriberCount` is the displayed figure as a number (21300000),
- `subscriberCountIsRounded` says whether it was abbreviated,
- `subscriberCountPrecision` is the smallest step that display can show (100000 for "21.3M", 1 for "726"),
- `subscriberCountText` is YouTube's own text.

Total views and video count on the About panel are **not** rounded, unlike the "1.8K videos" in the channel header, which is why they are read from the About panel and nothing else.

Measured on 2026-09-14:

| Channel | subscriberCountText | subscriberCountPrecision | totalViews | videoCount |
|---|---|---|---|---|
| @mkbhd | 21.3M subscribers | 100000 | 5633505202 | 1850 |
| @jawed | 6.58M subscribers | 10000 | 428280196 | 1 |
| @kawsarlog | 726 subscribers | 1 | 17244 | 27 |
| Music (auto-generated) | 128M subscribers | 1000000 | null | null |

### Why every row names the channel it read

A YouTube channel page carries other channels' numbers too (recommended and featured channels). On the `/@mkbhd/about` page measured on 2026-09-14, the first subscriber count in the page source was **1.14M** - a different channel - while Marques Brownlee's own **21.3M** sat much further down. This Actor reads the numbers only from inside the About panel and returns a row only when the channel id in that panel matches the page's own channel id. Every row then states:

- `channelId`, `handle` and `channelTitle` - what YouTube returned,
- `input` and `inputType` - what you typed,
- `channelMatchesInput` - `true`/`false` when you typed a handle or channel id, `null` when you gave a `/c/`, `/user/` or video URL (there is nothing to compare).

### Monitoring mode

Turn **monitoringMode** on and schedule the Actor. It remembers each channel's counts and, on later runs:

- a channel whose counts have not moved is **not returned as a row**; it costs the check fee (see below), and if no channel moved you get one free `no-change` row;
- a channel whose counts moved comes back with `previousSubscriberCount`, `subscriberDelta`, `previousTotalViews`, `totalViewsDelta`, `previousVideoCount`, `videoCountDelta`, `previousCheckedAt` and `changedFields`;
- a channel it has never seen is returned once, so nothing is missed on the first run.

Choose what counts as a change with **monitorMode**:

- `any-change` - subscriber count, total views or video count moved. How often YouTube refreshes the total view count on the About panel has not been measured; for @mkbhd it did not move across four runs within about one minute on 2026-09-14.
- `subscriber-change` - the displayed subscriber count moved.
- `video-change` - the number of public videos moved (new uploads or removed videos).

Because the subscriber count is rounded, `subscriberDelta` is a difference of rounded figures: `subscriberDeltaIsApproximate` is `true` whenever either side was abbreviated. A large channel can show the same subscriber figure for days while it grows; `totalViewsDelta` and `videoCountDelta` are exact.

Counts are remembered **per channel, not per list**, in a store in your own account, so adding a channel to the list does not re-charge the others. **resetMonitoringState** clears them.

In monitoring mode **every changed channel is returned**; `maxChannels` does not apply. To cap what a monitoring run can cost, set the run's maximum total charge. Before checking each channel, the run makes sure the maximum leaves room for that check plus its row, so a low limit does not keep charging checks without returning anything: channels that did not fit are not checked, not charged, and named in a free `not-read` row. Raise the maximum total charge and they will be checked.

**Do not put the same channel in two monitoring schedules that can run at the same time.** Each run re-reads the remembered counts just before saving and only overwrites the channels it checked itself, so two overlapping runs with different channels keep each other's counts. But Apify's key-value store has no atomic update, so two runs saving the same channel at the same moment can still overwrite each other, and that channel may come back once more on a later run.

### What it costs

- **Channel row returned**: $4.00 per 1,000 rows. With monitoring off, that is the only charge: one row per channel you listed.
- **Channel checked** (monitoring mode only): $0.30 per 1,000 checks - one per channel whose About panel was read and compared, whether or not it changed. This pays for watching on the days when nothing moved.
- The rows that explain a missing channel, a duplicate, "no change", "no filter match" or an unchecked channel are free. A channel that could not be read is not charged as a check. In monitoring mode, a run where nothing changed still charges one check per channel read.

Example: 100 channels checked once a day is about 3,000 checks a month, $0.90, plus $4.00 per 1,000 for the rows that actually changed (at most 3,000 rows, $12.00, if every channel changes every day).

### Input

| Field | Meaning |
|---|---|
| `channels` | One channel per line: `@handle`, handle, `UC...` id, channel URL or video URL. Up to 1,000 lines per run. The same channel given twice is read and charged once. Empty = `@mkbhd` as an example, and a row says so. |
| `maxChannels` | Maximum channel rows to return when monitoring is off (1-1,000, default 100). With filters off, reading stops once this many channels are read; with filters on, everything is read and the extra matches are counted in a free row. Not used in monitoring mode, where every changed channel is returned. |
| `monitoringMode` | Return a channel only when its counts change (see above). |
| `monitorMode` | `any-change`, `subscriber-change` or `video-change`. |
| `resetMonitoringState` | Forget the remembered counts. |
| `minSubscribers` / `maxSubscribers` | Compared with the displayed subscriber count. A channel that shows no subscriber count is kept. |
| `countries` | Keep channels whose About panel shows one of these countries, in English as YouTube writes them (`United States`, `India`). Channels showing no country are dropped when this is set. |
| `onlyVerified` | Keep channels with the Verified or Official Artist Channel badge. |
| `keywords` / `keywordMatch` / `excludeKeywords` | Match against title, description, handle and channel keywords. |
| `useProxy` | Pages are requested directly first; a proxy is used only when YouTube answers with a bot check, HTTP 429 or a page without channel data. |

```json
{
    "channels": ["@mkbhd", "veritasium", "https://www.youtube.com/channel/UCsXVk37bltHxD1rDPwtNM8Q", "https://youtu.be/jNQXAC9IVRw"],
    "monitoringMode": true,
    "monitorMode": "any-change"
}
```

### Output

```json
{
    "status": "ok",
    "input": "@mkbhd",
    "inputType": "handle",
    "channelId": "UCBJycsmduvYEL83R_U4JriQ",
    "handle": "@mkbhd",
    "channelTitle": "Marques Brownlee",
    "channelUrl": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ",
    "channelMatchesInput": true,
    "subscriberCount": 21300000,
    "subscriberCountText": "21.3M subscribers",
    "subscriberCountIsRounded": true,
    "subscriberCountPrecision": 100000,
    "totalViews": 5633505202,
    "totalViewsText": "5,633,505,202 views",
    "videoCount": 1850,
    "videoCountText": "1,850 videos",
    "joinedDate": "2008-03-21",
    "joinedDateText": "Joined Mar 21, 2008",
    "country": "United States",
    "isVerified": true,
    "channelBadge": "Verified",
    "description": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics | Tech Head | Internet Personality! [email removed] NYC",
    "keywords": ["MKBHD", "MarquesBrownlee", "Marques", "Brownlee"],
    "links": ["Twitter: twitter.com/MKBHD", "Instagram: instagram.com/MKBHD"],
    "isFamilySafe": true,
    "isFirstCheck": true,
    "subscriberDelta": null,
    "totalViewsDelta": null,
    "videoCountDelta": null,
    "changedFields": []
}
```

Values YouTube does not show are `null`, never 0. On 2026-09-14 the auto-generated Music channel showed no total views, video count or country; @YouTube showed no join date; @NASA and @Google showed no country. E-mail addresses and phone numbers written into a channel description are replaced with `[email removed]` / `[phone removed]`. Links are returned as the channel typed them, without an added `https://`.

### Rows you are never charged for

Every row below explains why there is no channel data, and costs nothing:

- `no-such-channel` - YouTube has no channel at that handle or id (HTTP 404). YouTube search is not used, so a typo is not guessed for you.
- `no-such-video` - a video URL whose video YouTube did not return, so its channel could not be found.
- `invalid-input` - a line that is not a handle, channel id, channel URL or video URL (for example a search, playlist or hashtag URL).
- `blocked` - YouTube answered with a bot check on every attempt.
- `unreadable` - the page could not be read, or its About panel named a different channel than the page itself.
- `no-results` - the page loaded but showed no subscriber, view or video count.
- `duplicate` - a line that resolved to a channel already read in the same run.
- `no-change` - monitoring: none of the channels changed since the last run. The row is free; the checks behind it are charged as described above.
- `no-match` - channels were read but none matched your filters.
- `not-read` - lines left unread (or filter matches not returned) because `maxChannels` was reached, the 1,000-line limit was passed, or, in monitoring mode, the run's maximum total charge left no room to check them and return their row.
- `budget-reached` - the run reached the maximum total charge you set; the row says how many channels were delivered.
- `example-input` - the input was empty and `@mkbhd` was read as an example.

### How it reads YouTube

- Only paths that YouTube's robots.txt does not disallow are requested: `/@handle/about`, `/channel/UC.../about`, `/c/.../about`, `/user/.../about`, and `/oembed` for video links. `/results`, `/feeds/videos.xml`, `/youtubei/` and `/api/` are never requested.
- One request per channel (two for a video URL). A short pause between channels.
- Measured on this Actor on 2026-09-14 from Apify: 10 input lines (7 channels returned, 3 rows explaining a missing channel, a search URL and a duplicate) took 11.4 seconds with no proxy needed, at a platform cost of $0.0004 for the run.
- Pages are requested in English (`hl=en&gl=US`) so the numbers are always written the same way.
- From Apify, the About page was returned with channel data on 4 of 4 direct requests and 4 of 4 datacenter-proxy requests when measured on 2026-09-14. That can change; bot checks are detected and retried rather than returned as empty data.

### Limits

- The subscriber count is YouTube's rounded display, not an exact figure. No public page shows the exact number.
- Hidden subscriber counts: no channel with a hidden count was found for testing, so what YouTube shows for one has not been measured; such a channel would get `subscriberCount: null`.
- Private analytics (watch time, revenue, demographics) are not public and are not returned.

# Actor input Schema

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

One channel per line, in any of these forms, mixed freely: @handle (@mkbhd), a plain handle (mkbhd), a channel id (UC...), a channel URL (https://www.youtube.com/@mkbhd, /channel/UC..., /c/Name, /user/Name, with or without https://), or a video URL (watch?v=, youtu.be, /shorts/) - for a video, the channel that uploaded it is read. Every row names the channel YouTube actually returned (channelId, handle, channelTitle) and says in channelMatchesInput whether it is the handle or id you typed. The same channel given twice is read and charged once. Up to 1,000 lines per run. If left empty, @mkbhd is read as an example and the output says so.

## `maxChannels` (type: `integer`):

How many channel rows to return in total when monitoring is off (1 to 1,000). You are charged only for the channel rows you actually receive; rows that explain a missing channel are free. Not used in monitoring mode: there every changed channel is returned, and the run's maximum total charge caps cost - a channel is checked only if the limit leaves room for its check and its row.

## `monitoringMode` (type: `boolean`):

Off = return every channel you listed, charged per row. On = remember each channel's counts and, on later runs, return a channel only when a count actually moved, with the previous value and the change (subscriberDelta, totalViewsDelta, videoCountDelta). Each channel checked costs a small per-check fee ($0.30 per 1,000 checks) whether or not it changed; changed channels are also charged as rows. Counts are remembered per channel, not per list, so adding a channel to the list does not re-charge the others. Do not put the same channel in two monitoring schedules that can run at the same time: overlapping runs keep each other's channels, but Apify's key-value store has no atomic update, so the same channel saved by both at once can come back again later.

## `monitorMode` (type: `string`):

Any change = subscriber count, total views or video count moved. Subscriber change only = the subscriber count YouTube displays moved; above 1,000 subscribers YouTube displays it to three significant figures (21.3M), so a large channel can show the same value for days while it is growing. Video count change only = the number of public videos moved, which is how you catch new uploads or removed videos.

## `resetMonitoringState` (type: `boolean`):

Clears every remembered count for this Actor in your account, so the next monitoring run returns each channel once again. This affects all your monitoring runs, because counts are stored per channel rather than per list.

## `minSubscribers` (type: `integer`):

Drop channels whose displayed subscriber count is below this. A channel that shows no subscriber count is kept. 0 = no minimum.

## `maxSubscribers` (type: `integer`):

Drop channels whose displayed subscriber count is above this. A channel that shows no subscriber count is kept. 0 = no maximum.

## `countries` (type: `array`):

Keep only channels whose About panel shows one of these countries, written as YouTube writes them in English (United States, India, Germany). Channels that show no country are dropped when this is set. Empty = all countries.

## `onlyVerified` (type: `boolean`):

Keep only channels whose name carries YouTube's Verified badge or the Official Artist Channel badge (the badge text is returned in channelBadge).

## `keywords` (type: `array`):

Keep only channels whose title, description, handle or channel keywords contain these words.

## `keywordMatch` (type: `string`):

Any = at least one keyword. All = every keyword.

## `excludeKeywords` (type: `array`):

Drop channels whose title, description, handle or channel keywords contain any of these words.

## `useProxy` (type: `boolean`):

Every channel page is requested directly first, which is the fastest and cheapest path and worked from Apify on 4 of 4 attempts when measured on 2026-09-14. If YouTube answers with a bot check, HTTP 429 or a page without the channel data, the run switches to a proxy session and tries again (up to 3 attempts). With this off, such a page is returned as an uncharged row instead.

## Actor input object example

```json
{
  "channels": [
    "@mkbhd",
    "@veritasium",
    "https://www.youtube.com/@NASA",
    "UCsXVk37bltHxD1rDPwtNM8Q"
  ],
  "maxChannels": 100,
  "monitoringMode": false,
  "monitorMode": "any-change",
  "resetMonitoringState": false,
  "minSubscribers": 0,
  "maxSubscribers": 0,
  "onlyVerified": false,
  "keywordMatch": "any",
  "useProxy": true
}
```

# Actor output Schema

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

One row per YouTube channel with its subscriber count, total views, video count, join date and country, or an uncharged row saying why there is none (no such channel, no such video, bot check, unchanged since the last monitoring run, no filter match, duplicate).

# 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",
        "UCsXVk37bltHxD1rDPwtNM8Q"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/youtube-subscriber-count-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",
        "UCsXVk37bltHxD1rDPwtNM8Q",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/youtube-subscriber-count-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",
    "UCsXVk37bltHxD1rDPwtNM8Q"
  ]
}' |
apify call neverempty/youtube-subscriber-count-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/youtube-subscriber-count-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/94tmya21EE4eeXbMb/builds/K0c3pHuNYQ9G3UsY8/openapi.json
