# YouTube Channel Scraper – Videos, Views & Likes (`simple.actors/youtube-channel-videos`) Actor

Scrape the latest videos from any public YouTube channel — title, link, exact view count, likes, publish date, duration, description and thumbnail — one row per video. No login, no cookies, no API key and no browser: a channel listing costs one request. Unofficial.

- **URL**: https://apify.com/simple.actors/youtube-channel-videos.md
- **Developed by:** [Simple Actors](https://apify.com/simple.actors) (community)
- **Categories:** Social media, Videos, News
- **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?

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 Channel Scraper – Videos, Views & Likes

Scrape the latest videos from any public YouTube channel — **title, link, exact
view count, likes, publish date, duration, description, category and
thumbnail** — one row per video. Built for channel monitoring, competitor and
content research, influencer vetting and video dataset building.

No login, no cookies, no API key and **no browser**. Everything on a channel
listing is in a JSON object inside the page YouTube serves to a logged-out
reader, so a whole channel costs one request.

*Unofficial. Not affiliated with, endorsed by, or sponsored by YouTube or
Google.*

### Features

- **Latest videos per channel** — up to 30, newest first, read from the page
  YouTube serves a logged-out reader.
- **Exact view counts and publish instants** — not YouTube's rounded "53K views"
  and "3 days ago", but 53,674 and the real timestamp.
- **Like counts, descriptions and categories** — which appear nowhere on the
  channel listing.
- **Every address form works** — `@handle`, `/channel/UC…`, and the legacy `/c/`
  and `/user/` forms, so a list saved a decade ago still resolves.
- **Bare handles and channel IDs accepted** — paste a spreadsheet column
  straight in.
- **Channel context on every row** — name, id, handle, subscriber count and the
  channel's total video count, so you can tell 30-of-30 from 30-of-6,100.
- **Rounded figures are always labelled** — `viewCountIsApproximate` and
  `publishedAtIsApproximate` mean you are never silently misled.
- **A cheap freshness mode** — turn details off and a whole channel is one
  request.
- **Incremental polling** — `onlyVideosNewerThan` takes `7 days`, `3 months` or
  an ISO date, and refuses rather than returning a misleading short list.
- **No browser, 256 MB** — fast and light.
- **Runs stay green.** A nonexistent channel or a pasted video URL is a row.

### Input

```json
{ "handles": ["NASA"], "maxVideos": 30 }
```

All four of YouTube's address forms work, so a list saved at any point in the
last decade still resolves:

```json
{
  "startUrls": [
    { "url": "https://www.youtube.com/@NASA" },
    { "url": "https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ" },
    { "url": "https://www.youtube.com/user/NASAtelevision" }
  ]
}
```

| Field | Type | Default | What it does |
|---|---|---|---|
| `startUrls` / `handles` | array | — | The channels. `handles` also takes bare handles (`NASA`) and channel IDs (`UCLA_…`). |
| `maxVideos` | integer | `30` | Newest first. 30 is the ceiling — see [What it cannot do](#what-it-cannot-do). |
| `includeVideoDetails` | boolean | `true` | Exact views, likes, description and publish date. One request per video, and what the price turns on — see [What it costs](#what-it-costs). |
| `onlyVideosNewerThan` | string | — | `7 days`, `3 months`, or an ISO date. |
| `proxy` | object | Apify datacenter | What it is built and measured on. Residential is available but rarely worth it — see below. |

### Output

```json
{
  "type": "video",
  "videoId": "FkgVB19I6xw",
  "url": "https://www.youtube.com/watch?v=FkgVB19I6xw",
  "title": "NASA's Roman Space Telescope: A New Perspective on the Cosmos",
  "viewCount": 53674,
  "viewCountIsApproximate": false,
  "likeCount": 1839,
  "publishedAt": "2026-08-24T21:00:22.000Z",
  "publishedText": "3 days ago",
  "publishedAtIsApproximate": false,
  "durationSeconds": 46,
  "category": "Science & Technology",
  "thumbnailUrl": "https://i.ytimg.com/vi/FkgVB19I6xw/hq720.jpg",
  "channelName": "NASA",
  "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
  "channelHandle": "@NASA",
  "subscriberCount": 15100000,
  "channelVideoCount": 6100,
  "scrapedAt": "2026-08-28T15:40:29.000Z"
}
```

**Store `channelId`, not the handle.** A channel's owner can change its handle;
the `UC…` id never changes.

#### Exact figures, and when you get them

YouTube rounds everything it displays. A channel listing says "53K views" and
"3 days ago" — 53,674 and 53,001 both print as 53K. The exact numbers live only
on each video's own page, which is what `includeVideoDetails` fetches, along
with the like count and description that exist nowhere else.

**About 92% of those reads land.** YouTube refuses some from datacenter
addresses; the Actor retries each up to six times on fresh ones, which took the
rate from 60% to 92% measured across 100 videos on ten channels. Residential
lifts the last 8% at roughly ten times the cost per video, which is rarely worth
it.

You are never misled. Every row says which figures it got:

| | `viewCountIsApproximate` | `likeCount` |
|---|---|---|
| exact figures read | `false` | a number |
| listing figures only | `true` | `null` |

A video with **no public view count at all** — a members-only upload, say —
comes back with `viewCount: null` and `viewCountIsApproximate: null`. A null
flag means there is no figure to describe, not a rounded one waiting to be
improved.

### How to use

#### From Apify Console

1. Open the Actor and click **Try for free** / **Start**.
2. Paste channel addresses into **Channels**, or a column of bare handles and
   channel IDs into **Channels (plain list)** — both are read together. Give a
   channel, not a video.
3. Leave **Exact views, likes and dates** on for real figures, likes,
   descriptions and categories. Turn it **off** for a cheap freshness sweep
   across many channels — a channel then costs one request.
4. To poll for what is new, set **Only videos newer than** to a window shorter
   than the gap between your runs.
5. Click **Start**, then open the **Dataset** tab and export as JSON, CSV or
   Excel.

**Store `channelId`, not the handle** — the handle can change, the `UC…` id
cannot.

#### From the API

```bash
curl -s "https://api.apify.com/v2/acts/simple.actors~youtube-channel-videos/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"handles": ["NASA"], "maxVideos": 30}'
```

A cheap daily freshness check across many channels, with the JavaScript client:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('simple.actors/youtube-channel-videos').call({
    handles: ['NASA', 'veritasium', 'UCLA_DiR1FfKNvjuUpBHmylQ'],
    includeVideoDetails: false,   // one request per channel, rounded figures
    onlyVideosNewerThan: '2 days',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

for (const row of items) {
    if (row.error) { console.warn(row.error, row.errorDescription); continue; }
    console.log(row.channelName, row.title, row.publishedText);
}
```

### Use cases

- **Monitor channels for new uploads** — poll a list on a schedule with
  `onlyVideosNewerThan` and `includeVideoDetails: false` for a cheap check, then
  read the details of anything new.
- **Competitor and content research** — titles, durations, categories,
  descriptions and exact view counts across rival channels.
- **Influencer vetting** — subscriber count, total video count and real
  engagement on recent uploads, rather than YouTube's rounded display figures.
- **Video performance benchmarking** — exact views and likes per video, loaded
  straight into a spreadsheet or warehouse.
- **Building a video dataset** — metadata for a channel's latest 30 uploads with
  stable `videoId` and `channelId` keys.
- **Media and brand monitoring** — watch newsroom, agency and brand channels for
  what they published today.

### Usage notes

#### What it costs

| | |
|---|---|
| each video returned | **$0.0005** |
| each video read for exact figures | **$0.0010** on top |

So **$1.50 per 1,000 videos** with exact views, likes, descriptions and publish
dates, and **$0.50 per 1,000** for titles, links, durations, thumbnails and
rounded figures.

That split is the real cost, passed on: a channel listing is one request for up
to thirty videos, while the exact figures are a request each. If you are
watching a lot of channels to see what is new, turn `includeVideoDetails` off
and a channel costs one request.

Nothing else is charged. A channel that could not be read, a video with no
detail page available, a run whose every input was unusable — none of them bill.

#### What it cannot do

**Thirty videos per channel.** That is what a channel page renders to a
logged-out reader; going further needs YouTube's internal browse API with a
signed continuation token, which is a far more fragile surface than reading a
page. Every row carries `channelVideoCount`, so you can always tell whether you
are seeing all of a channel's videos or the latest thirty of many.

If you ask for a window reaching further back than those thirty
(`onlyVideosNewerThan: "10 years"`), the channel comes back as an error row
(`window_too_wide`) rather than a short list that would look like a complete
answer for that window.

#### Failures

**The run always succeeds.** A channel that does not exist, a string that could
never be a handle, a video URL pasted where a channel belongs — each is a row,
and the run's status message counts them. Only a bug in this Actor fails a run.

> **Act on the presence of an `error` field, never on the run status.** A
> channel read fine that has posted nothing produces no rows; a channel that
> could not be read produces exactly one row, carrying `error` and no `videoId`.

| `error` | what happened |
|---|---|
| `not_found` | no such channel |
| `bad_input` | the text given could never be a channel, or is a video/playlist URL |
| `window_too_wide` | the window reaches further back than one page of videos shows |
| `read_failed` | YouTube could not be reached, or served a page in a language this Actor could not read |

### FAQ

**Is scraping YouTube legal?**
This Actor reads only the data YouTube already serves to a logged-out visitor in
the page itself — it does not log in, use cookies, or reach private or
members-only content. YouTube's Terms of Service restrict automated collection,
so check the platform's ToS and your own obligations before using it.

**Do I need a YouTube Data API key or a Google Cloud project?**
No. There is no key, no OAuth and no quota of your own to manage.

**Can I get more than 30 videos per channel?**
No. Thirty is what a channel page renders to a logged-out reader; going further
needs YouTube's internal browse API with a signed continuation token, which is a
far more fragile surface than reading a page. Every row carries
`channelVideoCount`, so you can always tell whether you are seeing all of a
channel's videos or the latest thirty of many.

**Does it support pagination?**
Not beyond that first page, for the reason above. To follow a channel over time,
run it on a schedule with `onlyVideosNewerThan`.

**Why is `viewCount` rounded on some rows?**
Because the exact figure lives only on each video's own page, and YouTube
refuses some of those reads from datacenter addresses — about 92% land after up
to six retries. Those rows keep the listing's rounded figures and say so via
`viewCountIsApproximate: true`, with `likeCount: null`. You are never silently
given a wrong number.

**What does `viewCountIsApproximate: null` mean?**
That there is no figure to describe at all — a members-only upload, for example,
publishes no view count. A null flag is not a rounded number waiting to be
improved.

**Should I turn on residential proxy?**
Rarely. It lifts that last 8% of detail reads at roughly ten times the cost per
video, and every row already tells you which figures it carries.

**Should I store the handle or the channel ID?**
The `channelId`. A channel's owner can change its handle at any time; the `UC…`
id never changes.

**Why did my run succeed when a channel was not read?**
By design. A channel that does not exist, a string that could never be a handle,
or a video URL pasted where a channel belongs are each answers about that input,
not faults — so each is a row and the run stays green. Act on the presence of an
`error` field, never on the run status.

# Actor input Schema

## `startUrls` (type: `array`):

Channel addresses, one per entry. All four of YouTube's forms work: https://www.youtube.com/@NASA, /channel/UC…, and the legacy /c/ and /user/ forms. Give a channel, not a video — a watch URL comes back as an error row telling you so.

## `handles` (type: `array`):

The same thing as a plain list — easier to paste a column out of a spreadsheet. Accepts "@NASA", bare "NASA", or a channel ID like UCLA\_DiR1FfKNvjuUpBHmylQ. Both inputs are read; giving either is enough.

## `maxVideos` (type: `integer`):

How many of the channel's latest videos to return, newest first. A channel page renders 30 to a logged-out reader and offers no way to page further without YouTube's internal API, so 30 is the ceiling — every row carries channelVideoCount so you can tell when you are seeing all of them.

## `includeVideoDetails` (type: `boolean`):

On by default. A channel listing rounds every view count ("53K views") and gives only a relative date ("3 days ago"). This reads each video's own page for the exact figures — 53,674 views and the real publish instant — and adds the like count, description and category, which appear nowhere else.

It is also what the price turns on. Reading a channel listing is one request for up to thirty videos; the exact figures are a request each, and measured that is five times the cost. So a video costs $0.0005 either way and the exact figures add $0.0010 on top — $1.50 per 1,000 videos with them, $0.50 per 1,000 without. Turn this off for a cheap freshness check across many channels.

Measured at about 92% of videos: YouTube refuses some of these reads from datacenter addresses even after six retries. Those rows keep the listing's rounded figures and say so via viewCountIsApproximate and publishedAtIsApproximate — never silently wrong — and are not charged the detail fee.

## `onlyVideosNewerThan` (type: `string`):

Keep only videos published after this point — an ISO date like 2026-08-01, or a window like "7 days", "3 months". If the window reaches further back than the 30 videos a channel page shows, the channel comes back as an error row (window\_too\_wide) rather than a short list that would look like a complete answer.

## `proxy` (type: `object`):

Defaults to Apify's datacenter proxy, which is included in every plan and is what this Actor is built and measured on. Channel listings read reliably; the per-video exact figures land on about 92% of videos after retries.

Residential is available but rarely worth it here: it lifts that last 8% at roughly ten times the cost per video, and every row already says which figures it carries.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.youtube.com/@NASA"
    }
  ],
  "handles": [
    "@NASA"
  ],
  "maxVideos": 30,
  "includeVideoDetails": true,
  "onlyVideosNewerThan": "7 days",
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `videos` (type: `string`):

One row per video: channel, title, date, views and link.

## `raw` (type: `string`):

Every field of every row, exactly as pushed.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.youtube.com/@NASA"
        }
    ],
    "handles": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("simple.actors/youtube-channel-videos").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 = {
    "startUrls": [{ "url": "https://www.youtube.com/@NASA" }],
    "handles": [],
}

# Run the Actor and wait for it to finish
run = client.actor("simple.actors/youtube-channel-videos").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 '{
  "startUrls": [
    {
      "url": "https://www.youtube.com/@NASA"
    }
  ],
  "handles": []
}' |
apify call simple.actors/youtube-channel-videos --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,simple.actors/youtube-channel-videos"
        }
    }
}

```

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/wNEhs130vibncNlaz/builds/JbgLc4PwQy09j1Fub/openapi.json
