# YouTube Metadata Scraper (`socialdataapi/youtube-metadata-scraper`) Actor

YouTube Metadata Scraper — Extract YouTube video metadata, including titles, descriptions, channel names, video IDs, URLs, upload dates, durations, view counts, likes, tags, and category details. Useful for SEO research, content analysis, competitor tracking, and video performance insights.

- **URL**: https://apify.com/socialdataapi/youtube-metadata-scraper.md
- **Developed by:** [Social Data API](https://apify.com/socialdataapi) (community)
- **Stats:** 3 total users, 2 monthly users, 80.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 Metadata Scraper — Search, Comments and Transcripts

YouTube Metadata Scraper collects video metadata, channel context, top-level comments and caption transcripts from direct video links or from a keyword search of YouTube's own results — one JSON row per video, with comments and transcripts nested right inside it. Every response is structured JSON, ready to pass directly to an LLM, index into a vector store, or feed a monitoring pipeline. No YouTube account, cookie, or login is read anywhere in the input schema or used by the request layer. Point it at a list of URLs, a set of search phrases, or both in the same run, and get a dataset back that already links the video, its channel, its top comments and its captions together.

![YouTube Metadata Scraper output as JSON: titles, views, likes, channel info, comments and transcripts](https://api.apify.com/v2/key-value-stores/WYkyEfqs10huyXYxr/records/a-youtube-metadata-hero.webp)

### What is YouTube Metadata Scraper?

YouTube Metadata Scraper is an Apify Actor that resolves videos two ways in the same run — from a list of `urls` (any watch, Shorts, live, embed, or `youtu.be` link, or a bare 11-character video ID) and/or from `keywords`, which it expands by paging through YouTube's own search-results endpoint. For every resolved video it returns one dataset row combining video metadata, channel details, up to `max_comments` top-level comments and, when available, the caption transcript — as plain text or as timed segments. Point it at a channel's recent uploads or a topic's top results to download youtube metadata at scale without writing any scraping code. No YouTube account or login is required for any of it: the Actor reads only what a logged-out visitor sees. Key capabilities:

- **Dual discovery** — direct `urls` for known videos, `keywords` for open-ended search, deduplicated and processed together in one run.
- **Exact engagement counters** — `viewCount` and `likeCount` are read as exact integers, not YouTube's abbreviated on-page text.
- **The real channel `@handle`** — `channelUsername` is the actual handle scraped from the page, never guessed from the display name.
- **Top-level comment threads** — comment text, author, like count, publish time and reply count, up to the limit you set.
- **Caption transcripts** — plain-text or timestamped segments, with human-readable language names and duplicate tracks removed.
- **Query controls** — `sort_order` for keyword runs, `max_results_per_keyword` for how deep to page, and separate toggles for auto-generated and non-English captions.

### What data can you get with YouTube Metadata Scraper?

Every run can return four data groups inside a single row per video.

| Result Type | Extracted Fields | Primary Use Case |
| --- | --- | --- |
| Video metadata | `id`, `url`, `title`, `description`, `lengthInSeconds`, `uploadDatetime`, `publish_date_since`, `category`, `tags`, `thumbnail`, `thumbnails_urls`, `viewCount`, `likeCount`, `commentCount`, `is_live_now`, `description_links`, `is_comments_off`, `is_members_only`, `is_paid_content` | Content research, SEO and performance analysis |
| Channel context | `channelID`, `channelURL`, `channelUsername`, `channelDisplayName`, `channelSubscribers`, `author_avatar_url`, `is_verified` | Creator identification, influencer vetting, outreach |
| Comments (`comments` array) | `id`, `text`, `author`, `authorChannelId`, `likeCount`, `publishedTime`, `replyCount` | Audience sentiment and engagement research |
| Transcripts (`transcripts` array) | `language`, `languageCode`, `languageName`, `isAutoGenerated`, `characterCount`, `content` | Content repurposing, subtitle generation, RAG ingestion, keyword search |

#### Comments and transcripts alongside search-driven discovery

The reason this Actor covers keyword discovery, comments and transcripts in one run is that none of it needs a second tool or a second cost: pass search phrases in `keywords`, and every video that surfaces already carries its own `comments` and `transcripts` arrays in the same row, controlled by `max_comments`, `include_transcripts` and the caption-language toggles — one run that combines youtube metadata and transcripts for an entire topic instead of two. A comment row inside `comments` carries the commenter's display name, channel ID, like count and reply count; a transcript row inside `transcripts` carries the caption language, whether it was auto-generated, its character count, and the text itself — as one joined string when `transcript_format` is `text`, or as a list of `{start, dur, text}` segments when it is `timestamps`.

```json
{
  "id": "M7lc1UVf-VE",
  "title": "Google I/O Keynote Highlights",
  "viewCount": 812340,
  "comments": [
    { "id": "UgzAbc123", "text": "Great overview, thanks!", "author": "Dana Park", "authorChannelId": "UCabc123", "likeCount": 42, "publishedTime": "3 months ago", "replyCount": 2 }
  ],
  "transcripts": [
    { "language": "EN", "languageCode": "en", "languageName": "English", "isAutoGenerated": false, "characterCount": 8120, "content": "Welcome everyone to this year's keynote..." }
  ]
}
```

#### The real `@handle` in `channelUsername`

Every channel is resolved to its actual `@handle`, read from the page's own canonical channel link rather than guessed from the display name — a display name like "Bro Code" does not reliably map to its real handle (`@BroCodez`, in that example), and a synthesized guess produces a channel URL that doesn't exist. `channelUsername` is empty only when YouTube itself exposes no handle for that channel, such as some auto-generated artist channels — it is never invented. `channelDisplayName` still carries the human-readable name shown under the video for anything that needs it. Together with `channelSubscribers`, `author_avatar_url` and `is_verified`, this gives every row a complete youtube channel metadata block alongside the video itself.

### How does YouTube Metadata Scraper differ from the official YouTube Data API?

YouTube Data API v3 is Google's own public API for video and channel metadata, and it returns real, accurate `statistics` for videos and channels once you have a project and a key. Where it falls short of this Actor is search volume, keyword-to-transcript coverage, and caption access for videos you don't own.

| Feature | YouTube Data API v3 | YouTube Metadata Scraper |
| --- | --- | --- |
| Setup | Google Cloud project, API key or OAuth client | Apify account only — no Google credentials |
| Caption/transcript text for a video you don't own | `captions.download` "requires the user to have permission to edit the video" ([Google Developers docs](https://developers.google.com/youtube/v3/docs/captions/download)) — third-party public videos are effectively off-limits | Reads caption text directly from the public watch page for any video, regardless of ownership |
| Combined coverage per lookup | Video stats, channel data, comment threads and (owned) captions each live behind separate resources (`videos`, `channels`, `commentThreads`, `captions`), metered from the same shared project quota | Video metadata, channel context, comments and transcript all come back in one row from one Actor run |
| Keyword-driven discovery | `search.list` returns matching videos, but every call draws from the same per-project daily quota that all your other API calls also consume | `keywords` pages through YouTube's own search results with no per-call quota to manage |
| Result delivery | Raw JSON responses only — you build your own storage and export layer | Results land in an Apify dataset, exportable as JSON, CSV, Excel, or XML, or read live via the Apify API |

The official API is the right choice when you already manage a Google Cloud project, need captions for videos your own channel owns, or want a first-party SLA for your own product. YouTube Metadata Scraper is the better fit for ad hoc or bulk research across videos you don't own — keyword discovery, comments and caption text together, with no OAuth flow or quota dashboard to build.

### What is the difference between a YouTube metadata scraper and a YouTube transcript scraper?

A YouTube transcript scraper answers one narrow question — what does this video's caption track say — and returns caption text with nothing else attached. A YouTube metadata scraper answers a broader one — what is this video, who made it, and how is it performing — but the two are often sold as separate tools, sometimes even by the same vendor, because caption extraction and metadata extraction historically ran through different code paths. That split matters once a workflow needs both: knowing a video is trending (`viewCount`, `likeCount`, `commentCount`) is what tells you it's worth reading the transcript for, and the transcript is what tells you why. YouTube Metadata Scraper returns both in the same JSON row — `transcripts` sits alongside `viewCount`, `channelUsername` and `comments` on the identical object — so there is no second tool, second run, or second cost to go from "this video is worth reading" to "here is what it says."

### How to scrape YouTube with YouTube Metadata Scraper?

1. Open [YouTube Metadata Scraper](https://apify.com/socialdataapi/youtube-metadata-scraper) on the Apify Store and start a new run.
2. Enter one or more video links or IDs in `urls`, and/or one or more search phrases in `keywords` — at least one of the two is required.
3. Set `sort_order` and `max_results_per_keyword` to control how keyword results are ranked and how many videos are collected per phrase.
4. Set `max_comments`, `include_transcripts`, `transcript_format`, `include_english_auto` and `include_non_english` for how much comment and caption depth to collect per video.
5. Start the run, then download or stream results from the dataset as JSON, CSV, Excel, or XML.

Example run input using the Actor's real parameter names:

```json
{
  "urls": ["https://www.youtube.com/watch?v=M7lc1UVf-VE"],
  "keywords": ["react tutorial for beginners"],
  "sort_order": "view_count",
  "max_results_per_keyword": 20,
  "max_comments": 10,
  "include_transcripts": true,
  "transcript_format": "text",
  "include_english_auto": true,
  "include_non_english": false
}
```

#### How to run multiple queries in one job

Both `urls` and `keywords` accept arrays, so a single run can process a batch of known videos and a batch of search phrases together — there is no need to launch a separate run per video or per keyword. Video IDs resolved from `urls` and video IDs discovered from every keyword in `keywords` are combined and deduplicated before processing starts, so a video that shows up both directly and inside a keyword's search results is only fetched, and only charged, once.

### ⬇️ Input

![Input fields of the YouTube Metadata Scraper](https://api.apify.com/v2/key-value-stores/WYkyEfqs10huyXYxr/records/a-youtube-metadata-input.webp)

The Actor accepts direct video links or IDs, keyword search phrases, or both — plus controls for keyword ranking, comment depth, transcript coverage and proxy behavior. All ten fields below come directly from the input schema; none are required on their own, but at least one of `urls` or `keywords` must be supplied.

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `urls` | No | array | Video URLs or IDs, one entry per video. Accepts `youtube.com/watch?v=…`, `youtu.be/…`, `youtube.com/shorts/…`, `youtube.com/live/…`, `youtube.com/embed/…`, `youtube.com/v/…`, and the bare 11-character video ID. Leave empty for keyword-only discovery. | `["https://www.youtube.com/watch?v=M7lc1UVf-VE"]` |
| `keywords` | No | array | YouTube search phrases, one per line. `sort_order` and `max_results_per_keyword` control ranking and volume for these. Shorts appearing inside search results are skipped; a Shorts watch URL pasted into `urls` is scraped normally. | `["react tutorial for beginners"]` |
| `sort_order` | No | string (enum: `relevance`, `upload_date`, `view_count`, `rating`; default `relevance`) | Applies to keyword runs only. YouTube's live sort menu currently honours only `relevance` and `view_count`; `upload_date` and `rating` are still sent and accepted for input compatibility, but YouTube ignores them and the run logs a warning instead of pretending otherwise. | `"view_count"` |
| `max_results_per_keyword` | No | integer (min `1`, max `5000`, default `10`) | Videos taken per keyword, paging through YouTube's search continuations. YouTube stops serving new results well before any high number — the measured deep-page ceiling is roughly 133–484 videos per keyword and varies by phrase, so a value above that returns whatever YouTube still has. | `20` |
| `max_comments` | No | integer (min `0`, max `5000`, default `2`) | Top-level comments to store per video, verified exact from 0 up to 300+. Set `0` to skip comments entirely. Reply bodies, comment sort order, and comment permalinks are not collected. | `10` |
| `include_transcripts` | No | boolean (default `true`) | Attach caption text for each video when YouTube exposes captions for that upload. Duplicate tracks carrying identical text are removed; the download is capped at 6 languages and 150 seconds per video. | `true` |
| `transcript_format` | No | string (enum: `text`, `timestamps`; default `text`) | `text` puts one paragraph string in `content`; `timestamps` puts a list of `{start, dur, text}` segments there instead. | `"text"` |
| `include_english_auto` | No | boolean (default `false`) | Also allow YouTube auto-captions in English alongside creator-uploaded captions — this is what lifts transcript coverage on videos with no manual captions. | `true` |
| `include_non_english` | No | boolean (default `false`) | Allow non-English manual or auto tracks. Popular videos expose dozens of auto-translated languages; the run keeps the first 6 distinct tracks and stops there. | `false` |
| `proxyConfiguration` | No | object (default: `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountryCode": "US"}`) | Your proxy choice is honoured request by request. Residential + US is the default and is strongly recommended: on a datacenter exit, roughly 83% of watch pages come back as an HTTP 200 sign-in page with no title, and transcripts return nothing at all. If a page comes back walled, the run escalates to Residential/US on its own and logs it. | `{ "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountryCode": "US" }` |

#### Free plan limits

Free plan: up to 20 videos per run (all URLs and keywords together) and 200 per month; paid plans have no limit.

#### Example JSON input

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=M7lc1UVf-VE",
    "https://www.youtube.com/shorts/8hly31xKli0"
  ],
  "keywords": ["react tutorial for beginners", "docker compose explained"],
  "sort_order": "relevance",
  "max_results_per_keyword": 15,
  "max_comments": 5,
  "include_transcripts": true,
  "transcript_format": "timestamps",
  "include_english_auto": true,
  "include_non_english": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountryCode": "US"
  }
}
```

**Common pitfall:** `max_comments` controls how many items land in each row's `comments` array, but `commentCount` is a separate field — it is YouTube's own displayed comment total for the video, read straight off the page, and is not shaped by `max_comments` at all. Setting `max_comments: 5` still reports the video's real full `commentCount`, alongside exactly 5 (or fewer, if the video has fewer) items in `comments`.

### ⬆️ Output

![Output fields of the YouTube Metadata Scraper](https://api.apify.com/v2/key-value-stores/WYkyEfqs10huyXYxr/records/a-youtube-metadata-output.webp)

Results are typed, normalized JSON with a consistent field set across every row, exportable from the Apify platform in JSON, CSV, Excel, XML, or HTML, or read live through the Apify API — enough to export youtube metadata straight into a spreadsheet or BI tool with no cleanup step. Comments and transcripts arrive nested inside their parent video row rather than as separate rows, so a single record carries everything collected for that video.

Every dataset row is billed as one `row_result` event — but only when the row is genuinely populated. A row is charged when it carries a `title`, a `viewCount` greater than `0`, and either a `channelID` or a `channelDisplayName`. A video that fails that check — for example a page YouTube served back in a degraded or partially blocked state, with fields the Actor could not read — is still saved to the dataset for visibility, but is pushed **without** the `row_result` charge. Check the Actor's Pricing tab on the Apify Store for the current per-event rate.

#### Scraped results

```json
[
  {
    "id": "M7lc1UVf-VE",
    "url": "https://www.youtube.com/watch?v=M7lc1UVf-VE",
    "title": "Google I/O Keynote Highlights",
    "description": "A look back at the announcements from this year's keynote.",
    "lengthInSeconds": 1523,
    "uploadDatetime": "2024-05-14T17:00:00-07:00",
    "publish_date_since": "1 year ago",
    "category": "Science & Technology",
    "tags": ["google", "keynote", "developers"],
    "thumbnail": "https://i.ytimg.com/vi/M7lc1UVf-VE/hqdefault.jpg",
    "thumbnails_urls": ["https://i.ytimg.com/vi/M7lc1UVf-VE/hqdefault.jpg"],
    "channelID": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
    "channelURL": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
    "channelUsername": "GoogleDevelopers",
    "channelDisplayName": "Google for Developers",
    "channelSubscribers": "2.5M",
    "author_avatar_url": "https://yt3.ggpht.com/example-avatar",
    "is_verified": true,
    "viewCount": 812340,
    "likeCount": 15230,
    "commentCount": 940,
    "is_live_now": false,
    "description_links": ["https://developers.google.com"],
    "is_comments_off": false,
    "is_members_only": false,
    "is_paid_content": false,
    "transcripts": [
      { "language": "EN", "languageCode": "en", "languageName": "English", "isAutoGenerated": false, "characterCount": 8120, "content": "Welcome everyone to this year's keynote..." }
    ],
    "comments": [
      { "id": "UgzAbc123", "text": "Great overview, thanks!", "author": "Dana Park", "authorChannelId": "UCabc123", "likeCount": 42, "publishedTime": "3 months ago", "replyCount": 2 }
    ]
  },
  {
    "id": "8hly31xKli0",
    "url": "https://www.youtube.com/watch?v=8hly31xKli0",
    "title": "60-Second Docker Compose Tip",
    "description": "A quick tip for structuring your compose file.",
    "lengthInSeconds": 58,
    "uploadDatetime": "2025-02-02T09:15:00-08:00",
    "publish_date_since": "5 months ago",
    "category": "Education",
    "tags": ["docker", "devops"],
    "thumbnail": "https://i.ytimg.com/vi/8hly31xKli0/hqdefault.jpg",
    "thumbnails_urls": ["https://i.ytimg.com/vi/8hly31xKli0/hqdefault.jpg"],
    "channelID": "UCexample2",
    "channelURL": "https://www.youtube.com/channel/UCexample2",
    "channelUsername": "DevOpsDaily",
    "channelDisplayName": "DevOps Daily",
    "channelSubscribers": "184K",
    "author_avatar_url": "https://yt3.ggpht.com/example-avatar-2",
    "is_verified": false,
    "viewCount": 43210,
    "likeCount": 2110,
    "commentCount": 76,
    "is_live_now": false,
    "description_links": [],
    "is_comments_off": false,
    "is_members_only": false,
    "is_paid_content": false,
    "transcripts": [],
    "comments": [
      { "id": "UgzDef456", "text": "Didn't know about this, thanks!", "author": "Marco Reyes", "authorChannelId": "UCdef456", "likeCount": 5, "publishedTime": "4 months ago", "replyCount": 0 }
    ]
  },
  {
    "id": "jNQXAC9IVRw",
    "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "title": "Backyard Coffee Roasting, Explained",
    "description": "How I roast small batches at home.",
    "lengthInSeconds": 641,
    "uploadDatetime": "2023-11-09T12:30:00-08:00",
    "publish_date_since": "2 years ago",
    "category": "Howto & Style",
    "tags": ["coffee", "roasting"],
    "thumbnail": "https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg",
    "thumbnails_urls": ["https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg"],
    "channelID": "UCexample3",
    "channelURL": "https://www.youtube.com/channel/UCexample3",
    "channelUsername": "",
    "channelDisplayName": "Home Roast Journal",
    "channelSubscribers": "12.3K",
    "author_avatar_url": "https://yt3.ggpht.com/example-avatar-3",
    "is_verified": false,
    "viewCount": 9840,
    "likeCount": 512,
    "commentCount": 61,
    "is_live_now": false,
    "description_links": [],
    "is_comments_off": false,
    "is_members_only": false,
    "is_paid_content": false,
    "transcripts": [
      { "language": "EN-ORIG (auto-generated)", "languageCode": "en", "languageName": "English (auto-generated)", "isAutoGenerated": true, "characterCount": 4310, "content": "so today we're going to talk about roasting at home..." }
    ],
    "comments": []
  }
]
```

### How can I use the data extracted with YouTube Metadata Scraper?

- **Content strategists and SEO researchers:** compare `title`, `tags`, `description` and `viewCount` across a keyword's top results to see which formats and topics are actually ranking.
- **AI engineers and LLM developers:** have an agent call the Actor with a topic in `keywords`, receive a youtube metadata json payload with video, comment and transcript data back, and pass `content` from `transcripts` to a model as grounded context for summarization or Q\&A.
- **Market and competitor researchers:** export `comments` for a channel's recent videos and run your own sentiment or keyword pass over `text`, without a separate comment scraper.
- **Influencer and creator vetting teams:** use `channelUsername`, `channelSubscribers` and `is_verified` to confirm a creator's real handle and scale before reaching out.

### 📈 How do you monitor video engagement over time?

Video engagement monitoring means running the same `urls` or `keywords` set on a recurring schedule and comparing each new run's rows against the previous run's for the same video. Because `viewCount`, `likeCount` and `commentCount` are all read fresh from that run's own page fetch, a video whose `viewCount` or `commentCount` climbs sharply between runs is a genuine growth signal, not a scraping artifact. The fields worth diffing are `viewCount` and `likeCount` on the video itself for overall traction, `commentCount` for discussion volume, and the `comments` array's newest entries (by `publishedTime`) for what people are saying right now.

A practical workflow: run the same keyword or URL set daily or weekly, key each run's rows by `id` plus the run date, then alert when a tracked video's `viewCount` or `commentCount` jumps sharply between runs, or when a new video appears in a monitored keyword's results for the first time. This Actor has no scheduler of its own — recurring runs are set up with Apify's Schedule feature on the Actor's page, which triggers a fresh run on the interval you configure and writes each run to its own dataset for comparison.

### Integrate YouTube Metadata Scraper and automate your workflow

YouTube Metadata Scraper works with any language or tool that can send an HTTP request, and with the Apify API and client libraries for scripted access. Because every call returns the same row shape, it works well as a youtube video metadata api for scripted lookups, a youtube video metadata extractor for one-off research, or the backbone of a bulk youtube metadata scraper pipeline that runs on a schedule.

#### REST API with Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run_input = {
    "urls": ["https://www.youtube.com/watch?v=M7lc1UVf-VE"],
    "keywords": ["react tutorial for beginners"],
    "max_results_per_keyword": 20,
    "max_comments": 10,
    "include_transcripts": True,
    "include_english_auto": True,
}

run = client.actor("socialdataapi/youtube-metadata-scraper").call(run_input=run_input)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["viewCount"], len(item.get("comments") or []))
```

#### MCP for query-grounded AI agents

Any Apify Actor, including this one (Apify Actor ID: socialdataapi/youtube-metadata-scraper, https://apify.com/socialdataapi/youtube-metadata-scraper), is callable through Apify's hosted MCP server at `https://mcp.apify.com?tools=socialdataapi/youtube-metadata-scraper`, or locally via `npx @apify/actors-mcp-server` with an `APIFY_TOKEN` environment variable. An MCP-connected agent can call the Actor as a tool — passing a topic or a video list — receive structured metadata, comment and transcript JSON back, and generate a grounded answer about a video or a search topic without the agent developer writing any YouTube-specific scraping code. Compatible with Claude Desktop, Claude Code, Cursor, and other MCP clients.

#### Scheduled monitoring and delivery

The Actor itself has no push-webhook feature, but the Apify platform supports scheduled runs (via Schedules) and run-completion webhooks for any Actor, including this one — so a recurring keyword or channel check can be wired to notify Slack, trigger a Zapier/Make workflow, or write to a data warehouse whenever a scheduled run finishes.

### Is it legal to scrape YouTube video metadata?

Scraping publicly visible web pages is generally lawful in the United States — courts have held that accessing data not gated behind a login is not a violation of the Computer Fraud and Abuse Act, most notably in *hiQ Labs, Inc. v. LinkedIn Corp.*, 9th Circuit, 2019. YouTube Metadata Scraper reads only content YouTube serves to a logged-out visitor: video metadata, channel details, top-level comments and captions are all public-facing, and no login, cookie, or credential is used or accepted anywhere in its input. Video and channel metadata is business/product data governed by YouTube's own Terms of Service rather than personal-data law; comment author display names and channel IDs, however, are personal data in many jurisdictions, so GDPR, CCPA, or equivalent regimes govern how you store and process the `comments` array once collected. Scraping for AI training and scraping for operational monitoring carry different risk profiles. Consult your legal team for commercial use cases involving bulk data storage.

### ❓ Frequently asked questions

#### Can I supply both URLs and keywords in the same run?

Yes. `urls` and `keywords` are independent and can both be populated in one run — the Actor resolves video IDs from both sources, deduplicates them, and processes the combined list together in a single dataset.

#### What does `sort_order` actually change?

For keyword runs, it's meant to re-rank YouTube's search results — but only `relevance` and `view_count` currently do anything. `upload_date` and `rating` are still sent to YouTube exactly as documented, but YouTube's live sort menu no longer honours either one, so results come back unordered by those criteria; the run logs a warning rather than pretending they worked. Both values stay in the enum so a saved input using them doesn't break.

#### How does YouTube Metadata Scraper handle YouTube's anti-bot measures?

It treats raw HTTP status codes as an unreliable signal — many blocks come back as a normal-looking page with no usable video data (a sign-in wall), which the Actor detects directly rather than relying on 403/429/503 responses alone. When a page comes back blocked, the run retries on a fresh proxy session and escalates from whatever proxy mode was configured up to Apify Residential/US, which is also the schema's default.

#### Does YouTube Metadata Scraper extract comments and transcripts together?

Yes — every video row carries both a `comments` array (top-level comment text, author, like count, publish time, reply count) and a `transcripts` array (caption language and text or timed segments) in the same object, controlled by `max_comments` and `include_transcripts` respectively. `transcripts` comes back as an empty array when YouTube exposes no captions for that upload.

#### How many results does YouTube Metadata Scraper return per query?

Every URL in `urls` resolves to one row. For `keywords`, up to `max_results_per_keyword` videos are taken per phrase (default `10`, maximum `5000`) — but YouTube's own search stops serving new results well before any high setting, at a measured ceiling of roughly 133–484 videos per keyword depending on the phrase. Comments are capped by `max_comments` (up to `5000`) and transcript tracks are capped at 6 languages per video.

#### How do I use YouTube Metadata Scraper to monitor video engagement over time?

Run the same `urls` or `keywords` set on a recurring Apify Schedule, key each run's rows by `id` plus the run date, and alert when a tracked video's `viewCount`, `likeCount`, or `commentCount` moves sharply against its own previous run.

#### Does YouTube Metadata Scraper work with Claude, ChatGPT, and AI agent frameworks?

Yes. It's reachable through Apify's hosted MCP server at `https://mcp.apify.com?tools=socialdataapi/youtube-metadata-scraper` for MCP-native clients, and callable as a standard HTTP endpoint by any agent framework that can issue a request and parse JSON — which enables a grounded agent that retrieves live video, comment, and transcript data before answering a question.

#### How does YouTube Metadata Scraper compare to other YouTube scrapers?

As observed on the Apify Store on 2026-07-30, `dz_omar/youtube-transcript-metadata-extractor` extracts transcripts and metadata from direct URLs but has no keyword-search input, and its own listing documents that comment extraction is a separate paid call to a second actor in Standby Mode; `apidojo/youtube-playlist-scraper` accepts keywords, but only to discover playlists, and extracts neither comments nor transcripts; and `beyondops/youtube-metadata-scraper-pro-v2` accepts direct video URLs only (no keyword-search field is documented) and recommends pasting a Netscape `cookies.txt` export to avoid anti-bot walls, with no transcript or comment extraction at all. YouTube Metadata Scraper is the only one of the three, as checked, that combines keyword search discovery, comments, and transcripts in one row, with no login cookie required.

#### What happens if a video is private, deleted, or blocked on every proxy exit?

It is retried and, if still unresolvable, skipped and logged with its error in the run's `SUMMARY` record rather than the dataset — a skipped video is never saved and never charged. If every requested video ends up in that state, the run is reported as failed rather than silently succeeding with zero rows.

#### Can I use YouTube Metadata Scraper without managing proxies or YouTube credentials?

You don't need a YouTube account, cookie, or credential of any kind — there is no such field in the input schema. Proxy configuration is exposed because YouTube walls most non-residential exits, so the Actor defaults `proxyConfiguration` to Apify Residential/US and escalates to it automatically if a lower-tier exit gets blocked mid-run; you can leave the default in place and never touch it.

### 💬 Your feedback

Found a bug or missing a field? Let us know through the Issues tab on this Actor's Apify Store page — it's actively monitored and helps prioritize fixes and new fields.

# Actor input Schema

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

One entry per video. Accepted shapes: `youtube.com/watch?v=…`, `youtu.be/…`, `youtube.com/shorts/…`, `youtube.com/live/…`, `youtube.com/embed/…`, `youtube.com/v/…` and the bare 11-character video ID. Leave empty if you only want keyword-based discovery. Free plan users get up to 20 URLs per run and 200 items per month; paid plans have no limit.

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

YouTube search phrases — one phrase per line. **Sort** and **Max videos per keyword** in this same panel control ranking and how many videos are collected per phrase. Shorts that appear inside search results are skipped; a Shorts watch URL pasted above is scraped normally.

## `sort_order` (type: `string`):

Applies to **keyword** runs only. YouTube's live sort menu currently honours only **Relevance** and **View count**; `upload_date` and `rating` are still sent and still accepted for input compatibility, but YouTube ignores them and the run logs a warning instead of pretending otherwise.

## `max_results_per_keyword` (type: `integer`):

At most this many videos are taken **per keyword**, paging through YouTube's search continuations. YouTube stops serving new results well before any high number: the measured deep-page ceiling is roughly **133–484 videos per keyword** and varies by phrase, so a value above that returns whatever YouTube still has. Free plan: up to 20 videos per run (all URLs and keywords together) and 200 per month; paid plans have no limit.

## `max_comments` (type: `integer`):

Top-level comments to store for each video (verified exact from 0 up to 300+). Set **0** to skip comments entirely. Reply bodies, comment sort order and comment permalinks are not collected here.

## `include_transcripts` (type: `boolean`):

Attach caption text for each video when YouTube exposes captions for that upload. Duplicate tracks carrying identical text are removed, and the download is capped at 6 languages and 150 seconds per video.

## `transcript_format` (type: `string`):

`text` puts one paragraph string in `content`; `timestamps` puts a list of `{start, dur, text}` segments there instead.

## `include_english_auto` (type: `boolean`):

Also allow **YouTube auto-captions** in English alongside creator-uploaded captions. Turning this on is what lifts transcript coverage on videos that have no manual captions.

## `include_non_english` (type: `boolean`):

Allow non-English manual or auto tracks. Popular videos expose dozens of auto-translated languages; the run keeps the first 6 distinct tracks and stops there so a single video cannot stall the whole job.

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

Your proxy choice is honoured request by request. **Residential + US is the default and is strongly recommended**: on a datacenter exit, roughly 83% of watch pages come back as an HTTP 200 sign-in page with no title, and transcripts return nothing at all. If a page comes back walled the run escalates to Residential/US on its own and says so in the log.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "sort_order": "relevance",
  "max_results_per_keyword": 10,
  "max_comments": 2,
  "include_transcripts": true,
  "transcript_format": "text",
  "include_english_auto": false,
  "include_non_english": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountryCode": "US"
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset.

## `overview` (type: `string`):

Same dataset, condensed overview view (fewer columns).

## `output` (type: `string`):

Convenience copy of the results written to the default key-value store: the single result object when exactly one video was processed, otherwise a JSON array of result objects.

## `summary` (type: `string`):

Counts of processed/failed videos and the list of skipped video IDs with error messages.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("socialdataapi/youtube-metadata-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 = { "urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] }

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

```

## MCP server setup

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