# YouTube Videos Scraper: Channels, Playlists, Search (`arcadium/youtube-videos`) Actor

YouTube videos and Shorts (title, channel, exact date, duration, views, likes, description) from channels, playlists, searches or links; $3.60 per 1,000 videos. Fast listing mode $1.17 per 1,000. No date-filter surcharge, free error rows.

- **URL**: https://apify.com/arcadium/youtube-videos.md
- **Developed by:** [Arcadium Data](https://apify.com/arcadium) (community)
- **Categories:** Videos, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.16 / 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.
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?

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 Videos Scraper: Channels, Playlists, Search

Give it YouTube channels, playlists, search terms, hashtags or video links and it returns one JSON row per video or Short: title, channel, exact publish date, duration, views, likes, category, Shorts and live flags, tags, hashtags, description, thumbnail and caption languages. Shorts also get comment count and sound. $3.60 per 1,000 videos, or $1.17 in fast listing mode; you pay only for rows you get.

**Why this actor**

- No extra charge for date ranges (the leading actor adds $1.30 per 1,000 on the free plan).
- Input names of the leading YouTube actors are accepted (`searchQueries`, `startUrls`, `maxResults`, `oldestPostDate` and more).
- Private, age-restricted, members-only and missing videos come back as free error rows that say why.

Built by Arcadium Data: public data, dated and agent-ready, never personal data.

### Use with AI agents

The actor takes plain JSON and returns one flat row per video, so an AI agent or MCP client (for example through [mcp.apify.com](https://mcp.apify.com)) can call `arcadium/youtube-videos` directly. Input example for "The last 10 videos of a channel with views and likes":

```json
{ "urls": ["https://www.youtube.com/@kurzgesagt"], "maxResults": 10 }
```

"Top 20 search results, cheap and fast":

```json
{ "searchQueries": ["web scraping tutorial"], "maxResults": 20, "listingOnly": true }
```

"Most viewed search results in Germany this month":

```json
{ "searchQueries": ["e-auto test"], "searchCountry": "DE", "sortingOrder": "views", "dateFilter": "month", "maxResults": 50 }
```

"Everything a channel published in the last 30 days":

```json
{ "urls": ["@veritasium"], "publishedAfter": "30 days", "maxResults": 500 }
```

"The newest Shorts of a channel, and Shorts for a hashtag and a search term":

```json
{ "urls": ["https://www.youtube.com/@MrBeast/shorts"], "hashtags": ["#cooking"], "searchQueries": ["life hacks"], "contentType": "shorts", "maxResults": 20 }
```

Read `title`, `publishDate`, `viewCount`, `likeCount` and `durationSeconds` (Shorts also `commentCount`, `soundTitle`, `soundArtist`); `position` is the search rank or playlist index. 100 videos cost $0.36 ($0.12 in fast mode). A private, age-restricted or members-only video, or an unknown channel, comes back as a free row with `rowType: "error"`, a `code` (`not_found`, `unavailable`, `no_match`, `blocked`, `invalid_input`) and a `message`, never as a failed run, so an agent can tell "does not exist" from "try again later".

### What you get

| Field | What it holds |
| --- | --- |
| `videoId`, `url`, `title` | The video. Shorts are returned the same way. |
| `channelName`, `channelId`, `channelHandle`, `channelUrl` | The channel that published it. |
| `publishDate`, `publishDateIsApprox`, `publishedText` | Exact ISO 8601 date with YouTube's offset (full rows), or a date estimated from "3 months ago" in fast mode. |
| `durationSeconds`, `viewCount`, `likeCount`, `category` | Length and stats at the time of the run. |
| `commentCount` | Exact comment count. Always on Shorts; on regular videos with `includeCommentCount`. |
| `soundTitle`, `soundArtist` | Shorts: the sound or song the Short uses, as YouTube's sound link shows it (null when the Short has none). |
| `isShort`, `isLive`, `isLiveContent`, `isUpcoming`, `isUnlisted`, `isFamilySafe` | Format and status flags. |
| `keywords`, `hashtags`, `description` | Tags, the #hashtags of the title and description, and the description (full or short); emails and phone numbers removed. |
| `thumbnailUrl`, `thumbnailMaxUrl` | 480x360 thumbnail (always), and the largest one when the upload has it. |
| `captionLanguages`, `hasCaptions` | Every caption track: `code`, `name`, `isAutoGenerated`. Pair it with our YouTube Transcripts actor to get the text. |
| `detailLevel` | `full` or `listing` (fast mode). |
| `source`, `sourceType`, `position` | Which channel tab, playlist, search term or hashtag the video came from, and its place in that list. |
| `scrapedAt` | When the row was read. |

Output example:

```json
{
  "rowType": "video",
  "platform": "youtube",
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
  "channelName": "Rick Astley",
  "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
  "channelHandle": "@RickAstleyYT",
  "channelUrl": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw",
  "publishDate": "2009-10-24T23:57:33-07:00",
  "publishDateIsApprox": false,
  "publishedText": null,
  "durationSeconds": 213,
  "viewCount": 1829080503,
  "likeCount": 19491607,
  "commentCount": null,
  "category": "Music",
  "isShort": false,
  "isLive": false,
  "isLiveContent": false,
  "isUpcoming": false,
  "isUnlisted": false,
  "isFamilySafe": true,
  "keywords": ["rick astley", "Never Gonna Give You Up", "nggyu"],
  "hashtags": ["rickastley"],
  "soundTitle": null,
  "soundArtist": null,
  "description": "The official video for “Never Gonna Give You Up” by Rick Astley. ...",
  "thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
  "thumbnailMaxUrl": "https://i.ytimg.com/vi_webp/dQw4w9WgXcQ/maxresdefault.webp",
  "captionLanguages": [
    { "code": "en", "name": "English", "isAutoGenerated": false },
    { "code": "en", "name": "English", "isAutoGenerated": true },
    { "code": "de-DE", "name": "German (Germany)", "isAutoGenerated": false }
  ],
  "hasCaptions": true,
  "detailLevel": "full",
  "source": null,
  "sourceType": "video",
  "position": null,
  "scrapedAt": "2026-10-22T08:00:00.000Z"
}
```

### Input

```json
{
  "urls": [
    "https://www.youtube.com/@kurzgesagt",
    "https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH",
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "searchQueries": ["iphone 17 review"],
  "maxResults": 10
}
```

| Field | What it does |
| --- | --- |
| `urls` | Channel links (`/@handle`, `@handle`, `/channel/UC...`, `/c/...`, `/user/...`; add `/shorts` or `/streams` for those tabs), playlist links, video links (`watch?v=`, `youtu.be`, `/shorts/`, `/live/`), bare video IDs, hashtag pages (`/hashtag/cooking`, `/hashtag/cooking/shorts`) and search page URLs. |
| `searchQueries` | Search terms; each gives up to `maxResults` videos in YouTube's order. |
| `hashtags` | Hashtags (`#cooking` or `cooking`); each gives up to `maxResults` videos from the hashtag page. |
| `contentType` | `videos` (default) or `shorts`: with `shorts`, search terms return Shorts only and hashtags read their Shorts tab. |
| `maxResults` | Videos per channel videos tab, playlist or search term (default 50, up to 10,000). |
| `maxResultsShorts`, `maxResultStreams` | Also read each channel's Shorts and Live tabs, up to this many (default 0 = skip). |
| `maxItems` | Stop after this many videos in total. A cost cap. |
| `publishedAfter`, `publishedBefore` | Date range, `YYYY-MM-DD` or relative (`"30 days"`). Skipped videos are free; a channel listed newest first stops at the first older videos. |
| `sortVideosBy` | Channel order: `NEWEST` (default), `POPULAR`, `OLDEST`. |
| `listingOnly` | Fast mode: listing data only, no per-video request, lower price (see below). |
| `includeCaptionLanguages` | Caption track list per video (on by default). |
| `includeCommentCount` | Comment count for regular videos too (Shorts always have it). One more small request per video. |
| `description` | `full` (default), `short` or `none`. |
| `sortingOrder`, `dateFilter`, `lengthFilter`, `isHD`, `hasSubtitles`, `hasCC`, `isLive`, `is4K` | YouTube's own search filters. |
| `searchCountry` | Two-letter country YouTube ranks search results for, e.g. `DE` (default `US`). |
| `includeErrorRows` | A free row for every input or video without data (on by default). |

Input names of the most used YouTube scrapers are accepted as they are (`startUrls` with `{ "url": ... }` objects, `channelUrls`, `youtubeHandles`, `channelHandle`, `channelId`, `videoUrls`, `keywords`, `oldestPostDate`, `sortOrder`, `maxVideos`, `maxVideosPerChannel`, `maxTotalResults`, `start_date`, `end_date`, `hashtag`, `onlyShorts`, `videoType`, the Shorts scraper's `channels` and `sortChannelShortsBy`, and the search actors' `q`, `query`, `order`, `sortBy`, `videoDuration`, `duration`, `uploadDate`, `type`, `regionCode` and `geo`), so an existing input can be pasted in.

#### Full rows and fast mode

A full row reads the video's own page data: exact publish date, exact views, likes, category, tags, description and caption languages (one or two small requests per video; Shorts one more for the comment count and the sound). Fast mode (`listingOnly: true`) returns what the channel, playlist or search page already shows, about 30 videos per request: title, rounded views ("3.5M"), duration, channel and a date estimated from "3 months ago". With a date range, fast mode still checks the exact date of videos close to the range edge, so the range stays exact.

### Use it from code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("arcadium/youtube-videos").call(run_input={
    "urls": ["https://www.youtube.com/@kurzgesagt"],
    "maxResults": 20,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["rowType"] == "video":
        print(item["publishDate"][:10], item["viewCount"], item["title"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('arcadium/youtube-videos').call({
  searchQueries: ['formula 1 highlights'],
  maxResults: 50,
  listingOnly: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.rowType === 'video').length, 'videos');
```

### Pricing

You pay per video or Short delivered, at the price of your Apify plan. Error rows, repeats and videos outside your date range are free.

| Apify plan | Per 1,000 videos | Per 1,000 Shorts | Per 1,000 videos or Shorts in fast mode |
| --- | --- | --- | --- |
| Free | $3.60 | $3.60 | $1.17 |
| Starter (Bronze) | $2.70 | $2.70 | $0.90 |
| Scale (Silver) | $2.34 | $2.34 | $0.72 |
| Business (Gold) | $2.16 | $2.16 | $0.45 |
| Platinum | $1.35 | $1.35 | $0.27 |
| Diamond | $0.90 | $0.72 | $0.18 |

Plus a one-off $0.001 per GB of memory when a run starts. No extra fee for date filters.

Set a maximum cost per run in the Apify run options and the actor stops before it goes over.

### Related actors and data

- [YouTube Transcripts Scraper: Text, Timed Segments, SRT](https://apify.com/arcadium/youtube-transcripts)
- [Google Trends Scraper: Interest, Related Queries, Trending](https://apify.com/arcadium/google-trends)
- [Google News Scraper: Real Article URLs, Full Text](https://apify.com/arcadium/google-news)

### Use cases

- Channel tracking: every new video of your competitors' channels each day, with views and likes.
- Influencer research: recent output, average views and posting rhythm of a list of channels.
- Search monitoring: who ranks for your keywords on YouTube (`position`), and how that changes.
- Content analytics: categories, lengths, Shorts versus long videos, live streams.
- Shorts trends: which Shorts take off for a hashtag or a search term, and which sounds they use.
- Agents: "list the last N videos" before picking which ones to summarise (then use our YouTube Transcripts actor).

### Limitations

- Public videos only. Private, age-restricted and members-only videos need a signed-in account and are skipped (free error row).
- Views and likes are read at run time; YouTube updates them with some delay. Likes are null when the creator hides them.
- Fast-mode dates are estimates ("2 years ago" can be off by up to a year); full rows have the exact date.
- YouTube's search filters (upload date, sort by date) are loose; use `publishedAfter` for an exact range.
- Fast mode on a hashtag's Shorts tab gives only the video id and rounded views (YouTube shows no title there); full rows have everything.
- The sound is what YouTube's sound link shows ("Song · Artist"); Shorts with their original audio and no sound link have `soundTitle` null.
- Comment counts only, no comment text; no video or audio download, no subscriber lists.

### Privacy

Public data only, read the way an anonymous visitor's browser and the YouTube app read it. No login, no cookies, no account. Comments and viewer data are never collected. Emails and phone numbers in titles and descriptions are replaced with `[email]` and `[phone]`. The channel name is the publisher of the video.

### FAQ

**Why are some dates marked approximate?**
In fast mode the listing only says "3 weeks ago". Turn fast mode off (the default) for exact ISO dates.

**A channel returned nothing. Why?**
Look at its error row: `not_found` means the handle or playlist does not exist; a date range that matches no video gives `no_match`. These rows are free.

**How fast is it?**
Full rows: about 2 to 4 videos per second. Fast mode: about 30 videos per request, a few hundred videos in under 10 seconds.

**Something looks wrong in the output. Who do I tell?**
Open an issue on the actor page with the run link. We read them all.

# Actor input Schema

## `urls` (type: `array`):

Example: https://www.youtube.com/@kurzgesagt (add /shorts or /streams for those tabs), @handle, a channel id (UC...), a playlist URL (https://www.youtube.com/playlist?list=...), a video or Shorts URL, a hashtag page (https://www.youtube.com/hashtag/cooking, add /shorts for Shorts only), a bare video id (dQw4w9WgXcQ) or a search page URL (https://www.youtube.com/results?search_query=lofi). Channels give up to maxResults videos each, newest first by default.

## `searchQueries` (type: `array`):

YouTube search terms, e.g. \["web scraping tutorial", "iphone 17 review"]. Each term gives up to maxResults videos in YouTube's search order (position is the rank). Can be combined with urls.

## `hashtags` (type: `array`):

YouTube hashtags, e.g. \["#cooking", "dancechallenge"] (letters, digits and \_ only). Each gives up to maxResults videos from the hashtag page; with contentType shorts only its Shorts. Can be combined with urls and searchQueries.

## `contentType` (type: `string`):

Values: videos (default), shorts. With shorts, search terms return Shorts only and hashtags read their Shorts tab. Channels: use maxResultsShorts or a /shorts channel URL. Full Shorts rows add commentCount, soundTitle and soundArtist and are charged as the short event.

## `maxResults` (type: `integer`):

Videos per channel videos tab, playlist or search term, up to 10,000 (default 50). Videos outside the date range, private or members-only videos do not count. Direct video URLs are not limited by this.

## `maxResultsShorts` (type: `integer`):

Also read the channel's Shorts tab, up to this many Shorts per channel (0 = skip the tab, default). A /shorts channel URL reads only that tab.

## `maxResultStreams` (type: `integer`):

Also read the channel's Live tab (past, running and scheduled streams), up to this many per channel (0 = skip the tab, default).

## `maxItems` (type: `integer`):

Stops the run after this many videos across all inputs, 1 to 1,000,000. A cost cap; leave empty for no limit.

## `publishedAfter` (type: `string`):

Date as YYYY-MM-DD (e.g. 2026-01-01) or a relative age such as "30 days", "2 weeks", "1 year". Older videos are skipped for free; a channel listed newest first stops at the first older videos.

## `publishedBefore` (type: `string`):

Date as YYYY-MM-DD (e.g. 2026-06-30) or a relative age such as "7 days". Newer videos are skipped for free.

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

Values: NEWEST (default), POPULAR (most viewed first), OLDEST. Applies to channel tabs. The date-range early stop works with NEWEST and OLDEST.

## `listingOnly` (type: `boolean`):

Skips the per-video details request and returns what the channel, playlist or search listing shows: title, rounded views, duration, approximate date (publishDateIsApprox true), channel. No likes, category, description or caption languages. Charged at the lower video-listing price. Off by default.

## `includeCaptionLanguages` (type: `boolean`):

Adds captionLanguages (every caption track: code, name, auto-generated flag) and hasCaptions. One more small request per video. On by default; ignored in fast mode.

## `includeCommentCount` (type: `boolean`):

Adds commentCount to regular videos (one more small request each). Shorts always get it. Ignored in fast mode. Off by default.

## `description` (type: `string`):

Values: full (default), short (about 300 characters), none. Emails and phone numbers are removed.

## `sortingOrder` (type: `string`):

Values: relevance (default), rating, date (upload date), views. Applies to searchQueries and search URLs.

## `searchCountry` (type: `string`):

Two-letter country code YouTube ranks search results for, e.g. DE or IN (default US). Applies to searchQueries and search URLs; titles and dates stay as published.

## `dateFilter` (type: `string`):

Values: hour, today, week, month, year. YouTube's own upload-date filter for searches; it is loose, so combine it with publishedAfter for an exact range.

## `lengthFilter` (type: `string`):

Values: under4 (under 4 minutes), between420 (4 to 20 minutes), plus20 (over 20 minutes).

## `isHD` (type: `boolean`):

Search results in HD only (YouTube's HD feature filter).

## `hasSubtitles` (type: `boolean`):

Search results that have subtitles or closed captions only.

## `hasCC` (type: `boolean`):

Search results under a Creative Commons licence only.

## `isLive` (type: `boolean`):

Search results that are live streams only.

## `is4K` (type: `boolean`):

Search results in 4K only (YouTube's 4K feature filter).

## `includeErrorRows` (type: `boolean`):

Adds a free row (rowType error, with a code and message) for every URL, private, age-restricted, members-only or missing video that gave no data. On by default.

## `maxRetries` (type: `integer`):

Advanced. How often a request is retried after a temporary error, 0 to 10.

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

Advanced. Leave empty: the actor starts on datacenter proxies and moves to residential when YouTube asks to confirm you are not a bot.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/@kurzgesagt"
  ],
  "searchQueries": [],
  "hashtags": [],
  "contentType": "videos",
  "maxResults": 10,
  "maxResultsShorts": 0,
  "maxResultStreams": 0,
  "includeCaptionLanguages": true,
  "includeCommentCount": false,
  "description": "full",
  "includeErrorRows": true,
  "maxRetries": 5
}
```

# Actor output Schema

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

All rows of this run in the default dataset.

## `view_overview` (type: `string`):

No description

## `view_shorts` (type: `string`):

No description

## `view_details` (type: `string`):

No description

## `view_errors` (type: `string`):

No description

## `record_stats` (type: `string`):

Requests, data volume, retries, proxy tiers and cost per video for this run.

# 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 = {
    "urls": [
        "https://www.youtube.com/@kurzgesagt"
    ],
    "searchQueries": [],
    "hashtags": [],
    "contentType": "videos",
    "maxResults": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("arcadium/youtube-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 = {
    "urls": ["https://www.youtube.com/@kurzgesagt"],
    "searchQueries": [],
    "hashtags": [],
    "contentType": "videos",
    "maxResults": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("arcadium/youtube-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 '{
  "urls": [
    "https://www.youtube.com/@kurzgesagt"
  ],
  "searchQueries": [],
  "hashtags": [],
  "contentType": "videos",
  "maxResults": 10
}' |
apify call arcadium/youtube-videos --silent --output-dataset

```

## MCP server setup

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