# YouTube Scraper (`dami_studio/youtube-scraper`) Actor

YouTube scraper for search results or a channel's newest uploads. Each row has the videoId, title, watch URL, channel, thumbnail, and the views, age and runtime exactly as YouTube prints them ("4 years ago", "34,811,276 views"). $0.135 per 1,000 videos.

- **URL**: https://apify.com/dami_studio/youtube-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Videos, Social media, Integrations
- **Features:** Limited permissions
- **Stats:** 20 total users, 9 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.13 / 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 scraper for search results, channel uploads, playlists and past live streams**: give it keywords, channels,
links, or all three, and you get one flat row per video with the id, title, watch link, channel, views, age,
runtime and thumbnail, plus the views and length as numbers.

The part that surprises people on run two: `viewCount` is exact on search rows, but rounded on channel rows, which say so in `viewCountIsApproximate`. Dates stay as `"4 years ago"`.

| | |
|---|---|
| **Input** | Keywords, channel links, `@handles`, `UC` ids, playlist links or YouTube search links |
| **Output** | One row per video: id, title, link, channel, views as text and number, age, runtime in seconds, thumbnail, hashtags, channel link and handle; channel rows add subscribers, total views, joined date and country |
| **Ceiling** | 1,000 videos per keyword, per channel and per playlist |
| **Account needed** | None |
| **Price** | $0.135 per 1,000 videos on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 37,000 |

### 🔍 What YouTube Scraper does

Three ways in, same row shape. A keyword runs a YouTube search, with any of YouTube's own filters. A channel
returns its newest uploads, or its most viewed or oldest, plus its Shorts and past streams if you ask. A link
returns that playlist or search. Mix them in one run and the rows come back together, with `mode` and
`sourceInput` telling you which produced each one.

Results are deduplicated by `videoId` across the whole run, so a video found twice is returned once and billed
once. `maxItems` is **per keyword, per channel and per playlist**, not per run.

### 📋 What data you get from each YouTube video

| What you get | Field |
|---|---|
| The 11 character video id and the watch link | `videoId`, `url` |
| Title and hashtags | `title`, `hashtags` |
| Who uploaded it | `channelName`, `channelId`, `channelUrl`, `channelHandle`, `channelVerified`, `channelAvatar`, `collaborators` |
| Views, as printed and as a number | `viewCountText`, `viewCount`, `viewCountIsApproximate` |
| Age and runtime | `publishedTimeText`, `lengthText`, `lengthSeconds` |
| What kind of row | `type` (video, live, upcoming, short, movie), `isLive`, `isMembersOnly`, `recordType`, `mode` |
| Which input produced it, and where | `sourceInput`, `position`, `channelTab` |
| The largest thumbnail on offer | `thumbnail` |
| Channel facts (channel rows, or search rows with **Add channel facts**) | `channelSubscriberCount`, `channelVideoCount`, `channelBanner`; channel rows also `channelTotalViews`, `channelJoinedDate`, `channelCountry`, `channelDescription`, `channelLinks` |
| Comment count add-on | `commentCountText`, `commentCount`, `commentCountIsApproximate`, `commentsTurnedOff` |

### ▶️ How to scrape YouTube videos

1. Open [YouTube Scraper](https://apify.com/dami_studio/youtube-scraper) and click **Try for free**.
2. Type keywords into **Search queries**, paste channels into **Channel URLs / handles**, or links into **YouTube links**.
3. Set **Max videos per query / channel**. Start at 50 while you look at the output.
4. Pick any filters or extras you want, then click **Start**.
5. Download the dataset as JSON, CSV or Excel, or read it over the Apify API.

Give it nothing to read and you get one uncharged row telling you so.

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

**$0.135 per 1,000 videos** on the Free plan. Paid plans pay a little less: $0.133 on Starter, $0.131 on Scale and $0.129 on Business. On the free plan, the $5
Apify gives you each month covers about 37,000 videos.

You pay per row delivered; the filters and **Add channel facts** cost nothing extra. A channel search bills each
channel row like a video row. Diagnostic rows, duplicates and rows a filter leaves out are never billed. The
comment count and channel profiles are optional add-ons, billed from 9 November 2026: on the Free plan a
comment count adds $0.125 per 1,000 videos, and a channel profile row costs $0.135 per 1,000; paid plans pay
a little less. Before that day both are free.

### 📥 What you give it

```json
{
  "searchQueries": ["lofi hip hop"],
  "channelUrls": ["https://www.youtube.com/@LofiGirl"],
  "startUrls": [{ "url": "https://www.youtube.com/playlist?list=PL6NdkXsPL07Il2hEQGcLI4dg_LTg7xA2L" }],
  "maxItems": 50,
  "uploadDate": "month",
  "channelSort": "popular"
}
```

| Field | Default | What it is |
|---|---|---|
| `searchQueries` | none | Keywords, one per line. The form opens with `lofi hip hop` as an example. |
| `channelUrls` | none | Channel links, `@handles` or bare `UC` ids. If one will not resolve, pass the full link or the `UC` id. |
| `startUrls` | none | Playlist links, search results links (their filters apply) or channel links. Upload a text file or a Google Sheet shared with anyone who has the link. Single video links are not read. |
| `maxItems` | `50` | Per keyword, channel and playlist, 0 to 1,000. Also your spend cap. 0 skips them, for runs that only want Shorts, streams or profiles. |
| `includeChannelFacts` | `false` | Subscriber count, video count, banner and handle on search and playlist rows, one lookup per channel. |
| `includeCommentCount` | `false` | Optional add-on: the comment count as YouTube shows it, and whether comments are off. |
| `channelProfilesOnly` | `false` | Optional add-on: one row per channel about the channel, no videos. |
| `uploadDate` | `any` | `hour`, `today`, `week`, `month` or `year`. Searches only. |
| `duration` | `any` | `short` (under 3 minutes), `medium` (3 to 20) or `long` (over 20). Live streams have no length, so they are left out. |
| `features` | none | Any of `hd`, `4k`, `live`, `subtitles`, `creativeCommons`, `360`, `vr180`, `3d`, `hdr`, `location`, `purchased`. All must match. |
| `searchType` | `videos` | `channels` gives one row per channel, `movies` the films YouTube sells or rents. |
| `searchCountry`, `searchLanguage` | `US`, `en` | Who the search is made for. In another language the numbers worked out from YouTube's text come back empty. |
| `channelSort` | `latest` | `popular` or `oldest`, for the Videos tab. |
| `publishedAfter` | none | A day like `2026-10-01` or `30 days`. Channel videos and streams only. |
| `maxStreams`, `maxShorts` | `0` | Past live streams and Shorts per channel, each with its own limit. |
| `notionConnector`, `notionParentId` | none | Optional. Write each video into a Notion database as well. |
| `proxyConfiguration` | none | Optional. Leave it empty unless you have your own servers you want the run to go out through. |

A filter value the run does not know is refused at the start with one free row, never ignored.

### 📤 What you get back

A real row from a run on 24 October 2026, with the thumbnail and avatar links trimmed:

```json
{
  "ok": true,
  "videoId": "CLeZyIID9Bo",
  "title": "Chill Lofi Mix [chill lo-fi hip hop beats]",
  "url": "https://www.youtube.com/watch?v=CLeZyIID9Bo",
  "channelName": "Settle",
  "channelId": "UCkKT4qf-TcPFOmpqhTawrGA",
  "viewCountText": "35,075,027 views",
  "publishedTimeText": "4 years ago",
  "lengthText": "1:44:52",
  "thumbnail": "https://i.ytimg.com/vi/CLeZyIID9Bo/hq720.jpg?sqp=...",
  "mode": "search",
  "viewCount": 35075027,
  "viewCountIsApproximate": false,
  "lengthSeconds": 6292,
  "sourceInput": "lofi hip hop",
  "position": 4,
  "channelUrl": "https://www.youtube.com/channel/UCkKT4qf-TcPFOmpqhTawrGA",
  "channelHandle": "@settlefm",
  "type": "video",
  "isLive": false,
  "hashtags": [],
  "channelVerified": false,
  "channelAvatar": "https://yt3.ggpht.com/89HydDCrVDqoTZbqoX...",
  "collaborators": null,
  "isMembersOnly": false,
  "recordType": "video"
}
```

| Field | How to read it |
|---|---|
| `viewCount` | A number. `viewCountIsApproximate: true` means a rounded figure like `88M views`. Null on a live stream. |
| `position` | Where the video sat in that keyword's results or that channel's list, counting from 1. |
| `channelHandle` | Filled when the row carries it; search rows from some big channels carry only the `UC` id unless **Add channel facts** is on. |
| `collaborators` | Set on a collaboration. `channelId` is empty on those rows. |
| `channelSubscriberCount` | YouTube's own rounding for big channels, as on the page. |

### 🧾 Reading the output

| Row | How to spot it | Billed |
|---|---|---|
| A video | `ok: true`, `recordType: "video"` | yes |
| A channel from a channel search | `ok: true`, `recordType: "channel"` | yes, like a video |
| A channel profile | `ok: true`, `recordType: "channelProfile"` | an add-on |
| A diagnostic | `ok: false` and an `errorCode` | no |

Filter on `ok == true` and you have your data. A failed keyword, channel or link gets its own row and the run
carries on.

| Code | What it means |
|---|---|
| `BAD_INPUT` | An input or link could not be used, a single video link was given, or the input named nothing. |
| `NOT_FOUND` | YouTube has no channel at that address. |
| `NO_RESULTS`, `NO_VIDEOS` | A search, playlist or channel gave nothing, or nothing matched your filters. |
| `SORT_NOT_AVAILABLE`, `SORT_NOT_APPLIED` | The channel order you asked for was not offered or not applied, so that channel's Videos tab gave no rows. |
| `FILTER_UNCHECKED` | A pasted search link carried a filter the run cannot check. |
|`RATE_LIMITED`, `SERVER_ERROR`, `NETWORK` | That target would not answer this run. Re-running usually clears it. |
| `NOT_SERVED` | This video was not served this run. Re-run it later. Runs before 13 October 2026 wrote `BLOCKED` here, and runs from 13 to 17 October wrote `NETWORK`. |
| `TIME_LIMIT` | The run got close to its time limit and stopped on its own. The videos delivered before that are in the dataset and charged as usual. Give the run more time, or ask for fewer videos, to get the rest. |

A run still ends as succeeded, with an amber status message, even when the time limit stopped it before any video came back. The Overview table shows
video columns only, so a diagnostic row reads as a blank line there.

### 💡 What people use it for

- Watching what ranks for a keyword this week, with `position` saved next to each video.
- Pulling a channel's most viewed videos, or everything since a date, for a pitch or a review.
- Turning a list of playlists into one table of videos with their channels.
- Finding the channels behind a topic, with their subscriber counts, through a channel search.

From a keyword to what is said and what viewers say back, in three runs:

1. Search a keyword here and copy the `url` column of the videos you care about.
2. Paste those links into the **Video URLs or IDs** box of
   [YouTube Transcript Scraper](https://apify.com/dami_studio/youtube-transcript-scraper) to get
   what each video says.
3. Paste the same links into the **Video URLs** box of
   [YouTube Comments Scraper](https://apify.com/dami_studio/youtube-comments-scraper) to read the
   replies.

### 🚧 What it does not do

- **No exact view counts on channel rows, no likes and no real dates.** A channel page prints `"88M views"` and
  `"4 years ago"`, and that is what comes back.
- **No single video links.** It reads searches, channels and playlists.
- **Search skips Shorts and playlists** that appear between ordinary results; Shorts come from a channel's Shorts tab.
- **No comment text, transcripts or subtitles.** Those live in the actors in the table below.
- **Comment counts are rounded.**
- **No search order.** Results come in YouTube's relevance order.
- **Very large limits come back short.** 1,000 is the most, and a big number often stops before it.
- It is not an official YouTube tool. Dami's Studio is independent and is not affiliated with or endorsed by YouTube or Google, or by any other company named on this page.

### 🧭 Which YouTube scraper do you need?

| If you want | Use |
|---|---|
| Keyword search results, a channel's videos, Shorts and streams | This one |
| A channel's uploads, with a higher ceiling per run | [YouTube Channel Scraper](https://apify.com/dami_studio/youtube-channel-scraper) |
| Shorts from a keyword, with exact view and like counts | [YouTube Shorts Scraper](https://apify.com/dami_studio/youtube-shorts-scraper) |
| Videos in a playlist | [YouTube Playlist Scraper](https://apify.com/dami_studio/youtube-playlist-scraper) |
| The most-viewed videos for a topic | [YouTube Trending Scraper](https://apify.com/dami_studio/youtube-trending-scraper) |
| Comments under a video | [YouTube Comments Scraper](https://apify.com/dami_studio/youtube-comments-scraper) |
| What a video says, as text, SRT and VTT | [YouTube Transcript Scraper](https://apify.com/dami_studio/youtube-transcript-scraper) |
| The video, its audio or its subtitles as a file | [YouTube Downloader Pro](https://apify.com/dami_studio/youtube-video-downloader) |

### ❓ Questions people ask

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

No. Nothing here is signed in.

#### Is maxItems per run?

No, per keyword, per channel and per playlist. Three targets at 50 is up to 150 rows.

#### Can I get exact YouTube view counts?

On search rows `viewCount` is already the exact figure the results page prints. Channel and playlist rows are rounded. YouTube Shorts Scraper returns exact view and like counts for Shorts.

#### Why is channelId empty on some rows?

Those rows are collaborations. `collaborators` lists the channels instead.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/youtube-scraper/api/python) has code for Python, JavaScript and the CLI.
MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/youtube-scraper`; coding assistants can load [our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-youtube-SKILL.md). All three bill your
Apify account at the same price.

#### Is scraping YouTube legal?

This reads public search results, public channel pages and public playlists, nothing signed in. Results can still
contain personal data, which GDPR and similar laws cover, so have a reason for collecting it. YouTube's own terms
still apply to what you do with it. Apify's write-up on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is
a good starting point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Include the keyword, channel or link you used and the run ID, and the
log is enough for us to see what happened.

# Actor input Schema

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

Keywords to search YouTube for, one per line, for example "lofi hip hop" or "python tutorial". Each query returns up to Max videos. Leave empty if you only want channel videos. An empty result usually means the query genuinely has no matches.

## `channelUrls` (type: `array`):

YouTube channels to pull videos from, one per line. Accepts full channel URLs (e.g. https://www.youtube.com/@LofiGirl, https://www.youtube.com/c/ChannelName, https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow), @handles (e.g. @LofiGirl), or raw UC channel ids (e.g. UCSJ4gkVC6NrvII8umztf0Ow). Each channel returns up to Max of its newest videos.

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

Optional. Paste YouTube links, or upload a text file or a Google Sheet of them. A search results link runs that search with the filters in the link. A playlist link gives its videos, up to Max. A channel link works like the channel field. Single video links are not read.

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

Maximum videos per search query, per channel and per playlist. A few hundred is realistic. Very large values take much longer and are likelier to come back short. 0 skips searches, playlists and the channels' Videos tab, for runs that only want Shorts, past streams or channel profiles.

## `includeChannelFacts` (type: `boolean`):

Adds each channel's subscriber count, video count, banner and handle to search and playlist rows. It reads each channel's page once, so a run with many channels takes longer. Channel rows always carry these. Off by default.

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

Optional add-on. Adds each video's comment count as YouTube shows it (rounded, like 4.1K) and whether comments are turned off.

## `channelProfilesOnly` (type: `boolean`):

Optional add-on. Each channel gives one row about the channel, with no videos: subscribers, video count, total views, joined date, country, description, links, avatar and banner.

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

Only search results uploaded in this period. Applies to search queries.

## `duration` (type: `string`):

Only search results of this length. Short is under 3 minutes, medium 3 to 20 minutes, long over 20 minutes. Live streams have no length, so a length filter leaves them out.

## `features` (type: `array`):

Only search results with all of these features. Creative Commons is the licence the uploader picked; check it yourself before reusing a video.

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

Videos (the default), channels, or movies. A channel search gives one row per channel; a movie search gives the films YouTube sells or rents.

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

Two-letter country code the search is made for, such as US, DE or BR. Leave empty for US.

## `searchLanguage` (type: `string`):

Language code for the search, such as en, de or pt-BR. Leave empty for English. In another language YouTube writes views and dates in that language, so viewCount and the other numbers worked out from that text come back empty.

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

Which of a channel's videos come first: the newest, the most viewed, or the oldest. Applies to the Videos tab.

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

A day like 2026-10-01, or a number of days like "30 days". Applies to the channels' videos and past streams. Older videos are left out and not charged.

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

How many of each channel's past and current live streams to add, from its Live tab. 0 (the default) skips them.

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

How many of each channel's Shorts to add, from its Shorts tab. 0 (the default) skips them.

## `notionConnector` (type: `string`):

Optional. Write each video as a page into your Notion when the run finishes. Authorize a Notion connector once in Settings → API & Integrations → MCP connectors, then pick it here. Leave empty to skip (default), results are always saved to the dataset regardless.

## `notionParentId` (type: `string`):

Optional. The Notion data source ID of the database to write into (only used if a Notion connector is set). Leave empty to create the pages privately in your workspace instead.

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

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "searchQueries": [
    "lofi hip hop"
  ],
  "maxItems": 50,
  "includeChannelFacts": false,
  "includeCommentCount": false,
  "channelProfilesOnly": false,
  "uploadDate": "any",
  "duration": "any",
  "features": [],
  "searchType": "videos",
  "channelSort": "latest",
  "maxStreams": 0,
  "maxShorts": 0
}
```

# Actor output Schema

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

Scraped rows are stored in the default dataset (one row per result). Empty or failed runs return a single uncharged diagnostic row instead.

# 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 = {
    "searchQueries": [
        "lofi hip hop"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/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 = { "searchQueries": ["lofi hip hop"] }

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/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 '{
  "searchQueries": [
    "lofi hip hop"
  ]
}' |
apify call dami_studio/youtube-scraper --silent --output-dataset

```

## MCP server setup

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