# YouTube Scraper - Videos by Search, Channel & URL (`lanternlab318/youtube-scraper`) Actor

Public YouTube video data from search terms, channels and video links.

- **URL**: https://apify.com/lanternlab318/youtube-scraper.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.99 / 1,000 videos

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

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 - Videos by Search, Channel & URL

A Lantern Lab Actor. Collect public YouTube video data three ways: by **search term**, from a **channel**, or from a list of **video links**. Each video comes back with its **title**, **description**, **publish time**, **duration**, **views**, **likes**, **comment count**, **channel name, ID and subscriber count**, **tags**, **category**, **thumbnail** and whether it is a **Short**. No login, no cookies, no account of your own: the Actor reads only what YouTube shows a signed-out visitor.

**Price: USD 1.99 per 1,000 videos, plus USD 0.005 per run.** A search that finds nothing, a channel that does not exist, a bad link, a blocked request and a duplicate are free - you pay for complete video rows only. See [Pricing](#pricing).

Need the spoken words too? Use [YouTube Transcript Scraper](https://apify.com/lanternlab318/youtube-transcripts) on the same links.

### Input

```json
{
    "searchQueries": ["lofi hip hop"],
    "channelUrls": ["https://www.youtube.com/@NASA"],
    "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
    "maxResults": 30
}
```

| Field | What it does |
| --- | --- |
| `searchQueries` | Search terms. Each returns up to `maxResults` videos in YouTube's result order, Shorts included. |
| `channelUrls` | `youtube.com/@handle`, `/channel/UC...`, `/c/Name`, `/user/Name`, a bare `@handle` or `UC...` ID. Each returns up to `maxResults` of the channel's most recent videos (Videos tab, newest first). |
| `videoUrls` | Watch, `youtu.be`, Shorts, embed or live links, or bare 11-character video IDs. The list as a whole returns up to `maxResults` videos, in the order given. |
| `maxResults` | The most videos per search term, per channel and for the link list. Default 30. Never exceeded. |
| `maxUpstreamRequests` | Optional hard ceiling on requests to YouTube. Left empty, the Actor derives one from the input. |

### Output

One row per video (`recordType: "video"`). Every input that produced no video gets one free `status` row that says why. Totals are in the run's key-value store under `RUN_SUMMARY`.

```json
{
    "recordType": "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)",
    "description": "The official video for “Never Gonna Give You Up” by Rick Astley. ...",
    "publishedAt": "2009-10-24T23:57:33-07:00",
    "publishedDateText": "Oct 24, 2009",
    "durationSec": 213,
    "viewCount": 1832227041,
    "likeCount": 19490275,
    "commentCount": 2400000,
    "commentCountText": "2.4M",
    "commentsTurnedOff": false,
    "channelName": "Rick Astley",
    "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
    "channelUrl": "https://www.youtube.com/@RickAstleyYT",
    "channelSubscriberCount": 4550000,
    "channelSubscriberCountText": "4.55M subscribers",
    "thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    "tags": ["rick astley", "Never Gonna Give You Up", "..."],
    "category": "Music",
    "isShorts": false,
    "isLiveContent": false,
    "isLiveNow": false,
    "isUnlisted": false,
    "sourceType": "video",
    "sourceInput": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "sourceRank": 1,
    "collectedAt": "2026-10-31T10:12:03.511Z",
    "partial": false,
    "partialReason": null,
    "statusText": null
}
```

Notes on the fields:

- `viewCount` and `likeCount` are exact. `commentCount` and `channelSubscriberCount` are the rounded numbers YouTube shows (`2.4M`), with the page text kept next to them in `commentCountText` / `channelSubscriberCountText`.
- `publishedAt` is YouTube's publish timestamp; `collectedAt` is when this run read the video.
- A live stream has `viewCount: null` while it is live: YouTube shows concurrent viewers there, not views.
- `commentCount` is `null` when YouTube shows no count; `commentsTurnedOff: true` says the channel turned comments off.
- A collaboration video lists several owner channels; `channelName` / `channelId` are the main one and `channelSubscriberCount` is `null`, because the page states no single count.
- `sourceType` / `sourceInput` / `sourceRank` say which input found the video and where it ranked.
- A field YouTube did not state is `null`, never guessed.

#### Incomplete rows

`partial: true` means the row is not complete, and it is **not charged**. `partialReason` says why:

| `partialReason` | Meaning |
| --- | --- |
| `blocked` | YouTube asked to confirm we are not a bot, from two different exit IPs. Fields from the watch page are kept; duration, tags and the exact publish time are `null`. |
| `upstream_error` | A request to YouTube failed. |
| `upstream_request_cap` / `charge_limit_reached` | The run reached `maxUpstreamRequests` or your maximum cost per run, and stopped. |

Other `status` rows are complete answers, not failures, and are free: `no_results` (the search lists nothing), `not_found` (no such channel), `invalid_input` (not a YouTube link), and video rows with `unavailable` (deleted, private, age-restricted or members-only).

#### Duplicates

A video is collected once per run, keyed by video ID. If a search, a channel and your link list all contain the same video, it is written and charged once; `RUN_SUMMARY.sources[].duplicates` counts the skips.

### Pricing

Pay per event: **USD 1.99 per 1,000 videos, plus USD 0.005 per run.** Apify platform usage, including proxy traffic, is included; you pay only these two events.

| Event | Price | Charged for |
| --- | --- | --- |
| `video` | USD 0.00199 (USD 1.99 per 1,000) | Each complete video row. Partial rows and `status` rows are free. |
| `run-start` | USD 0.005 | Once per run, together with the first video. A run that returns no video is not charged at all. |

Example: 3 search terms x 100 videos = 300 videos costs USD 0.005 + 300 x 0.00199 = USD 0.602.

To cap a run's cost, set **Maximum cost per run** in the run options. When it is reached, the Actor stops, writes a `charge_limit_reached` status row and does not start the remaining inputs.

### Limits

- What a signed-out visitor sees: no private, members-only or age-restricted videos, no comments text, no downloads, no transcripts.
- A channel input reads the Videos tab (long-form uploads). Shorts and live streams of a channel appear when you search for them or pass their links.
- Search returns what YouTube's search page lists for the term; very broad terms are cut at `maxResults`.

### Notes

Respect YouTube's terms and local law, and collect only what you need. Channel names are public, but they can identify a person.

# Actor input Schema

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

What you would type into YouTube search, one per line. Each term returns up to "Max results" videos in YouTube's own result order (Shorts included). A term YouTube finds nothing for is reported with partialReason "no_results" and is not charged.

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

One channel per line: youtube.com/@handle, youtube.com/channel/UC..., /c/Name, /user/Name, a bare @handle or a bare UC... channel ID. Each channel returns up to "Max results" of its most recent videos from the Videos tab, newest first. A handle that does not exist is reported with partialReason "not_found" and is not charged.

## `videoUrls` (type: `array`):

One video per line: a watch link (youtube.com/watch?v=...), youtu.be/..., a Shorts, embed or live link, or a bare 11-character video ID. The whole list is one input: at most "Max results" of these videos are collected, in the order given. A line that is not a YouTube video is reported with partialReason "invalid_input" and is not charged.

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

The most videos to take from each search term, from each channel, and from the video-link list as a whole. Never exceeded. A video already collected earlier in the same run is skipped and counted, not written or charged again.

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to YouTube, retries included. At the ceiling collection stops, a status row with partialReason "upstream_request_cap" says where, and the run ends. Leave empty and the Actor derives a ceiling from the input (at most 4 requests per video plus the listing pages), so a run is never unbounded.

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

The proxy used for search, channel and watch-page requests. The video-details request always goes through a residential IP, because YouTube answers datacenter IPs with its "confirm you are not a bot" check; that part is not affected by this setting.

## Actor input object example

```json
{
  "searchQueries": [
    "lofi hip hop"
  ],
  "channelUrls": [
    "https://www.youtube.com/@NASA"
  ],
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "maxResults": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `runSummary` (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 = {
    "searchQueries": [
        "lofi hip hop"
    ],
    "channelUrls": [
        "https://www.youtube.com/@NASA"
    ],
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/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"],
    "channelUrls": ["https://www.youtube.com/@NASA"],
    "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
}

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/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"
  ],
  "channelUrls": [
    "https://www.youtube.com/@NASA"
  ],
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ]
}' |
apify call lanternlab318/youtube-scraper --silent --output-dataset

```

## MCP server setup

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