# ViewStats YouTube Channel Stats Scraper (`tomgab/viewstats-channel-scraper`) Actor

Ultra-fast, browserless YouTube channel analytics scraper powered by ViewStats. Resolve channels by name, @handle, UC channel ID, or URL to extract 28-day views, Longs vs. Shorts split, monthly/weekly/daily view history, subscriber deltas, rankings, and estimated revenue.

- **URL**: https://apify.com/tomgab/viewstats-channel-scraper.md
- **Developed by:** [Tomáš G](https://apify.com/tomgab) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 successfully scraped channels

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

<p align="center">
    <img src="https://api.apify.com/v2/key-value-stores/LA10KHOIL8D0FynUg/records/viewstats.png" alt="ViewStats icon" width="96" height="96">
</p>

## ViewStats YouTube Channel Stats Scraper

Ultra-fast, browserless Apify Actor for extracting YouTube channel statistics and historical view analytics from [ViewStats](https://www.viewstats.com/).

Instead of spinning up Playwright or Chromium, this Actor communicates directly with ViewStats' binary API over native HTTP and decodes `AES-256-GCM` payloads via WebCrypto (`crypto.subtle`) — scraping **30+ YouTube channels in seconds** while running comfortably inside **128–256 MB RAM**.

### Flexible Channel Inputs

Pass channels in any format — the Actor resolves them automatically:

- **Channel Name** — `Marques Brownlee` (resolved via ViewStats channel search)
- **Channel Handle** — `@mkbhd`
- **Immutable YouTube Channel ID** — `UCBJycsmduvYEL83R_U4JriQ`
- **YouTube or ViewStats URL** — `https://www.youtube.com/@mkbhd` or `https://www.viewstats.com/@mkbhd/channelytics`

All four formats above identify Marques Brownlee's channel.

### Pricing

**$5 per 1,000 successfully scraped channels ($0.005 per channel)**, including the selected historical time series and standard platform usage during the run. One successful channel record triggers one `channel-scraped` event. History points are not charged individually. Error and not-found records do not trigger this event. Identical trimmed inputs are deduplicated; different inputs resolving to the same channel produce separate results.

A small automatic startup fee of **$0.00005 per run** also applies. For example, 100 successful channels cost $0.50005. The Actor stops scheduling new channels when its spending limit is reached; results already in flight may be omitted to respect the limit. Check the Store Pricing tab for the current prices. Reading or storing datasets after a run may incur Apify's standard storage charges.

### Data Availability and Limitations

- Only channels tracked by ViewStats can be returned. Name searches select the best available match; use an exact YouTube channel ID or handle when identity matters.
- Historical coverage and metric availability depend on ViewStats. Missing fields can be `null`; revenue figures are estimates, not actual creator earnings.
- Each input produces an `ok`, `not_found`, or `error` record unless the run budget is exhausted or the run is interrupted. Inspect `status` and `error` in your integration.
- Standard Apify Proxy is enabled by default on Apify. Custom or residential proxy configurations can increase operating costs and are not needed for the default workflow.
- This is an independent Actor and is not affiliated with ViewStats or YouTube. Use the data in accordance with applicable terms and laws.

### Extracted Data Per Channel

- **Channel Identity & Metadata:** `channelId` (`UC...`), `handle` (`@...`), `displayName`, `verified`, `country`, `avatarUrl`, `bannerUrl`, `youtubeUrl`, `viewstatsUrl`
- **Totals & Rankings:** `subscriberCount`, `viewCount`, `videoCount`, `vpv90` (90-day views per video), `globalViewsRanking`, `globalSubscribersRanking`, `countrySubscriberRanking`, `categorySubscriberRanking`
- **28-Day & Monthly Metrics:** `views28d`, `views28dDelta`, `longViews28d`, `shortViews28d`, `monthlyAverageViews`, `estimatedMonthlyRevenueLowUsd`, `estimatedMonthlyRevenueHighUsd`, `uploadFrequency`
- **Multi-Period Breakdowns:**
  - `infoCards` (`weekly`, `monthly`, `quarterly`, `yearly`, `allTime`)
  - `averages` (`daily`, `weekly`, `monthly`, `yearly`, `uploadFrequency`)
  - `longsAndShorts` (`weekly`, `monthly`, `ninetyDays`, `yearly`, `allTime` long-form vs. Shorts views and upload counts)
- **Historical Time Series (`statsHistory`):** Configurable `range` (`7`, `28`, `90`, `365`, `alltime`) and `groupBy` (`monthly`, `weekly`, `daily`) with `viewCount`, `viewCountDelta`, `subscriberCount`, `subscriberCountDelta`, `videoCount`, `videoCountDelta`, and estimated USD revenue per bucket.

### Example Input

```json
{
    "channels": [
        "Marques Brownlee",
        "@mkbhd",
        "UCBJycsmduvYEL83R_U4JriQ",
        "https://www.youtube.com/@mkbhd"
    ],
    "range": "365",
    "groupBy": "monthly",
    "includeHistory": true,
    "includeEvents": false,
    "concurrency": 5
}
```

These four entries demonstrate alternative formats for the same channel. Use just one for a single result; submitting all four produces four separately charged results.

Or scrape a single channel directly:

```json
{
    "channel": "Marques Brownlee"
}
```

### Example Dataset Output Item

Illustrative output (values are examples):

```json
{
    "input": "Marques Brownlee",
    "resolvedBy": "search",
    "status": "ok",
    "error": null,
    "channelId": "UCBJycsmduvYEL83R_U4JriQ",
    "handle": "@mkbhd",
    "displayName": "Marques Brownlee",
    "verified": true,
    "country": "US",
    "subscriberCount": 20800000,
    "viewCount": 4912345678,
    "videoCount": 1765,
    "views28d": 107686373,
    "views28dDelta": 14231090,
    "longViews28d": 92604035,
    "shortViews28d": 25917682,
    "monthlyAverageViews": 118521717,
    "estimatedMonthlyRevenueLowUsd": 52100,
    "estimatedMonthlyRevenueHighUsd": 144500,
    "uploadFrequency": 5,
    "vpv90": 2410000,
    "globalViewsRanking": 1420,
    "globalSubscribersRanking": 412,
    "countrySubscriberRanking": 158,
    "categorySubscriberRanking": 14,
    "avatarUrl": "https://yt3.ggpht.com/...",
    "bannerUrl": "https://yt3.googleusercontent.com/...",
    "youtubeUrl": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ",
    "viewstatsUrl": "https://www.viewstats.com/@mkbhd/channelytics",
    "infoCards": { "weekly": {}, "monthly": {}, "quarterly": {}, "yearly": {}, "allTime": {} },
    "averages": { "daily": {}, "weekly": {}, "monthly": {}, "yearly": {}, "uploadFrequency": 5 },
    "longsAndShorts": { "weekly": {}, "monthly": {}, "ninetyDays": {}, "yearly": {}, "allTime": {} },
    "statsRange": "365",
    "statsGroupBy": "monthly",
    "statsHistory": [
        {
            "date": "2026-10-01",
            "viewCount": 4912345678,
            "viewCountDelta": 107686373,
            "subscriberCount": 20800000,
            "subscriberCountDelta": 100000,
            "videoCount": 1765,
            "videoCountDelta": 14,
            "estimatedLowRevenueUsd": 52100,
            "estimatedHighRevenueUsd": 144500,
            "estimatedRevenueUsd": 98300,
            "dayOfWeek": "THURSDAY",
            "isToday": false
        }
    ],
    "scrapedAt": "2026-10-29T18:10:00.000Z"
}
```

### Local Development

```bash
npm install
npm test
npm run typecheck
npm run build

## Validate Apify schemas and run locally
apify validate-schema
apify run
```

# Actor input Schema

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

List of YouTube channel names, handles, channel IDs, or YouTube / ViewStats URLs. Each successful channel costs $0.005, including history. Error and not-found records are free; a $0.00005 startup fee applies per run.

## `channel` (type: `string`):

Convenience field for scraping a single YouTube channel by name, @handle, UC channel ID, or URL. Can be used instead of or together with 'channels'.

## `range` (type: `string`):

Lookback window for the historical stats series.

## `groupBy` (type: `string`):

Time bucket granularity for the historical stats series (monthly, weekly, or daily).

## `includeHistory` (type: `boolean`):

Fetch the historical time-series array (statsHistory) for the selected range and grouping. Disable for an even faster summary-only run.

## `includeEvents` (type: `boolean`):

Include individual video upload events inside each historical bucket when available.

## `concurrency` (type: `integer`):

Maximum number of channels to scrape concurrently.

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

Uses standard Apify Proxy by default on the Apify platform because Cloudflare restricts the historical /stats endpoint from raw AWS EC2 IPs.

## Actor input object example

```json
{
  "channels": [
    "Marques Brownlee",
    "@mkbhd",
    "UCBJycsmduvYEL83R_U4JriQ",
    "https://www.youtube.com/@mkbhd"
  ],
  "range": "365",
  "groupBy": "monthly",
  "includeHistory": true,
  "includeEvents": false,
  "concurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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": [
        "Marques Brownlee",
        "@mkbhd",
        "UCBJycsmduvYEL83R_U4JriQ",
        "https://www.youtube.com/@mkbhd"
    ],
    "channel": "",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tomgab/viewstats-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": [
        "Marques Brownlee",
        "@mkbhd",
        "UCBJycsmduvYEL83R_U4JriQ",
        "https://www.youtube.com/@mkbhd",
    ],
    "channel": "",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("tomgab/viewstats-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": [
    "Marques Brownlee",
    "@mkbhd",
    "UCBJycsmduvYEL83R_U4JriQ",
    "https://www.youtube.com/@mkbhd"
  ],
  "channel": "",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call tomgab/viewstats-channel-scraper --silent --output-dataset

```

## MCP server setup

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