# YouTube Scraper & API Alternative - Videos, Shorts | $1/1k (`glasswing/youtube-scraper`) Actor

YouTube scraper and Data API alternative with no API key or quota: keyword search with filters, channel videos, Shorts and live streams, playlists and single videos. Exact views, likes, comment count, duration, upload date, hashtags and channel subscribers. $1 per 1,000 results.

- **URL**: https://apify.com/glasswing/youtube-scraper.md
- **Developed by:** [Raffy](https://apify.com/glasswing) (community)
- **Categories:** Videos, Social media, Developer tools
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

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

### What does YouTube Scraper do?

YouTube Scraper turns **YouTube searches, channels, Shorts, live streams, playlists and single videos into clean video data**: title, channel, subscribers, exact views, likes, comment count, duration, upload date, full description, hashtags and thumbnail. Type a keyword, paste a channel link, an @handle, a playlist or a video link, and a few seconds later you have one row per video as JSON, CSV or Excel.

It is a **YouTube Data API alternative with no API key and no quota**: no Google Cloud project, no OAuth, no daily unit budget, no browser. The Actor reads the same public data youtube.com shows to a logged-out visitor, so it is fast and costs **$1 per 1,000 videos**.

Five ways in, freely combined in one run:

- **Search YouTube** by keyword, with YouTube's own filters: upload date (last hour to this year), type (videos, Shorts, channels, playlists, movies), length (under 4, 4 to 20, over 20 minutes) and sort by relevance or view count. Each row keeps its `searchQuery` and `position`.
- **Scrape a channel**: its Videos, Shorts or Live tab, newest, most popular or oldest first, by @handle, channel link or channel ID.
- **Get a channel profile**: one row with the About data: description, subscribers, total videos, total views, join date, country and the links the creator lists.
- **Scrape a playlist** by link or ID, in playlist order.
- **Look up single videos or Shorts** by link or video ID.

It reads video and channel metadata only. It does **not** collect comments, commenter names, e-mail addresses or any other data about individual viewers.

### Use cases

- **Replace the YouTube Data API.** Get search results, channel uploads, playlist items and video statistics without an API key, quota units or a Google Cloud project. A 10,000-video job that would eat a day of API quota is one run here.
- **Channel monitoring.** Schedule a run on your own or competitors' channels (Latest order) and get every new upload with views, likes and comment counts; compare channels by subscribers and output.
- **Search and SEO research.** See which videos rank for a keyword, in which position, how old they are and how many views they get; filter by upload date to catch new content, or sort by views to find the evergreen hits.
- **Shorts research.** Pull a channel's Shorts tab or search Shorts only, with exact views, likes and upload dates.
- **Playlists and courses.** Export a playlist in order, with each video's length, views and upload date.
- **Live streams.** List a channel's past and current streams, see which ones are live right now (`isLive`) and which were streamed (`wasLive`).
- **Influencer and creator research.** Profile rows give subscribers, total views, total videos, join date, country and the creator's listed links for a shortlist of channels.
- **Datasets for analytics and AI.** Titles, descriptions, hashtags, durations and engagement numbers for thousands of videos, ready for a spreadsheet, a BI tool or a model.
- **Tools for AI agents.** Give an agent YouTube search and video lookup through the Apify MCP server, without an API key.
- **Automation.** Send results to Google Sheets, Airtable, Supabase, Slack or a CRM with Make, n8n or Zapier, or read them from the Apify API.

### Why use YouTube Scraper?

- **Cheap.** $1 per 1,000 results plus $0.005 per run, Apify platform usage included, with no extra charge for video details, filters or channel profiles.
- **Fast.** No browser. The default run finishes in well under a minute on Apify (10 to 40 seconds in our tests); a run with 3 search terms, 2 channels and 1 playlist saved 583 fully detailed videos in under 4 minutes.
- **Complete rows by default.** Search, channel and playlist rows are opened one by one to add exact views, likes, comment count, upload date, full description, hashtags and the channel's subscribers (switch **Include video details** off for list data only).
- **Honest results.** Every row carries `status` (`ok`, `not_found`, `error`). A deleted video, an unknown @handle or a missing playlist is a free `not_found` row, and the run still ends successfully.
- **Built for agents and automation.** Call it with no input and it returns data; paste links, @handles or bare IDs; schedule it, call it from the API or connect it to Google Sheets, Slack, Zapier or Make.

### What data can YouTube Scraper extract?

| Field | Type | Description |
|---|---|---|
| `type` | string | `video`, `short`, `channel` or `playlist` |
| `source` | string | How the row was found: `search`, `channel`, `playlist` or `video` (a link or ID you gave) |
| `title` | string | Video, channel or playlist title |
| `videoId` | string | YouTube video ID |
| `url` | string | Link to the video, Short, channel or playlist |
| `channelName` | string | Channel name |
| `channelId` | string | Channel ID (`UC...`) |
| `channelHandle` | string | Channel @handle |
| `channelUrl` | string | Channel link |
| `channelVerified` | boolean | Channel shows a verified badge |
| `subscriberCount` | integer | Channel subscribers as YouTube shows them (YouTube rounds, e.g. 15.1M) |
| `viewCount` | integer | Views (exact with video details) |
| `likeCount` | integer | Likes (exact, with video details) |
| `commentCount` | integer | Comments as YouTube shows them (with video details) |
| `duration` | string | Length as shown, e.g. `13:38` |
| `durationSeconds` | integer | Length in seconds |
| `publishedAt` | string | Upload date `YYYY-MM-DD` (with video details, and for single videos) |
| `publishedTimeText` | string | Relative time as shown, e.g. `3 days ago`, `Streamed 5 months ago` |
| `description` | string | Full description with video details (a search snippet without); personal email addresses and phone numbers in it read `[email]` / `[phone]` |
| `hashtags` | array | Hashtags above the title and in the description |
| `thumbnailUrl` | string | Thumbnail image |
| `isShort` | boolean | The video is a Short |
| `isLive` | boolean | Live right now |
| `wasLive` | boolean | A past live stream |
| `isUpcoming` | boolean | A scheduled live stream or premiere that has not started (`isLive` and `wasLive` are then `false`) |
| `isMembersOnly` | boolean | YouTube marks the video Members only |
| `commentsTurnedOff` | boolean | The creator turned comments off (with video details; left out when the page does not say) |
| `isAgeRestricted` | boolean | The logged-out watch page is age-gated (with video details; see Limitations) |
| `descriptionLinks` | array | Every link in the description as `{ url, text }` with the full target URL (with video details) |
| `searchQuery` | string | The search term that found the row |
| `position` | integer | 1-based position in the search, channel list or playlist |
| `inputUrl` | string | The link, @handle or ID you gave |
| `status` | string | `ok`, `not_found` or `error` (see below) |
| `error` | string | Reason when `status` is not `ok` |
| `scrapedAt` | string | ISO 8601 time of extraction |

Channel profile rows (`type: channel`) add `videoCount`, `channelViewCount`, `joinedDate`, `country`, `links` (title and URL of each link on the About panel), `avatarUrl` and `bannerUrl`. Channel search results (`type: channel`, `source: search`) carry the channel's name, handle, subscribers, description snippet and avatar. Playlist search results (`type: playlist`) carry `playlistId` and `videoCount`. `detailsError` appears only when the extra video-details request failed; the row then keeps its list data. Optional fields are left out when YouTube does not show them.

#### Result status (tri-state output)

| `status` | Meaning | Billed? |
|---|---|---|
| `ok` | The video, channel or playlist was found and extracted. | Yes |
| `not_found` | YouTube answered, but the video, @handle, channel tab or playlist does not exist, or a search has no results. | No |
| `error` | YouTube could not be read after retries. `error` says why. | No |

### How to scrape YouTube with YouTube Scraper

1. Open the Actor in Apify Console and click **Try for free**.
2. Paste links, @handles or IDs into **YouTube URLs, handles or IDs**, and/or type keywords into **Search terms**.
3. Set **Max results per search, channel or playlist** and **Maximum results (total)**. Start with the defaults to see the output.
4. Optional: pick search filters (upload date, type, length, sort, features such as 4K or subtitles), the channel tab (Videos, Shorts, Live, About), the channel order (Latest, Popular, Oldest), a date limit for channels and extra Shorts / live-stream caps.
5. Click **Start**. The default run takes well under a minute.
6. Open the **Output** tab or **Export** the dataset as JSON, CSV, Excel, XML or HTML.

To automate it, use the **API** tab (Node.js, Python, curl examples) or add a **Schedule**.

#### Which input finds what

| You give | You get |
|---|---|
| `web scraping tutorial` in Search terms | The top videos for that keyword, with `searchQuery` and `position` |
| `https://www.youtube.com/@TED`, `@TED`, `UCAuUUnT6oDeKwE6v1NGQxug` | TED's videos (or Shorts / Live with **Channel tab**) |
| `https://www.youtube.com/@NASA/shorts` | That channel's Shorts |
| `https://www.youtube.com/@NASA/streams` | That channel's past, current and scheduled live streams |
| `https://www.youtube.com/@NASA/about` or Channel tab `about` | One channel profile row |
| `https://www.youtube.com/playlist?list=PL...` or `PL...` | The playlist's videos in order |
| `https://youtu.be/...`, a bare 11-character video ID, a `/shorts/` or `/live/` link | That one video with full details |
| `https://www.youtube.com/results?search_query=...` | The same search, including the filters in its `sp=` parameter |

`m.youtube.com` and `music.youtube.com` links work too.

### Input examples

Every field is optional; a call with no input scrapes the built-in example channel. One example per mode:

**Search** (newest videos for a keyword, this month, sorted by views):

```json
{
    "searchQueries": ["web scraping tutorial", "python web scraping"],
    "uploadDate": "month",
    "sortBy": "views",
    "maxResultsPerSource": 50,
    "maxItems": 100
}
```

**Channel uploads** (a channel's 100 most popular videos):

```json
{
    "startUrls": ["https://www.youtube.com/@TED"],
    "channelSort": "popular",
    "maxResultsPerSource": 100,
    "maxItems": 100
}
```

**Shorts** (a channel's latest Shorts, plus Shorts from a search):

```json
{
    "startUrls": ["https://www.youtube.com/@NASA/shorts"],
    "searchQueries": ["space facts"],
    "searchType": "shorts",
    "maxResultsPerSource": 30,
    "maxItems": 60
}
```

**Live streams** (a channel's past, current and scheduled streams):

```json
{
    "startUrls": ["https://www.youtube.com/@NASA"],
    "channelTab": "streams",
    "maxResultsPerSource": 20,
    "maxItems": 20
}
```

**Playlist** (every video in order):

```json
{
    "startUrls": ["https://www.youtube.com/playlist?list=PLWKjhJtqVAbnqBxcdjVGgT3uVR10bzTEB"],
    "maxResultsPerSource": 500,
    "maxItems": 500
}
```

**Single videos** (links or IDs, one row each):

```json
{
    "startUrls": ["https://www.youtube.com/watch?v=Ke6XX8FHOHM", "https://youtu.be/HGOBQPFzWKo", "https://www.youtube.com/shorts/-62C9R_41Sc"],
    "maxItems": 10
}
```

**Channel profiles** (one About row per channel, no videos):

```json
{
    "startUrls": ["@NASA", "@TED", "@freecodecamp"],
    "channelTab": "about",
    "maxItems": 3
}
```

**AI agents** (one search string, nothing else):

```json
{ "query": "artemis moon launch", "maxItems": 10 }
```

#### Input fields

See the **Input** tab for the full schema. The main options:

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array of strings | `["https://www.youtube.com/@TED"]` | Video, Short, channel, @handle, playlist or search-result links, or bare IDs |
| `searchQueries` | array of strings | - | Keywords to search |
| `maxResultsPerSource` | integer | `10` | Cap per search term, channel and playlist (up to 5,000) |
| `maxItems` | integer | `20` | Cap for the whole run |
| `includeVideoDetails` | boolean | `true` | Open each video for exact views, likes, comments, date, description, hashtags |
| `includeChannelInfo` | boolean | `false` | Add one channel profile row per channel |
| `channelTab` | string | `videos` | `videos`, `shorts`, `streams` or `about` |
| `channelSort` | string | `newest` | `newest`, `popular` or `oldest` |
| `searchType` | string | `video` | `video`, `shorts`, `channel`, `playlist`, `movie` or `all` |
| `uploadDate` | string | `any` | `hour`, `today`, `week`, `month`, `year` |
| `videoDuration` | string | `any` | `short` (under 4 min), `medium` (4 to 20), `long` (over 20) |
| `sortBy` | string | `relevance` | `relevance` or `views` |
| `searchFeatures` | array of strings | - | Only results with all these features: `live`, `4k`, `hd`, `subtitles`, `creativeCommons`, `360`, `vr180`, `hdr`, `location` |
| `oldestPostDate` | string | - | Channel runs: only items published on or after this date, e.g. `2026-01-31` or `30 days` |
| `maxShorts` | integer | - | Channel runs: also read this many Shorts per channel (caps the Shorts tab when it is the chosen tab) |
| `maxStreams` | integer | - | Channel runs: also read this many Live-tab items per channel (caps the Live tab when it is the chosen tab) |
| `query` | string | - | A single search term (same as one entry in `searchQueries`) |
| `proxyConfiguration` | object | Apify Proxy on (datacenter) | Residential proxies are not needed |

The search filters apply to **Search terms** and to `youtube.com/results` links without their own `sp=` filter. If you call the Actor with search terms only, the built-in example channel is skipped.

### Output

You can download the dataset as JSON, CSV, Excel, XML or HTML, or read it from the API. Real rows from a run on 2026-10-16, one per row type (descriptions and thumbnail links shortened):

**Video from a search** (`source: search`):

```json
{
    "url": "https://www.youtube.com/watch?v=Ke6XX8FHOHM",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:40:27.967Z",
    "type": "video",
    "source": "search",
    "searchQuery": "nasa artemis",
    "position": 2,
    "title": "Artemis II to the Moon: Launch to Splashdown (NASA Mission Animation)",
    "videoId": "Ke6XX8FHOHM",
    "channelName": "NASA",
    "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
    "channelHandle": "@NASA",
    "channelUrl": "https://www.youtube.com/@NASA",
    "channelVerified": true,
    "subscriberCount": 15100000,
    "viewCount": 2865942,
    "likeCount": 37322,
    "duration": "7:45",
    "durationSeconds": 465,
    "publishedAt": "2025-02-10",
    "publishedTimeText": "1 year ago",
    "isLive": false,
    "wasLive": false,
    "isShort": false,
    "description": "The Artemis II mission, slated to launch early 2026, will fly four astronauts around the Moon. This mission wi ...",
    "thumbnailUrl": "https://i.ytimg.com/vi/Ke6XX8FHOHM/hqdefault.jpg"
}
```

**Video from a playlist** (`source: playlist`; channel videos look the same with `source: channel`):

```json
{
    "url": "https://www.youtube.com/watch?v=HGOBQPFzWKo",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:33:11.944Z",
    "type": "video",
    "source": "playlist",
    "inputUrl": "https://www.youtube.com/playlist?list=PLWKjhJtqVAbnqBxcdjVGgT3uVR10bzTEB",
    "position": 2,
    "title": "Intermediate Python Programming Course",
    "videoId": "HGOBQPFzWKo",
    "channelName": "freeCodeCamp.org",
    "channelId": "UC8butISFwT-Wl7EV0hUK0BQ",
    "channelHandle": "@freecodecamp",
    "channelUrl": "https://www.youtube.com/@freecodecamp",
    "channelVerified": true,
    "subscriberCount": 11900000,
    "viewCount": 4530932,
    "likeCount": 92866,
    "commentCount": 2000,
    "duration": "5:55:47",
    "durationSeconds": 21347,
    "publishedAt": "2020-08-27",
    "publishedTimeText": "6 years ago",
    "isLive": false,
    "wasLive": false,
    "isShort": false,
    "description": "Take your Python skills to the next level with this intermediate Python course. First, you ...",
    "thumbnailUrl": "https://i.ytimg.com/vi/HGOBQPFzWKo/hqdefault.jpg"
}
```

**Short** (`type: short`, from a channel's Shorts tab):

```json
{
    "url": "https://www.youtube.com/shorts/-62C9R_41Sc",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:35:44.528Z",
    "type": "short",
    "source": "channel",
    "inputUrl": "https://www.youtube.com/@NASA/shorts",
    "position": 1,
    "title": "From the Moon to the 50-Yard Line",
    "videoId": "-62C9R_41Sc",
    "channelName": "NASA",
    "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
    "channelHandle": "@NASA",
    "channelUrl": "https://www.youtube.com/@NASA",
    "channelVerified": true,
    "subscriberCount": 15100000,
    "viewCount": 46003,
    "likeCount": 858,
    "publishedAt": "2026-10-05",
    "publishedTimeText": "10 days ago",
    "isShort": true,
    "isLive": false,
    "wasLive": false,
    "description": "From the Moon to the 50-yard line. ...",
    "hashtags": ["#FlyEaglesFly", "#ArtemisII", "#MoonJoy"],
    "thumbnailUrl": "https://i.ytimg.com/vi/-62C9R_41Sc/hqdefault.jpg"
}
```

**Live stream** (a past stream from a channel's Live tab; a stream that is on air right now has `isLive: true` and no `viewCount`, because YouTube shows current viewers instead):

```json
{
    "url": "https://www.youtube.com/watch?v=ivng4U5xeYM",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:40:27.852Z",
    "type": "video",
    "source": "channel",
    "inputUrl": "https://www.youtube.com/@NASA/streams",
    "position": 4,
    "title": "NASA’s SpaceX Crew-12 Postflight News Conference (Oct. 15, 2026)",
    "videoId": "ivng4U5xeYM",
    "channelName": "NASA",
    "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
    "channelHandle": "@NASA",
    "channelUrl": "https://www.youtube.com/@NASA",
    "channelVerified": true,
    "subscriberCount": 15100000,
    "viewCount": 52470,
    "likeCount": 820,
    "duration": "39:56",
    "durationSeconds": 2396,
    "publishedTimeText": "Streamed 17 hours ago",
    "isLive": false,
    "wasLive": true,
    "isShort": false,
    "description": "After spending over seven months in space, members of NASA’s SpaceX Crew-12 will discuss their mission aboard ...",
    "thumbnailUrl": "https://i.ytimg.com/vi/ivng4U5xeYM/hqdefault.jpg"
}
```

**Single video lookup** (`source: video`, from a link or ID you gave):

```json
{
    "url": "https://www.youtube.com/watch?v=Ke6XX8FHOHM",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:42:28.285Z",
    "type": "video",
    "source": "video",
    "inputUrl": "https://www.youtube.com/watch?v=Ke6XX8FHOHM",
    "position": 1,
    "title": "Artemis II to the Moon: Launch to Splashdown (NASA Mission Animation)",
    "videoId": "Ke6XX8FHOHM",
    "channelName": "NASA",
    "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
    "channelHandle": "@NASA",
    "channelUrl": "https://www.youtube.com/@NASA",
    "channelVerified": true,
    "subscriberCount": 15100000,
    "viewCount": 2865943,
    "likeCount": 37322,
    "publishedAt": "2025-02-10",
    "publishedTimeText": "1 year ago",
    "isLive": false,
    "wasLive": false,
    "description": "The Artemis II mission, slated to launch early 2026, will fly four astronauts around the Moon. This mission wi ...",
    "thumbnailUrl": "https://i.ytimg.com/vi/Ke6XX8FHOHM/hqdefault.jpg"
}
```

**Channel profile** (`type: channel`, from Channel tab `about` or **Add one channel profile row per channel**):

```json
{
    "url": "https://www.youtube.com/@NASA",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:33:11.656Z",
    "type": "channel",
    "source": "channel",
    "inputUrl": "https://www.youtube.com/@NASA/about",
    "position": 1,
    "title": "NASA",
    "channelName": "NASA",
    "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
    "channelHandle": "@NASA",
    "channelUrl": "https://www.youtube.com/@NASA",
    "channelVerified": true,
    "description": "NASA's mission is to pioneer the future in space exploration, scientific discovery, and ae ...",
    "subscriberCount": 15100000,
    "videoCount": 6191,
    "channelViewCount": 1128867209,
    "joinedDate": "2008-06-03",
    "links": [
        { "title": "nasa.gov", "url": "https://www.nasa.gov" },
        { "title": "X", "url": "https://x.com/nasa" }
    ],
    "avatarUrl": "https://yt3.googleusercontent.com/eIf5fNPcIcj9...=s900-c-k-c0x00ffffff-no-rj",
    "bannerUrl": "https://yt3.googleusercontent.com/y8Srh-oCcilL...=w2560-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj"
}
```

**Channel from a search** (`searchType: channel`):

```json
{
    "url": "https://www.youtube.com/@SpaceAgency",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:35:13.899Z",
    "type": "channel",
    "source": "search",
    "searchQuery": "space agency",
    "position": 1,
    "title": "Space.Agency",
    "channelName": "Space.Agency",
    "channelId": "UCR1qqnuREBQ5zCgUbuWdfsQ",
    "channelHandle": "@SpaceAgency",
    "channelUrl": "https://www.youtube.com/@SpaceAgency",
    "subscriberCount": 53600,
    "description": "Space.Agency is a marketplace of commercial space companies, products and services. ...",
    "avatarUrl": "https://yt3.ggpht.com/PCzuZSg3l8Ul...=s176-c-k-c0x00ffffff-no-rj-mo"
}
```

**Playlist from a search** (`searchType: playlist`):

```json
{
    "url": "https://www.youtube.com/playlist?list=PLWKjhJtqVAbnqBxcdjVGgT3uVR10bzTEB",
    "status": "ok",
    "scrapedAt": "2026-10-16T12:27:27.049Z",
    "type": "playlist",
    "source": "search",
    "searchQuery": "python tutorial",
    "position": 2,
    "title": "Python Tutorials",
    "playlistId": "PLWKjhJtqVAbnqBxcdjVGgT3uVR10bzTEB",
    "channelName": "freeCodeCamp.org",
    "channelId": "UC8butISFwT-Wl7EV0hUK0BQ",
    "channelHandle": "@freecodecamp",
    "channelUrl": "https://www.youtube.com/@freecodecamp",
    "videoCount": 12,
    "thumbnailUrl": "https://i.ytimg.com/vi/eWRfhZUzrAc/hq720.jpg?sqp=..."
}
```

**Not found** (an unknown @handle; free, and the run still succeeds):

```json
{
    "url": "https://www.youtube.com/youtubei/v1/navigation/resolve_url?prettyPrint=false&ytk=resolve&ytin=https%3A%2F%2Fwww.youtube.com%2F%40thisdoesnotexist12345xyz&yto=0&ytl=2",
    "status": "not_found",
    "error": "HTTP 404: page does not exist",
    "scrapedAt": "2026-10-16T12:33:11.353Z"
}
```

For `not_found` and `error` rows, `url` is the request that answered, with the link you gave inside it.

### How much does it cost to scrape YouTube?

This Actor uses **pay-per-event** pricing, with Apify platform usage and proxies included:

| Event | Price |
|---|---|
| Actor start | $0.005 per run (at the default 1 GB memory) |
| Result (`status: ok` row: a video, Short, channel or playlist) | $0.001 per result |

What typical jobs cost:

| Job | Results | Cost |
|---|---|---|
| Default run (try it) | 20 | $0.025 |
| Top 100 videos for one keyword | 100 | $0.105 |
| A channel's 1,000 latest videos | 1,000 | $1.005 |
| 50 channel profiles | 50 | $0.055 |
| 10,000 videos from many searches and channels in one run | 10,000 | $10.005 |
| Daily check of 10 channels, 20 newest videos each, for 30 days | 6,000 | about $6.15 |

Rows with `status` `not_found` or `error` are never billed, and video details, filters and channel profiles cost nothing extra. The free Apify plan's monthly credit covers a few thousand videos. You can cap spending per run with **Maximum results** and in the run's **Max total charge** option; the Actor stops cleanly when the limit is reached. The start fee is per GB of memory, so keep the default 1 GB (the Actor does not need more).

### Use the YouTube Scraper API

Every run is available through the [Apify API](https://docs.apify.com/api/v2). Get your token from **Settings > API & Integrations** in Apify Console.

**JavaScript / Node.js** (`npm install apify-client`):

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('glasswing/youtube-scraper').call({
    searchQueries: ['web scraping tutorial'],
    uploadDate: 'month',
    maxResultsPerSource: 50,
    maxItems: 50,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const video of items.filter((row) => row.status === 'ok')) {
    console.log(video.title, video.viewCount, video.url);
}
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("glasswing/youtube-scraper").call(run_input={
    "startUrls": ["https://www.youtube.com/@NASA"],
    "channelSort": "newest",
    "maxResultsPerSource": 50,
    "maxItems": 50,
})

for video in client.dataset(run["defaultDatasetId"]).iterate_items():
    if video["status"] == "ok":
        print(video["title"], video.get("viewCount"), video["url"])
```

**One HTTP call** (runs the Actor and returns the rows, for runs under 5 minutes):

```bash
curl -X POST "https://api.apify.com/v2/acts/glasswing~youtube-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "artemis moon launch", "maxItems": 10}'
```

**AI agents and MCP.** Add the Actor to Claude, Cursor, VS Code or any MCP client through the Apify MCP server at `https://mcp.apify.com?tools=glasswing/youtube-scraper`. The agent can call it with a single `query` string or a list of links; every row says whether it is real data (`ok`), missing (`not_found`) or a failure (`error`), so the agent never mistakes an empty result for a broken tool.

### Export and join

**Export 10,000+ videos in one run.** Put all your search terms, channels and playlists into one input, set **Max results per search, channel or playlist** (up to 5,000) and **Maximum results** to the total you want, and start one run. A 10,000-video export costs $10.005 and pays the start fee once. When it finishes, download the dataset as CSV, Excel, JSON, JSONL, XML or HTML from the run's **Output** tab, or page through it with the API:

```bash
## CSV with only the columns you need, 1,000 rows per page (raise offset for the next page)
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?format=csv&clean=true&fields=videoId,title,channelId,channelName,viewCount,likeCount,commentCount,durationSeconds,publishedAt,status&limit=1000&offset=0&token=YOUR_APIFY_TOKEN"
```

**Keys for joining rows.** Every row carries stable YouTube IDs, so you can merge runs and tables without matching on titles:

| Key | Use it to |
|---|---|
| `videoId` | Deduplicate (the same video can come back from a search and from a channel in one run), join daily snapshots to track views and likes over time, and join to the comments and transcript Actors below. |
| `channelId` | Join videos to their channel profile rows (`type: channel`, from **Add one channel profile row per channel**) for subscribers, total views and country. |
| `searchQuery` + `position` | Track where a video ranks for a keyword from run to run. |
| `scrapedAt` | Tell snapshots apart when you append scheduled runs to one table. |

**Join with comments or transcripts.** Both Actors accept bare video IDs and return `videoId` on every row:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("glasswing/youtube-scraper").call(run_input={
    "searchQueries": ["home espresso machine review"],
    "maxResultsPerSource": 200,
    "maxItems": 200,
})
videos = [v for v in client.dataset(run["defaultDatasetId"]).iterate_items() if v["status"] == "ok"]
ids = list(dict.fromkeys(v["videoId"] for v in videos))  # deduplicated, order kept

t_run = client.actor("glasswing/youtube-transcript-scraper").call(run_input={"startUrls": ids[:50]})
transcripts = {row["videoId"]: row for row in client.dataset(t_run["defaultDatasetId"]).iterate_items()}

for v in videos[:50]:
    print(v["videoId"], v["viewCount"], v["videoId"] in transcripts)
```

In a spreadsheet, the same join is one lookup on the `videoId` column (`XLOOKUP` in Excel, `VLOOKUP` in Google Sheets). Filter on `status = ok` first: `not_found` and `error` rows are free and carry no video data.

### Tips

- Keep `maxItems` small while testing, then raise it.
- Put many search terms, channels and playlists into one run instead of many single runs; you pay the start fee once.
- Switch **Include video details** off when you only need titles, links, durations and rounded view counts: runs need one request per 20 videos instead of one per video (the price stays the same).
- For a channel's most viewed videos use **Channel sort order: Popular**; for new uploads keep **Latest** and schedule the run.
- Need only 4K, HD, subtitled, Creative Commons, live, 360°, VR180, HDR or geotagged results? Pick them under **Features** (`searchFeatures`). For any other filter combination, apply it on youtube.com, copy the results page link and paste it into **YouTube URLs**: its `sp=` filter is kept.
- Monitoring channels? Set **Only items published since** (`oldestPostDate`, e.g. `7 days`) with **Latest** order: each channel's list stops at the first older video, so a scheduled run only pays for new uploads.
- Want a channel's videos, Shorts and streams in one run? Keep **Channel tab** on Videos and set **Max Shorts per channel** and **Max live streams per channel**.

### Limitations

- Video tags (keywords), category and captions/transcripts are not included: YouTube only serves them to its video player, which refuses requests from cloud servers.
- Comments and commenter data are not collected.
- `subscriberCount` and `commentCount` are YouTube's rounded figures (e.g. 15.1M, 2K); likes and views are exact with video details on.
- Shorts rows usually have no `duration`: YouTube's Shorts lists do not carry it. Single video lookups get it from YouTube's search when the video page does not show it (one extra request, same price); a video that search does not find yet keeps the row without `duration`.
- A video credited to several channels at once ("Channel A and Channel B") reports the first, main channel in the channel fields.
- Without video details, channel and playlist rows carry YouTube's rounded view counts and a relative upload time only; search rows carry exact views.
- Live streams that are on air have no `viewCount`. Scheduled streams and premieres have `isUpcoming: true` and no `viewCount`; with video details `publishedAt` is then the scheduled date. For a stream that ended a few hours ago YouTube shows only "Streamed 7 hours ago", so `publishedAt` is computed from that (UTC date).
- `oldestPostDate` works from YouTube's relative upload times in channel lists ("3 weeks ago"), which YouTube rounds down, so a few items from just before the date can remain; filter on `publishedAt` (with video details) for an exact cut. Shorts lists carry no upload time and are not filtered by date.
- YouTube search returns a few hundred results per keyword at most; use several related keywords for more. Each channel or playlist is capped at 5,000 rows per run.
- Private videos are not available. Age-restricted and members-only videos come back with their public page data only, flagged by `isAgeRestricted` and `isMembersOnly`. YouTube sends no explicit age-gate flag to logged-out visitors; `isAgeRestricted` is `true` when the watch page shows neither recommendations nor a comment section, which is how YouTube serves age-gated videos.
- When YouTube changes its pages, rows come back as `error` and the Actor is updated quickly (report it in the **Issues** tab).

### FAQ

#### Do I need a YouTube API key or a Google account?

No. There is no API key, no OAuth, no Google Cloud project and no login. The Actor reads the public data youtube.com shows to any logged-out visitor.

#### Is there a quota like the YouTube Data API?

No. The YouTube Data API gives a project 10,000 quota units per day and charges 100 units per search call. This Actor has no daily quota; you pay per result and set your own limits with **Maximum results**.

#### How many videos can I scrape?

Up to 5,000 per channel or playlist per run, and as many channels, playlists and search terms per run as you like. YouTube itself stops a single keyword search after a few hundred results.

#### Which fields do I get, and which not?

Title, IDs, links, channel, subscribers, views, likes, comment count, duration, upload date, description with its full links, hashtags, thumbnail, and live/upcoming/Short/members-only/age-restricted/comments-off flags for videos; About data for channels. Not included: tags, category, captions, comments and dislikes (YouTube no longer shows dislikes).

#### How fresh is the data?

Live. Every run reads YouTube at that moment; nothing is cached. `scrapedAt` shows when each row was read. Schedule the Actor to track changes over time.

#### Is it legal to scrape YouTube?

Scraping publicly available data is generally legal, but you are responsible for how you use the output. Read the legal notice below and YouTube's terms of service, and do not collect personal data without a legitimate reason.

#### How do I get a channel's most popular videos?

Paste the channel link or @handle, set **Channel sort order** to **Popular** and **Max results per search, channel or playlist** to the number you want.

#### How do I get a channel's subscribers, total views and links?

Turn on **Add one channel profile row per channel**, or set **Channel tab** to **About** for the profile row only.

#### Can it scrape Shorts and live streams?

Yes: a channel's Shorts tab (a `/shorts` channel link or **Channel tab: Shorts**), Shorts in search (**Search result type: Shorts**), single `/shorts/` links, and a channel's Live tab (a `/streams` channel link or **Channel tab: Live**). To get a channel's videos, Shorts and streams in one run, set **Max Shorts per channel** and **Max live streams per channel**. Scheduled streams are marked `isUpcoming`.

#### Why is a row `not_found`?

The video was removed or made private, the @handle does not exist, the channel has no Shorts or Live tab, or the search had no results. `not_found` rows are free.

#### Do I need proxies?

No setup needed. Apify's datacenter proxy is on by default and included in the price; residential proxies are not needed.

#### Can I use this Actor from an AI agent or MCP client?

Yes. Every row is self-describing (`status` + `error`), inputs are plain strings, links, @handles or IDs, and a call with no input returns data. Connect it through the Apify MCP server (see **Use the YouTube Scraper API** above) or read results through the dataset API.

#### Why did I get fewer rows than `maxItems`?

Each search term, channel and playlist stops at **Max results per search, channel or playlist**, a search can run out of results, or the run hit your **Max total charge** limit.

### Related Actors

- [Google Trends Scraper](https://apify.com/glasswing/google-trends-scraper) - use it to see whether a topic is rising before you research the videos that rank for it.
- [Keyword Suggestions API](https://apify.com/glasswing/keyword-suggestions-scraper) - use it to expand one seed keyword into hundreds of long-tail searches to run through YouTube Scraper.
- [Google News Scraper](https://apify.com/glasswing/google-news-scraper) - use it to read the news coverage behind a trending video topic.
- [Pinterest Scraper](https://apify.com/glasswing/pinterest-scraper) - use it to compare the same topic's pins and boards with its YouTube videos.
- [Substack Scraper](https://apify.com/glasswing/substack-scraper) - use it to follow creators who publish both newsletters and videos.
- [Telegram Channel Scraper](https://apify.com/glasswing/telegram-channel-scraper) - use it to monitor public Telegram channels next to the YouTube channels you track.
- [YouTube Transcript Scraper](https://apify.com/glasswing/youtube-transcript-scraper) - use it to pull the full captions of the videos you find, as timed segments or plain text.
- [YouTube Comments Scraper](https://apify.com/glasswing/youtube-comments-scraper) - use it to read what viewers say under the videos, with likes and reply threads.

### Legal and data-protection notice

This Actor extracts only data that YouTube publishes publicly to logged-out visitors; it does not extract private user data such as e-mail addresses, phone numbers, gender or precise location, it does not collect comments or commenter names, and it does not log in or get around access controls. Channel names, @handles and the links a creator lists on their channel are public business information, but your results could still contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you are unsure whether your reason is legitimate, consult your lawyers. You are responsible for complying with YouTube's terms of service and applicable law when using the extracted data.

This Actor is an independent tool and is not affiliated with, endorsed by or sponsored by YouTube, Google or their owners. All trademarks belong to their respective owners.

# Changelog

This Actor's version history is a separate document: https://apify.com/glasswing/youtube-scraper/changelog.md

# Actor input Schema

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

One per line. Accepted: video links (watch?v=, youtu.be, /shorts/, /live/), channel links (@handle, /channel/UC..., /c/, /user/, optionally ending in /videos, /shorts, /streams or /about), bare @handles, video IDs, channel IDs (UC...), playlist links or IDs (PL...), and youtube.com/results?search_query=... pages (their own filters are kept). m.youtube.com and music.youtube.com links work too. If you call the Actor with search terms only, the built-in example channel is skipped automatically.

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

Keywords to search on YouTube, one per line, exactly as you would type them in the YouTube search box. Each term returns up to 'Max results per search, channel or playlist' rows. The options under 'Search filters' apply to these terms. A call that sends only search terms does not also scrape the built-in example channel.

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

Stop after this many rows have been saved in total. Each saved row with status `ok` is one billable result ($1 per 1,000); rows with status `not_found` or `error` are free. Keep it small for test runs.

## `maxResultsPerSource` (type: `integer`):

Cap for each search term, each channel and each playlist (up to 5,000). YouTube pages through about 20 results per request, and a single keyword search stops after a few hundred results.

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

For every video and Short found in a search, channel or playlist, also open the video to add the exact view count, like count, comment count, upload date, full description, hashtags and the channel's subscriber count. One extra request per video; the price per result stays the same. Switch it off for faster list-only runs (rounded view counts, relative upload time).

## `includeChannelInfo` (type: `boolean`):

For each channel you give, also save one row with the channel's About data: description, subscribers, total videos, total views, join date, country, the links the creator lists, avatar and banner. The profile row is one result.

## `channelTab` (type: `string`):

Which list to read from a channel link that does not already end in /videos, /shorts, /streams or /about. 'about' returns only the channel profile row.

## `channelSort` (type: `string`):

Order of a channel's videos, Shorts or streams, the same chips YouTube shows on the channel page.

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

Channel runs only: skip videos, Shorts and streams published before this date, given as a date (2026-01-31) or relative (30 days, 2 weeks, 6 months). Leave empty for no date limit. Channel lists show YouTube's relative upload time ("3 weeks ago"), so a few rows from just before the date can stay in; filter on publishedAt for an exact cut. Shorts lists carry no upload time and are not filtered. With Latest order the channel's list stops at the first older item.

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

Channel runs only: also read this many Shorts from each channel's Shorts tab, in addition to the tab chosen above. When 'Channel tab' is Shorts, this replaces 'Max results per search, channel or playlist' for it. Leave empty or 0 for today's behaviour.

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

Channel runs only: also read this many items from each channel's Live tab (past, current and scheduled streams), in addition to the tab chosen above. When 'Channel tab' is Live, this replaces 'Max results per search, channel or playlist' for it. Leave empty or 0 for today's behaviour.

## `searchType` (type: `string`):

What a search returns. 'all' mixes videos, Shorts, channels and playlists as YouTube shows them. The search filters apply to Search terms and to youtube.com/results links that carry no filter of their own.

## `uploadDate` (type: `string`):

Only videos uploaded within this period (YouTube's own filter).

## `videoDuration` (type: `string`):

YouTube's length filter: under 4 minutes, 4 to 20 minutes, or over 20 minutes.

## `sortBy` (type: `string`):

Relevance (YouTube's default) or view count.

## `searchFeatures` (type: `array`):

YouTube's feature filters: only results that have all the chosen features. Applies to Search terms and to youtube.com/results links without a filter of their own.

## `query` (type: `string`):

Same as one entry in 'Search terms', for API callers and AI agents that send a single string. Sent on its own, it skips the built-in example channel.

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

Apify Proxy (datacenter, the default group) is on by default: YouTube starts refusing one server's requests after a few hundred video pages in a row, and spreading them over datacenter IPs keeps large runs clean. Residential proxies are not needed. Switch it off for small runs if you prefer.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.youtube.com/@TED/videos"
  ],
  "searchQueries": [
    "web scraping tutorial"
  ],
  "maxItems": 20,
  "maxResultsPerSource": 10,
  "includeVideoDetails": true,
  "includeChannelInfo": false,
  "channelTab": "videos",
  "channelSort": "newest",
  "searchType": "video",
  "uploadDate": "any",
  "videoDuration": "any",
  "sortBy": "relevance",
  "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 = {
    "startUrls": [
        "https://www.youtube.com/@TED/videos"
    ],
    "searchQueries": [
        "web scraping tutorial"
    ],
    "maxItems": 20,
    "maxResultsPerSource": 10,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("glasswing/youtube-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/@TED/videos"],
    "searchQueries": ["web scraping tutorial"],
    "maxItems": 20,
    "maxResultsPerSource": 10,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("glasswing/youtube-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/@TED/videos"
  ],
  "searchQueries": [
    "web scraping tutorial"
  ],
  "maxItems": 20,
  "maxResultsPerSource": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call glasswing/youtube-scraper --silent --output-dataset

```

## MCP server setup

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