# YouTube Transcript Scraper - Captions & Subtitles API (`scrapers-hub/youtube-transcript-scraper`) Actor

📝 YouTube Transcript Scraper extracts timestamped transcripts, captions & subtitles from any public video in 18 languages — plus title, channel & view data. ⚡ No API key. 🚀 Perfect for AI pipelines, SEO & repurposing. ✅

- **URL**: https://apify.com/scrapers-hub/youtube-transcript-scraper.md
- **Developed by:** [Scrapers Hub](https://apify.com/scrapers-hub) (community)
- **Categories:** Social media, Automation, Developer tools
- **Stats:** 5 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### YouTube Transcript Scraper — Timestamped Captions, Subtitles and Video Metadata

YouTube Transcript Scraper pulls the full timestamped transcript out of any public YouTube video, in 18 languages, without an API key, OAuth client, or a logged-in Google account. Paste one watch URL or a hundred, pick a language, and every video comes back as a structured record: the transcript as an array of `text` + `timestamp` segments, plus the video title, channel name, subscriber count, view count, and publish date. Output is clean JSON, ready to hand to an LLM, drop into a spreadsheet, or push through a summarization pipeline. Built for teams who need the actual spoken content of a video as data — not a link to it.

### 🤔 What is YouTube Transcript Scraper?

YouTube Transcript Scraper is a caption-extraction Actor that reads YouTube's own public transcript tracks and returns them as structured, timestamped data. It needs no Google Cloud project, no YouTube Data API quota, and no login — only a video URL. Where the official YouTube Data API makes captions available only to the video's owner under OAuth, this Actor reads the same publicly rendered caption tracks a viewer sees when they click "Show transcript," including auto-generated ASR captions.

Alongside the transcript, each record carries the video metadata scraped from the watch page itself, so you don't need a second API call to know whose channel a transcript came from or how many views it had at scrape time.

- **Timestamped transcript extraction** — every caption line returned as `{text, timestamp}` in `M:SS` form
- **18 language options** with automatic fallback when your first choice isn't published
- **Video metadata in the same record** — title, channel, subscriber count, views, publish date
- **Automatic proxy rotation** on YouTube IP blocks, with a fresh residential exit IP per video
- **Per-video fault isolation** — one dead video never kills the rest of your batch
- **Both URL forms accepted** — `youtube.com/watch?v=` and short `youtu.be/` links

#### How YouTube Transcript Scraper handles language fallback

The `language` field takes a two-letter code, but YouTube doesn't publish every language for every video, so the Actor degrades in a defined order rather than returning nothing. It first looks for a transcript matching your requested code (or a regional variant of it, so `en` also matches `en-US`). If that isn't published, it falls back to English. If English isn't there either, it falls back to any auto-generated English track. Failing all three, it takes the first transcript track the video offers in any language.

That means a run against a batch of mixed-language videos returns transcripts for nearly all of them rather than silently dropping the ones that don't match your setting — but it also means a returned transcript is not *guaranteed* to be in the language you asked for. If language purity matters for your downstream pipeline, check the text before trusting the code.

### 📦 What data can you get with YouTube Transcript Scraper?

Every run produces one dataset row per video URL, combining the caption track with the watch-page metadata.

| Result type | Extracted fields | Primary use case |
| --- | --- | --- |
| Transcript segments | `transcript[].text`, `transcript[].timestamp` | Summarization, search indexing, RAG chunking, subtitle files |
| Video metadata | `videoTitle`, `channelName`, `channelSubscription`, `views`, `videoPostDate` | Attribution, ranking, channel-level analysis, dataset filtering |
| Request record | `url` | Joining results back to your input list |

#### Timestamped segments, not a wall of text

The `transcript` field is an array, not a single string. Each element is one caption cue with its own start time formatted as `M:SS` — `"0:42"`, `"12:07"`. This is deliberate and it's the field that does the most work downstream.

Because each line keeps its offset, you can chunk a transcript by time window for a RAG index, deep-link a viewer to the exact moment a phrase was spoken (`&t=` on the watch URL), rebuild an SRT or WebVTT subtitle file, or align a transcript against engagement data to see which minute of a video people actually watched. A flattened transcript string throws all of that away. If you *do* want plain prose, joining the `text` values is one line of code — the reverse is impossible.

```json
{
  "transcript": [
    { "text": "(light cheerful music)", "timestamp": "0:03" },
    { "text": "♪ I don't want a lot for Christmas ♪", "timestamp": "0:10" },
    { "text": "♪ There is just one thing I need ♪", "timestamp": "0:16" }
  ]
}
```

#### Channel and view metadata

Metadata is parsed from the watch page's own `ytInitialPlayerResponse` and `ytInitialData` payloads using a brace-matching JSON extractor rather than a regex, so nested objects and escaped strings inside the blob don't truncate the parse. `views` comes back as a human-formatted string (`"908,020,767 views"`) and `channelSubscription` as YouTube's own abbreviated form (`"13M subscribers"`) — parse them to integers yourself if you need arithmetic. If YouTube serves a stripped page to a particular exit IP, the Actor rotates to a new one and retries up to three times before returning `null` metadata with the transcript still intact.

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

This is the comparison that matters most, because the official API technically has a captions endpoint — it just won't give you other people's captions.

| Feature | YouTube Data API v3 | YouTube Transcript Scraper |
| --- | --- | --- |
| Access requirement | Google Cloud project, API key, OAuth 2.0 client | None — no key, no login, no quota project |
| Third-party captions | `captions.download` only works for videos you own | Reads any public video's published caption track |
| Auto-generated (ASR) captions | Not downloadable via the API | Returned like any other track |
| Quota | Daily units; caption calls are expensive | No Google quota — billed per result on Apify |
| Metadata | Separate `videos.list` call you write and join yourself | Title, channel, subs, views, date in the same row |
| Blocked requests | Your client handles backoff | Automatic residential proxy rotation and retry |
| Output shape | Raw caption file (SBV/SRT) you parse | Normalized `{text, timestamp}` JSON array |

Use the official Data API when you're managing your own channel's captions, uploading subtitle tracks, or need fields the watch page doesn't expose. Use YouTube Transcript Scraper when you need the spoken content of videos you don't own, in bulk, as structured data — which is what most research, summarization, and content-analysis work actually requires.

### 🚀 How to scrape YouTube transcripts with YouTube Transcript Scraper?

1. Open the **youtube-transcript-scraper** Actor in Apify Console and click **Try for free**.
2. Paste one or more YouTube video URLs into `startUrls`. Both `https://www.youtube.com/watch?v=ID` and `https://youtu.be/ID` are accepted.
3. Pick a `language` from the dropdown — English is the default.
4. Click **Start** and watch the run log; each URL is logged as it's processed.
5. Open the **Dataset** tab and export as JSON, CSV, or Excel, or pull it through the Apify API.

A complete run needs exactly one field:

```json
{
  "startUrls": [
    { "url": "https://www.youtube.com/watch?v=aAkMkVFwAoo" }
  ],
  "language": "en"
}
```

#### Running a batch of videos in one job

`startUrls` accepts an array of any length, and every entry is processed within the same run. Videos are handled sequentially, each on its own freshly rotated proxy IP, so a 50-URL batch takes roughly 50× the single-video time rather than finishing in parallel. In testing, a single video completed in about 11 seconds end to end, so plan for roughly nine to ten videos per minute and set your run timeout accordingly on large batches.

Crucially, each video is wrapped in its own error boundary. A private video, a deleted video, a livestream with no caption track, or a malformed URL is logged and skipped — the run continues and the rest of your batch still lands in the dataset. You will not lose 49 transcripts because the 50th URL was bad.

### ⬇️ Input

YouTube Transcript Scraper takes one required field. Everything else has a working default.

| Parameter | Required | Type | Description | Example value |
| --- | --- | --- | --- | --- |
| `startUrls` | **Yes** | array | YouTube video URLs to scrape. Accepts `{"url": "..."}` objects or plain strings. Both `youtube.com/watch?v=` and `youtu.be/` forms work. | `[{"url": "https://www.youtube.com/watch?v=aAkMkVFwAoo"}]` |
| `language` | No | string (select) | Preferred transcript language code. One of `en`, `de`, `hi`, `nl`, `pl`, `tr`, `vi`, `id`, `es`, `fr`, `it`, `pt`, `ru`, `ja`, `ko`, `zh-Hans`, `ar`, `bn`. Default `"en"`. | `"es"` |
| `proxyConfiguration` | No | object | Apify Proxy settings. **Not exposed in the Console form** — settable only through the API or a task's raw input. Defaults to Apify residential proxies, which is the recommended setting. | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}` |

```json
{
  "startUrls": [
    { "url": "https://www.youtube.com/watch?v=aAkMkVFwAoo" },
    { "url": "https://youtu.be/dQw4w9WgXcQ" }
  ],
  "language": "en"
}
```

**Pitfall:** `proxyConfiguration` is read by the Actor but is not declared in its input schema, so it never appears in the Console's input form. If you leave it alone you get residential proxies automatically, which is what you want — YouTube blocks datacenter IPs aggressively for caption requests. You only need to set it explicitly (via API or a saved task) if you want a specific proxy group or country, and setting `useApifyProxy: false` will noticeably increase the rate of empty transcripts.

**Second pitfall:** an invalid URL that contains no extractable video ID is logged as an error and skipped entirely — it produces *no* dataset row at all, rather than a row with null fields. If your output row count is lower than your input URL count, check the run log for `Invalid YouTube URL` lines before assuming the transcripts were missing.

### ⬆️ Output

One dataset row per successfully processed video, in a stable schema across runs. Export as JSON, CSV, or Excel, or fetch it from the Apify API.

#### Scraped results

```json
[
  {
    "url": "https://www.youtube.com/watch?v=aAkMkVFwAoo",
    "videoTitle": "Mariah Carey - All I Want for Christmas Is You (Make My Wish Come True Edition)",
    "channelName": "MariahCareyVEVO",
    "channelSubscription": "13M subscribers",
    "views": "908,020,767 views",
    "videoPostDate": "2019-12-19T21:00:11-08:00",
    "transcript": [
      { "text": "(light cheerful music)", "timestamp": "0:03" },
      { "text": "♪ I don't want a lot for Christmas ♪", "timestamp": "0:10" },
      { "text": "♪ There is just one thing I need ♪", "timestamp": "0:16" },
      { "text": "♪ I don't care about the presents ♪", "timestamp": "0:21" }
    ]
  }
]
```

A note on partial results: metadata and transcript are fetched independently, so a row can legitimately arrive with a full `transcript` and `null` metadata (YouTube served a stripped page), or with complete metadata and an empty `transcript` array (the video genuinely has no captions published, or captions are disabled). Treat `transcript: []` as "no captions on this video," not as an error.

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

- **AI and LLM engineers:** chunk `transcript` by timestamp window and embed it for a retrieval index, so an agent can answer questions grounded in what was actually said in a video and cite the minute it was said.
- **Content and SEO teams:** turn long-form video into blog drafts, show notes, or timestamped chapter markers, and mine the transcript for the phrases your audience already uses.
- **Media researchers and analysts:** scrape a channel's back catalogue and track how the language around a topic shifts over time, using `videoPostDate` and `channelName` to segment.
- **Accessibility and localization teams:** pull existing caption tracks in bulk to audit coverage, spot videos missing captions entirely, or seed a translation workflow.
- **Market and competitor researchers:** extract what competitors say in their own product videos and webinars, with `views` and `channelSubscription` as a rough reach weighting.

### 📈 How do you monitor a YouTube channel's transcripts over time?

Monitoring here means re-running the same URL set on a schedule and diffing, rather than treating each run as a one-off. Transcripts themselves rarely change once published, so the fields worth watching are the metadata: `views` and `channelSubscription` both move continuously, and comparing them across runs turns a static transcript archive into a performance dataset.

The practical workflow is to keep a list of video URLs, schedule YouTube Transcript Scraper to re-run it on a fixed interval, and join each run's output to the previous one on `url`. Rising `views` against a stable transcript tells you which spoken content is actually earning watch time. A transcript that changes between runs means the creator re-uploaded or re-captioned the video — worth flagging in a competitive-intelligence context.

To follow a whole channel rather than a fixed URL list, pair this Actor with a channel or playlist scraper that discovers new video URLs, then feed those URLs in. Apify Console's built-in **Schedule** feature triggers the run on an interval, and a run-completion webhook can push each new batch straight into your own pipeline.

### 🔌 Integrate YouTube Transcript Scraper into your workflow

YouTube Transcript Scraper is callable from anything that can make an HTTP request.

#### 🐍 REST API with Python

```python
import requests

TOKEN = "YOUR_APIFY_TOKEN"
url = f"https://api.apify.com/v2/acts/scrapers-hub~youtube-transcript-scraper/run-sync-get-dataset-items?token={TOKEN}"

payload = {
    "startUrls": [{"url": "https://www.youtube.com/watch?v=aAkMkVFwAoo"}],
    "language": "en",
}

videos = requests.post(url, json=payload).json()

for video in videos:
    full_text = " ".join(seg["text"] for seg in video["transcript"])
    print(video["videoTitle"], "-", len(full_text), "characters")
```

#### 🤖 MCP for AI agents

The Actor is reachable through Apify's Actors MCP Server, so an MCP-compatible client — Claude Desktop, Claude.ai, VS Code — can call it as a tool and ground its answer in a real transcript. Register it with `npx @apify/actors-mcp-server --tools scrapers-hub/youtube-transcript-scraper` with your `APIFY_TOKEN` set, or connect to the hosted endpoint at `https://mcp.apify.com`.

#### ⏰ Scheduled runs and webhooks

Use Apify Console's **Schedule** tab to run the Actor on a recurring interval with no cron server of your own, and attach a webhook on run completion to pull each finished dataset into your warehouse or vector store automatically.

### 💰 Pricing

This Actor is billed per result — one charge per video record pushed to the dataset — on top of Apify platform usage. Videos that fail entirely and produce no row are not charged as results. Current per-result pricing is shown on the Actor's Apify Store page; check there for the live figure rather than relying on any number quoted in documentation.

### ⚖️ Is it legal to scrape YouTube transcripts?

YouTube Transcript Scraper reads only publicly accessible caption tracks and watch-page data — the same content any visitor sees without logging in. Courts have held that scraping publicly available web data does not violate the U.S. Computer Fraud and Abuse Act (*hiQ Labs, Inc. v. LinkedIn Corp.*, 9th Cir. 2019, reaffirmed 2022), though YouTube's own Terms of Service separately govern permitted use of the platform. Transcripts are creative content owned by the uploader: extracting them for analysis, search, and research is a different matter from republishing them verbatim, and copyright still applies to the underlying work. Consult your legal team before redistributing scraped transcripts or using them commercially at scale.

### ❓ Frequently asked questions

#### Does YouTube Transcript Scraper need an API key or Google account?

No. It reads YouTube's public caption tracks and watch pages directly — no API key, OAuth client, Google Cloud project, or login of any kind.

#### Can it download auto-generated captions?

Yes. Auto-generated (ASR) tracks are treated like any other transcript, and are used as an explicit fallback when no manually written track exists in your requested language. This is the main thing the official YouTube Data API cannot do for videos you don't own.

#### What languages are supported?

Eighteen: English, German, Hindi, Dutch, Polish, Turkish, Vietnamese, Indonesian, Spanish, French, Italian, Portuguese, Russian, Japanese, Korean, Simplified Chinese, Arabic, and Bengali. If your chosen language isn't published for a video, the Actor falls back to English, then auto-generated English, then the first available track.

#### Why is the transcript array empty for some videos?

Because that video has no published captions — they were disabled by the uploader, or none were ever generated. An empty `transcript` array with populated metadata is a valid result, not a failure. Livestreams and very new uploads are the most common cases.

#### Why is my metadata null but the transcript populated?

The watch page and the caption track are fetched through separate requests. If a proxy exit IP receives a stripped page, the Actor retries on a fresh IP up to three times, then returns null metadata rather than discarding the transcript it already has.

#### How many videos can I scrape in one run?

There's no hard cap in the input — `startUrls` takes as many URLs as you want to paste. Videos are processed sequentially at roughly nine to ten per minute, so size your run timeout to match the batch.

#### Can I get an SRT or VTT subtitle file out of it?

Not directly, but the output contains everything needed to build one. Each cue's text and start time are preserved, so generating SRT or WebVTT is a short formatting step over the `transcript` array.

#### Does it work with `youtu.be` short links?

Yes. Both `youtube.com/watch?v=ID` and `youtu.be/ID` are parsed for the video ID. Playlist and channel URLs are not — feed those through a playlist or channel scraper first to expand them into video URLs.

#### How does it handle YouTube blocking?

It routes through Apify residential proxies by default and takes a fresh exit IP for every video. When YouTube blocks a request outright, the Actor rotates to a new IP and retries up to three times before giving up on that video and moving to the next.

#### Can I use it with Claude, ChatGPT, or an agent framework?

Yes. It's a plain HTTP endpoint from any framework, and it's registered on Apify's Actors MCP Server so MCP clients can call it directly as a tool.

#### Does one bad URL break the whole run?

No. Every video is processed inside its own error boundary — invalid URLs, private videos, and unexpected failures are logged and skipped while the rest of the batch completes normally.

### 🔗 Related Actors

Pair YouTube Transcript Scraper with the rest of the YouTube toolkit to go from a channel to full-text transcripts in two steps:

- **Youtube Channel Video Scraper** — enumerate every video URL on a channel, then feed them in here
- **Youtube Playlist Scraper** — expand a playlist into video URLs for bulk transcription
- **Youtube Video Downloader** — grab the media file itself alongside its transcript
- **TikTok Subtitles Extractor** — the same timestamped-caption workflow for TikTok videos

### 💬 Your feedback

Found a video where the transcript comes back empty but captions clearly exist, or a metadata field that stopped parsing? Report it through the Issues tab on this Actor's Apify Console page. YouTube changes its watch-page payloads without notice, and those reports are how the extraction logic keeps matching reality.

Prefer email? Contact the team directly at **scraperhubapi@gmail.com**. Include the Actor name, the exact input you used, and the run ID so the issue can be reproduced and fixed quickly.

# Actor input Schema

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

List one or more YouTube video URLs.

## `language` (type: `string`):

Select the language for the transcript.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.youtube.com/watch?v=aAkMkVFwAoo"
    }
  ],
  "language": "en"
}
```

# Actor output Schema

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

Records produced by Youtube Transcript Scraper, stored in the run's default dataset.

# API

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

## JavaScript example

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

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

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

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = { "startUrls": [{ "url": "https://www.youtube.com/watch?v=aAkMkVFwAoo" }] }

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

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

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

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.youtube.com/watch?v=aAkMkVFwAoo"
    }
  ]
}' |
apify call scrapers-hub/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapers-hub/youtube-transcript-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/2yYTbMdTGEMshJ9MR/builds/gdAvvnGTY9m37NSPh/openapi.json
