# YouTube Video Scraper (`thescrapelab/youtube-video-scraper`) Actor

Scrape YouTube video titles, descriptions, channels, thumbnails, durations and available view counts from video URLs, channels or search queries. Export results to JSON, CSV or Excel.

- **URL**: https://apify.com/thescrapelab/youtube-video-scraper.md
- **Developed by:** [Inus Grobler](https://apify.com/thescrapelab) (community)
- **Categories:** Videos, Social media, Marketing
- **Stats:** 4 total users, 1 monthly users, 89.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 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

## YouTube Video Scraper

Collect public YouTube video metadata from video URLs, channel handles or URLs, and search queries. Export your results as JSON, CSV, or Excel. The Actor reads public HTML pages and does not call paid data APIs, model APIs, YouTube source APIs, or other Actors.

### Input

In Apify Console, run the prefilled example to collect one video from **@OpenAI**. Replace the channel handle with your own. Clear Channels when switching to video URLs or search queries.

```json
{
  "channels": ["@OpenAI"],
  "maxItems": 1,
  "useApifyProxyFallback": false
}
```

For individual videos:

```json
{
  "startUrls": [{"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw"}],
  "maxItems": 1
}
```

For a search:

```json
{
  "searchQueries": ["OpenAI"],
  "maxItems": 5
}
```

| Field | Meaning | Default |
| --- | --- | --- |
| `startUrls` | Public video or channel URLs. Supports youtu.be, Shorts, and live video links. | Empty |
| `channels` | Handles such as `@OpenAI`, channel IDs, or channel URLs. | Prefilled `@OpenAI` in Console |
| `searchQueries` | Search phrases; `keywords` remains a compatible alias. | Empty |
| `maxItems` | Maximum delivered videos; for uploaded records, maximum scanned records. | 1 |
| `maxVideosPerChannel` / `maxVideosPerKeyword` | Maximum delivered videos from each initial channel/search page. | 10 each |
| `maxTotalVideos` | Additional total cap; zero uses `maxItems`. | 0 |
| `maxPublicPages` | Maximum HTTP requests across sources, redirects, and retries. | 3 |
| `requestTimeoutSecs` | Total download timeout per attempt, including redirects and streaming. | 12 |
| `deduplicateVideos` | Return one result per video ID across sources. | true |
| `useApifyProxyFallback` | Explicitly enable potentially paid proxy retries. | **false** |
| `proxyConfiguration` | Proxy settings used only when proxy fallback is explicitly true. | Residential proxy for enabled retries |
| `analysisQuery` | Literal phrase to find in delivered records. | Empty |

With no source fields supplied to the API, the Actor uses the same one-video `@OpenAI` example. Channel and search modes collect the initial public page; they do not paginate through a complete archive or all search results. Limits are maximums, not guaranteed result counts.

### Output

Each default-dataset row includes a video ID, canonical URL, title, source, and collection time. Channel details, thumbnails, durations, publication text, descriptions, and view counts are included when exposed on the public page.

An output excerpt from a previously collected public video:

```json
{
  "record_type": "video",
  "video_id": "6ep8Vvm4Bdw",
  "url": "https://www.youtube.com/watch?v=6ep8Vvm4Bdw",
  "title": "Why Merge CEO Shensi Ding stays hands-on with Codex",
  "query": "@OpenAI",
  "query_type": "channel",
  "dataInputSource": "web",
  "sourceVerified": true,
  "metadataCoverage": "initial_page"
}
```

Actual rows also include collection and processing timestamps and a processing hash. Metadata varies by source and layout. Exact observed counts preserve real zeros. Abbreviated values such as `147K views` remain in `view_count_text`; unavailable metrics, including likes, stay unknown. No guessed counts or placeholder videos are produced.

Results are saved after each source. `OUTPUT` and `PUBLIC_COLLECTION_SUMMARY` in the run key-value store report requested, attempted, and skipped source counts, HTTP attempts, proxy usage, delivered records, and stop reasons. `status` is `succeeded`, `partial`, or `failed`. Stop reasons distinguish result limits, request limits, deadlines, charging limits, exhausted sources, and a zero-result limit. A later source failure preserves earlier delivered records and returns partial results. A run with no meaningful metadata fails clearly, except when an explicit zero-result or charging limit prevents collection.

`EVIDENCE_ANALYSIS` contains deterministic analysis and literal phrase matches from confirmed delivered records. `sourceRecordHash` identifies a processed row; it is not a YouTube-issued identifier. Metadata does not establish playback availability.

### Use cases

Collect titles and descriptions for content research, inspect a channel's visible videos, find videos through public search, or save metadata snapshots for later comparison.

### Uploaded records

Existing workflows can use `providedRecords` or `providedDatasetId` with `maxItems`. Clear all YouTube source fields when supplying records. Uploaded records are validated and deduplicated, labeled `dataInputSource: "provided"` and `sourceVerified: false`, and are not fetched from YouTube. Actual observation timestamps are required for meaningful snapshot comparisons. `PROVIDED_DATA_SUMMARY` reports validation and delivery counts.

### Costs and public-data limits

**No paid data API is used.** Apify hosting, storage, result pricing, and explicitly enabled proxy usage can still incur charges; consult the Store pricing panel.

Direct collection is the default. Proxy settings and environment proxy variables do not silently enable a proxy. If YouTube blocks a public page, the Actor returns a clear source failure or partial results. To allow up to two proxy retries within your request budget, explicitly set:

```json
{
  "startUrls": [{"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw"}],
  "maxItems": 1,
  "useApifyProxyFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

Changing from earlier releases: omitting `useApifyProxyFallback` now means **false**. Supplying `proxyConfiguration` alone does not enable it and produces a warning in the run summary.

Collection has a 150-second deadline, a four-megabyte decompressed HTML limit per document, and the configured request/result budgets. TLS verification remains enabled. Collection uses public HTML only. Private, age-restricted, login-only, consent-wall, and otherwise restricted pages are skipped without bypass attempts. No video files, comments, transcripts, or continuation API responses are collected. Older `includeLikeCount`, `useYtDlpFallback`, and `maxConcurrency` settings remain accepted for compatibility and do not enable additional collection services.

### Python API example

This example uses Apify Client for Python 3.x.

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("nRTzbCmODVqCn10lZ").call(run_input={
    "channels": ["@OpenAI"],
    "maxItems": 1,
    "useApifyProxyFallback": False,
})
if run is None:
    raise RuntimeError("The Actor did not return a completed run.")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)
```

For support, share a run ID and sanitized input through the Actor Issues tab.

# Actor input Schema

## `providedRecords` (type: `array`):

Paste records you have permission to process. These are normalized, validated and deduplicated without fetching their original websites. Use this OR a supplied dataset ID. Invalid web URLs, non-finite values and negative prices or counts are rejected. Unknown values remain unknown.

## `providedDatasetId` (type: `string`):

Optional 17-character ID of a dataset you can access. Clear the JSON records to \[] when using a dataset. Only a bounded number of records is read.

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

Maximum video records to return. For uploaded records, bounds the records scanned, including rejected and duplicate rows.

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

YouTube searches. Collects the initial public results page, without pagination.

## `channels` (type: `array`):

Channel handles such as @OpenAI, channel IDs, or YouTube channel URLs. Collects the initial videos page.

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

YouTube video URLs or channel URLs. Shorts and youtu.be links are supported.

## `maxTotalVideos` (type: `integer`):

Optional additional cap on results; zero uses Maximum videos.

## `keywords` (type: `array`):

Alias for searchQueries.

## `maxVideosPerKeyword` (type: `integer`):

Maximum initial-page videos per search query.

## `maxVideosPerChannel` (type: `integer`):

Maximum initial-page videos per channel.

## `deduplicateVideos` (type: `boolean`):

Keep one result per video ID across sources.

## `includeLikeCount` (type: `boolean`):

Compatibility setting. Exact like counts are not reliably exposed on public HTML pages and are left unknown.

## `useYtDlpFallback` (type: `boolean`):

Compatibility setting. No downloader or source API fallback is used.

## `useApifyProxyFallback` (type: `boolean`):

Opt in to potentially paid Apify Proxy retries for blocked or missing public metadata. Disabled unless explicitly true, including API calls. At most two retries within maxPublicPages. No paid data API is called.

## `maxConcurrency` (type: `integer`):

Compatibility setting. Public page requests run sequentially to keep request budgets predictable.

## `requestTimeoutSecs` (type: `integer`):

Total timeout per public HTML download attempt, including redirects and streaming. Also bounded by the 150-second collection deadline.

## `analysisQuery` (type: `string`):

Optional literal phrase to find in the returned records. Matches are saved in EVIDENCE_ANALYSIS.

## `maxPublicPages` (type: `integer`):

Maximum HTTP requests across all sources, redirects and proxy retries. Unvisited sources and the stop reason appear in the run summary.

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

Used only when useApifyProxyFallback is explicitly true. Providing this configuration alone does not enable proxies. Enabled fallback uses residential proxies when no configuration is supplied.

## Actor input object example

```json
{
  "maxItems": 1,
  "searchQueries": [],
  "channels": [
    "@OpenAI"
  ],
  "startUrls": [],
  "maxTotalVideos": 0,
  "keywords": [],
  "maxVideosPerKeyword": 10,
  "maxVideosPerChannel": 10,
  "deduplicateVideos": true,
  "includeLikeCount": false,
  "useYtDlpFallback": true,
  "useApifyProxyFallback": false,
  "maxConcurrency": 2,
  "requestTimeoutSecs": 12,
  "maxPublicPages": 3
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

Default dataset items. Each row is a YouTube video record with metadata and source fields.

## `kvOutputRecord` (type: `string`):

Run summary with totals, warnings, source health, and dataset links.

## `kvStoreKeys` (type: `string`):

List of key-value store records saved by the run.

## `providedDataSummary` (type: `string`):

No description

## `evidenceAnalysis` (type: `string`):

No description

## `collectionSummary` (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": [],
    "channels": [
        "@OpenAI"
    ],
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("thescrapelab/youtube-video-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQueries": [],
    "channels": ["@OpenAI"],
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("thescrapelab/youtube-video-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [],
  "channels": [
    "@OpenAI"
  ],
  "startUrls": []
}' |
apify call thescrapelab/youtube-video-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thescrapelab/youtube-video-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nRTzbCmODVqCn10lZ/builds/0KXsgu3G2gEUo3MKZ/openapi.json
