# YouTube Channel & Video Scraper (`garnet_puppet/youtube-channel-video-scraper`) Actor

Give it a channel, playlist, video link or search keyword and get the videos with title, view count, duration, publish date, thumbnail and the channel profile. Approximate values are flagged as approximate, and recent uploads are upgraded to exact figures for free.

- **URL**: https://apify.com/garnet\_puppet/youtube-channel-video-scraper.md
- **Developed by:** [Mehmet](https://apify.com/garnet_puppet) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 video scrapeds

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

Give this Actor a YouTube channel, a playlist, a video link or a search keyword, and it
returns the videos with their metadata: title, view count, duration, publish date,
thumbnail, and the channel's own profile. One row of output is one video.

*This Actor is independent and not affiliated with, endorsed by, or sponsored by
YouTube or Google LLC.*

### What it does

Paste channel URLs, `@handles`, channel IDs, playlist links or single video links into
**Channels, playlists or videos**, or use **Search keywords** to find videos by topic
instead. For each source the Actor reads YouTube's own listing pages and returns every
video it finds, up to the limits you set.

Three tabs are supported, each with its own limit so you are never billed for something
you did not ask for:

- **Videos** - the channel's regular uploads. Default limit 30.
- **Shorts** - a separate tab on YouTube. Default limit 0.
- **Live** - past and upcoming live streams. Default limit 0.

You can also choose the reading order: **Latest**, **Popular** or **Oldest**. Popular
and Oldest reach videos that a newest-first scrape would never get to on a large
channel.

### What makes the data different

Most channel scrapers hand you exactly what YouTube's listing page shows: a rounded
view count (`5.9M views`) and a relative date string (`13 days ago`). That is cheap to
produce, and for a lot of work it is fine. The problem starts when you feed it into a
spreadsheet or a database, because `"13 days ago"` is not a date, and it silently
becomes wrong the moment it is stored.

This Actor does three things about that.

**1. It gives you a real date.** Every record carries `publishedAt` as an ISO 8601
timestamp alongside the original `date` string, so both the human-readable form and the
machine-readable form are there.

**2. It tells you when a number is approximate.** Two boolean flags,
`viewCountIsApproximate` and `publishedAtIsApproximate`, say plainly whether a value
was derived from a rounded or relative source. Nothing is presented as exact when it is
not. If a date cannot be parsed at all, `publishedAt` is `null` rather than a guess.

**3. It upgrades what it can, for free.** With **Exact data for recent uploads** left
on (the default), the Actor spends one extra request per channel on that channel's
public feed, which carries the **exact view count**, the **exact publish date** and the
**full description** for its 15 most recent videos. Those records come back with both
approximation flags set to `false`. This costs you nothing extra.

So on a typical channel scrape you get precise data for the recent videos most people
actually care about, approximate-but-labelled data for the rest, and no way to confuse
the two.

### Channel profile, included

With **Include channel profile** on (the default), every record carries an
`aboutChannelInfo` object with the channel's name, handle, description, subscriber
count, total video count, total views, join date, country, links, avatar, banner and
verification status. It is not billed separately.

### Full details mode

Turning on **Full details** makes the Actor fetch every video individually. That adds:

- the **exact** view count and publish date for *every* video, not just recent ones
- the **full description**
- the video's **tags** (`keywords`)
- the YouTube **category**
- a duration for Shorts, which the listing page does not provide

This is one extra request per video, so runs are far slower, and each video is billed
an additional `video-details` event on top of `video-scraped`. Leave it off unless you
need tags or exact numbers on old videos.

### Honest limitations

- **Shorts have no duration and no publish date on the listing page.** YouTube does not
  publish them there. Those fields come back `null` for Shorts unless you turn on Full
  details. This is a limit of the source, and it is stated here rather than hidden
  behind an empty column.
- **Date filters are approximate for older videos.** "Published after" and "Published
  before" work on `publishedAt`. For the 15 most recent uploads of a channel that value
  is exact. Beyond that it is derived from a relative string like `3 weeks ago`, so a
  video near the boundary can fall on either side. The flags on every record tell you
  which is which.
- **A video with no known date is never filtered out.** Shorts would otherwise vanish
  silently from a date-filtered run, and you would have no way to tell why.
- **Live streams currently airing** have no view count. YouTube shows concurrent
  viewers instead, and that number is in `concurrentViewers` with `liveStatus: "live"`.

### Input

| Field | Default | What it does |
|---|---|---|
| `startUrls` | - | Channel URLs, `@handles`, channel IDs, playlist links, video links |
| `searchKeywords` | - | Search YouTube by keyword instead of pasting URLs |
| `maxVideos` | 30 | Videos tab limit per source |
| `maxShorts` | 0 | Shorts tab limit per source |
| `maxStreams` | 0 | Live tab limit per source |
| `sortVideosBy` | `latest` | `latest`, `popular` or `oldest` |
| `oldestPostDate` | - | Skip videos published before this date |
| `newestPostDate` | - | Skip videos published after this date |
| `enrichWithFeed` | `true` | Exact data for the 15 most recent uploads. Free |
| `includeChannelAbout` | `true` | Attach the channel profile. Free |
| `fullDetails` | `false` | One request per video: tags, exact numbers. Billed extra |
| `incremental` | `false` | Skip videos delivered by earlier runs of the same task |
| `previewOnly` | `false` | Report what would be scraped and billed, deliver nothing |
| `maxPagesPerSource` | 10 | Pagination hard stop, roughly 30 videos per page |

A **channel** link gives you that channel's videos. A **playlist** link gives you the
videos on that playlist. A **single video** link gives you that one video with its full
details - not the whole channel it belongs to, so pasting a list of video URLs bills
you for those videos and nothing more.

### Output

```json
{
  "id": "wt4p2oalmRY",
  "title": "Is spider web really stronger than steel?",
  "duration": "26:58",
  "channelName": "Veritasium",
  "channelUsername": "veritasium",
  "channelUrl": "https://www.youtube.com/channel/UCHnyfMqiRRG1u-2MsSQLbXA",
  "date": "13 days ago",
  "url": "https://www.youtube.com/watch?v=wt4p2oalmRY",
  "viewCount": 5945666,
  "fromYTUrl": "https://www.youtube.com/channel/UCHnyfMqiRRG1u-2MsSQLbXA",
  "type": "video",
  "thumbnailUrl": "https://i.ytimg.com/vi/wt4p2oalmRY/hqdefault.jpg",
  "input": "https://www.youtube.com/@veritasium",
  "order": 0,

  "videoId": "wt4p2oalmRY",
  "channelId": "UCHnyfMqiRRG1u-2MsSQLbXA",
  "durationSeconds": 1618,
  "publishedAt": "2026-08-02T13:21:11.000Z",
  "publishedAtIsApproximate": false,
  "viewCountIsApproximate": false,
  "concurrentViewers": null,
  "liveStatus": "none",
  "description": "The full video description...",
  "keywords": [],
  "category": null,
  "scrapedAt": "2026-08-15T18:00:00.000Z",
  "contentHash": "…",

  "aboutChannelInfo": {
    "channelName": "Veritasium",
    "channelUsername": "veritasium",
    "channelId": "UCHnyfMqiRRG1u-2MsSQLbXA",
    "channelUrl": "https://www.youtube.com/channel/UCHnyfMqiRRG1u-2MsSQLbXA",
    "channelDescription": "An element of truth - videos about science…",
    "channelDescriptionLinks": [],
    "channelJoinedDate": "Jul 21, 2010",
    "channelLocation": "Australia",
    "channelAvatarUrl": "https://yt3.googleusercontent.com/…",
    "channelBannerUrl": "https://yt3.googleusercontent.com/…",
    "channelTotalVideos": 526,
    "channelTotalViews": 3123456789,
    "numberOfSubscribers": 21100000,
    "isChannelVerified": true,
    "inputChannelUrl": "https://www.youtube.com/@veritasium"
  }
}
```

`keywords` and `category` are filled only in Full details mode.

#### Already using another YouTube channel scraper?

The first block of fields uses the same names and the same shapes as the most widely
used channel scraper on Apify, including the nested `aboutChannelInfo` object. Existing
integrations, spreadsheets and Make or Zapier flows keep working; the extra fields are
additions, not replacements.

### Billing

Two pay-per-event charges:

| Event | When |
|---|---|
| `video-scraped` | Every video record delivered to the dataset |
| `video-details` | Only in Full details mode, once per video, on top of the above |

Nothing else is billed. The channel profile, the free exact-data upgrade, pagination
and search all cost you nothing beyond the videos they produce.

**Incremental mode** remembers which videos a task already delivered and skips them, so
a scheduled run does not bill you a second time for a video you already have.

**Free preview** reports exactly which videos would be scraped and how many would be
billed, and delivers none of them. Each preview row carries `videosToCharge` and, when
Full details is on, `detailEventsToCharge` as well - so the preview never understates
what a real run would cost. Listing pages are cheap for us, so this preview is free for
you. One caveat, stated plainly: Apify's own platform charges an Actor start event on
every run, and no Actor can suppress that. "Free preview" means no scraping charges,
not a zero-cost run.

### Frequently asked

**Why is `viewCount` rounded on older videos?**
Because YouTube's listing page only shows `5.9M views` there. The exact number needs a
separate request per video, which is what Full details mode does. Rather than silently
present the rounded figure as exact, the record flags it.

**Can I get every video on a huge channel?**
Raise `maxVideos` and `maxPagesPerSource`. Each page holds about 30 videos, so 100
pages reaches roughly 3,000 videos. If a source had more videos than your limits
allowed, the run status says so instead of quietly cutting the list short.

**Does it need a proxy?**
No. Everything it does works from a plain datacenter connection, which is part of why
it is cheap to run.

**What happens if YouTube blocks the run?**
After five consecutive blocked or rate-limited sources the Actor stops on purpose and
says why in the run status, rather than burning the rest of your run on requests that
are not succeeding. Whatever it already delivered stays in the dataset.

# Actor input Schema

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

Channel URLs, @handles, channel IDs, playlist URLs or single video URLs. A channel gives you its videos; a playlist gives you the videos on it; a single video URL gives you that one video with its full details, not the whole channel.

## `searchKeywords` (type: `array`):

Search YouTube instead of pasting URLs. Search results carry exact view counts.

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

Upper bound on videos taken from the Videos tab of each source. Set to 0 to skip the Videos tab.

## `maxShorts` (type: `integer`):

Shorts live on a separate tab. Default 0 so you are never billed for Shorts you did not ask for. Note: YouTube does not publish a duration or a publish date for Shorts, so those fields come back empty unless you turn on Full details.

## `maxStreams` (type: `integer`):

Past and upcoming live streams from the Live tab. Default 0.

## `sortVideosBy` (type: `string`):

Which order the Videos tab is read in. 'Popular' and 'Oldest' let you reach videos a newest-first scrape would never get to.

## `oldestPostDate` (type: `string`):

Skip videos published before this date (YYYY-MM-DD). Careful: for videos older than the 15 most recent, YouTube only gives a relative date such as '3 weeks ago', so this filter is approximate for them. Every record tells you which dates are approximate.

## `newestPostDate` (type: `string`):

Skip videos published after this date (YYYY-MM-DD). Same approximation note as above.

## `enrichWithFeed` (type: `boolean`):

One extra request per channel upgrades its 15 most recent videos to an exact view count, an exact publish date and the full description. Costs you nothing extra.

## `includeChannelAbout` (type: `boolean`):

Attach the channel's subscriber count, total views, join date, country, links, avatar and banner to every record. Costs you nothing extra.

## `fullDetails` (type: `boolean`):

Fetch every video individually to add tags, the full description, an exact view count and an exact publish date. One request per video, so runs are much slower, and each video is billed an additional 'video-details' event.

## `incremental` (type: `boolean`):

Remembers video IDs delivered by previous runs of this task and skips them, so a scheduled run never bills you twice for the same video.

## `previewOnly` (type: `boolean`):

Report what would be scraped and how many videos would be billed, without delivering or billing any of them. Listing is free for us, so this preview is free for you. The platform's own Actor start event still applies.

## `maxPagesPerSource` (type: `integer`):

Hard stop on pagination. Each page holds about 30 videos.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.youtube.com/@veritasium"
  ],
  "searchKeywords": [],
  "maxVideos": 30,
  "sortVideosBy": "latest",
  "enrichWithFeed": true,
  "includeChannelAbout": true,
  "fullDetails": false,
  "incremental": false,
  "previewOnly": false,
  "maxPagesPerSource": 10
}
```

# Actor output Schema

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

One item per video: title, view count, duration, publish date, thumbnail, live status and the channel profile.

## `runSummary` (type: `string`):

Counts for the whole run: what was delivered, what was skipped, what failed, and any warnings. Read this to see whether the run did what you expected.

# 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": [
        "https://www.youtube.com/@veritasium"
    ],
    "searchKeywords": [],
    "maxVideos": 30,
    "maxShorts": 0,
    "maxStreams": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("garnet_puppet/youtube-channel-video-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 = {
    "startUrls": ["https://www.youtube.com/@veritasium"],
    "searchKeywords": [],
    "maxVideos": 30,
    "maxShorts": 0,
    "maxStreams": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("garnet_puppet/youtube-channel-video-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 '{
  "startUrls": [
    "https://www.youtube.com/@veritasium"
  ],
  "searchKeywords": [],
  "maxVideos": 30,
  "maxShorts": 0,
  "maxStreams": 0
}' |
apify call garnet_puppet/youtube-channel-video-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,garnet_puppet/youtube-channel-video-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/c60l6wakrbN13R5bg/builds/3GEVFcJeVGtkVR20U/openapi.json
