# YouTube Channel Transcript Scraper: Bulk Subtitles (`kyungminlee/youtube-transcript-scraper`) Actor

Turn whole YouTube channels or playlists into text: subtitles for hundreds of videos per run, no API key or browser. Each video returns plain text, timed segments, language, title, channel, views, likes and publish date; auto-translation optional. JSON/CSV, API or scheduled runs.

- **URL**: https://apify.com/kyungminlee/youtube-transcript-scraper.md
- **Developed by:** [Kyungmin Lee](https://apify.com/kyungminlee) (community)
- **Categories:** Videos, AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 transcripts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

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

### What does YouTube Transcript Scraper do?

**YouTube Transcript Scraper** turns videos, whole channels or playlists into text in bulk: paste `@handles`, channel or playlist URLs and get the subtitles of hundreds of videos in one run, with no YouTube account, API key or browser. Pick preferred languages, prefer human captions over auto-generated ones, or let YouTube auto-translate.

Each video is one record with `videoId`, `url`, `title`, `channel`, `durationSeconds`, `viewCount`, `likeCount`, `publishDate`, `language`, `isAutoGenerated`, `availableLanguages`, `text` (clean plain text), `segments` (start, duration, text) and `wordCount`. Export JSON/CSV, feed the API into RAG or summarisation pipelines, or schedule the Actor to transcribe new uploads as they appear.

### Known limitation: datacenter IPs and YouTube bot checks

YouTube increasingly answers **"Sign in to confirm you're not a bot"** when a *recent* video is requested from a datacenter IP address — and that includes Apify Proxy's datacenter pool. Older, well-known videos usually still work, but a bulk run over a channel's latest uploads can fail on every video. What this Actor does about it:

- **Failed videos are never charged.** A bot-checked, caption-less or unavailable video produces an `error` item and costs nothing — including the per-video listing fee for channels and playlists, which is settled only after that video's transcript succeeded.
- **Bring your own residential proxies.** Under *Proxy configuration → Custom proxies* paste one or more residential proxy URLs (`http://user:pass@host:port`). They take precedence over Apify Proxy and are rotated between requests. This is the recommended setup for channel-wide runs.
- **Failure reasons are summarised** at the end of the log and in the `SUMMARY` record of the key-value store (`botCheck`, `noCaptions`, `unavailable`, `other`), so you can tell an IP block from a video that simply has no subtitles.
- If YouTube blocks five videos in a row, the run stops early instead of burning through your budget.

### Why use YouTube Transcript Scraper?

- **Feed AI and RAG pipelines**: turn hours of video into text for summarization, Q\&A, embeddings or fine-tuning.
- **Content repurposing**: draft blog posts, newsletters, show notes and social snippets from your videos.
- **Research and monitoring**: search what competitors, creators or news channels actually say; track mentions of products or topics.
- **Accessibility and localization**: get subtitles in the language you need, translated by YouTube on the fly.
- **Bulk workflows**: transcribe the latest 200 videos of a channel with one input.

### How to get YouTube transcripts

1. Paste **video URLs** (watch, youtu.be, Shorts, embed) or IDs — or **channel handles/URLs** and **playlist URLs** for bulk extraction.
2. Set **Preferred languages** (default `en`). Turn on **Translate to** if you want another language.
3. Click **Start**. Each video becomes one dataset item with `text` and `segments`.
4. Download as JSON, CSV, Excel or fetch via API; connect to Sheets, Make or your own code.

### Input

| Field | Default | Description |
|---|---|---|
| `videoUrls` | `["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]` | Video URLs or IDs |
| `channelUrls` | – | `@handle`, channel URL or `UC…` ID; newest videos first |
| `channelSource` | `uploads` | `uploads` (all), `videos`, `shorts`, `streams` |
| `maxVideosPerChannel` | `20` | Newest N videos per channel |
| `playlistUrls` | – | Public playlists |
| `maxVideosPerPlaylist` | `50` | First N videos of each playlist |
| `languages` | `["en"]` | Preferred language codes, prefix matching (`en` → `en-GB`) |
| `preferManual` | `true` | Human subtitles before auto-generated |
| `fallbackToAnyLanguage` | `true` | Use the best available track if preferred languages are missing |
| `translateTo` | – | Target language for YouTube auto-translation |
| `includeMetadata` | `true` | Views, likes, publish date (one extra request per video) |
| `includeSegments` | `true` | Timed segments in addition to plain text |
| `proxyConfiguration` | Apify Proxy on | Keep on. For recent videos, add your own residential proxies under *Custom proxies* — see *Known limitation* above |

Example — transcribe a channel's latest 50 videos in English or Korean:

```json
{
  "channelUrls": ["@mkbhd"],
  "maxVideosPerChannel": 50,
  "languages": ["en", "ko"],
  "includeSegments": false
}
```

### Output

One item per video:

```json
{
  "type": "transcript",
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Rick Astley - Never Gonna Give You Up (Official Video)",
  "channel": "Rick Astley",
  "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
  "durationSeconds": 213,
  "viewCount": 1800000000,
  "likeCount": 19400000,
  "publishDate": "2009-10-24",
  "language": "en",
  "languageName": "English",
  "isAutoGenerated": false,
  "availableLanguages": [{ "code": "en", "name": "English", "kind": "manual", "isTranslatable": true }, { "code": "en", "name": "English (auto-generated)", "kind": "auto", "isTranslatable": true }],
  "segmentCount": 61,
  "wordCount": 380,
  "text": "We're no strangers to love. You know the rules and so do I...",
  "segments": [{ "start": 18.6, "duration": 3.2, "text": "We're no strangers to love" }]
}
```

Videos without captions or that are unavailable are reported as `{"type": "error", "videoId": ..., "error": "NoCaptionsAvailable"}` items and are **not charged**.

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

### Data fields

| Field | Description |
|---|---|
| `videoId`, `url`, `title`, `channel`, `channelId` | Video identity |
| `durationSeconds`, `viewCount`, `likeCount`, `publishDate`, `thumbnailUrl`, `description`, `keywords`, `isShort`, `isLive` | Metadata |
| `language`, `languageName`, `isAutoGenerated`, `translatedTo` | Which subtitle track was used |
| `availableLanguages` | All subtitle tracks YouTube offers for the video |
| `text`, `wordCount` | Plain transcript |
| `segments`, `segmentCount` | `{start, duration, text}` in seconds |
| `sourceChannel`, `sourcePlaylist` | Present for videos found through a channel/playlist |

### How much does it cost to get YouTube transcripts?

Pricing is **pay per event**: one small fee per transcript fetched and a smaller fee per video listed from a channel or playlist. The listing fee is only charged for videos whose transcript was actually delivered, so failed videos (bot check, no captions, private, unavailable) cost nothing at all. A typical video takes 1–3 seconds, so transcribing 100 videos finishes in a few minutes.

### Tips

- For big channels set `includeSegments: false` and `includeMetadata: false` to get compact, fast output.
- `languages: ["en"]` matches `en`, `en-US` and `en-GB`. Put several codes in order of preference.
- Auto-translation (`translateTo`) is rate-limited by YouTube; the Actor rotates proxies and backs off automatically, but very large translated runs may need to be split.
- Schedule the Actor with a channel URL and `maxVideosPerChannel: 5` to transcribe every new upload automatically.

### FAQ and disclaimers

**Does it transcribe audio?** No. It returns the subtitles YouTube already has (manual or auto-generated). Videos without any captions return an error item.

**Why did a video fail with BotCheckRequired?** YouTube occasionally challenges datacenter IPs. The Actor retries through a fresh proxy; if it persists, rerun later.

**Is it legal?** The Actor collects publicly available subtitle data that YouTube serves to any viewer. You are responsible for how you use it and for complying with YouTube's terms and applicable copyright law. This is an unofficial tool, not affiliated with YouTube or Google.

**Something broke?** YouTube changes its internals regularly. Open an issue in the **Issues** tab with the video URL and it will be fixed quickly. Custom features (keyword search inside transcripts, chaptering, summaries) are available on request.

# Actor input Schema

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

YouTube video URLs (watch, youtu.be, shorts, embed, live) or bare 11-character video IDs.

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

Channels to transcribe in bulk: `@handle`, channel URL or `UC...` ID. The newest videos are taken (see *Max videos per channel*).

## `channelSource` (type: `string`):

Which channel tab to list.

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

Newest N videos per channel.

## `playlistUrls` (type: `array`):

Public playlists (`PL...`) to transcribe.

## `maxVideosPerPlaylist` (type: `integer`):

First N videos of each playlist.

## `languages` (type: `array`):

Language codes in order of preference (`en`, `en-US`, `ko`, `de`...). Prefix matching is used (`en` matches `en-GB`).

## `preferManual` (type: `boolean`):

Use manually uploaded subtitles before auto-generated ones when both exist.

## `fallbackToAnyLanguage` (type: `boolean`):

If none of the preferred languages exists, return the best available track instead of failing.

## `translateTo` (type: `string`):

Optional target language code (`en`, `es`, `ko`...). Uses YouTube's own auto-translation of the selected track. Rate-limited by YouTube; keep Apify Proxy on.

## `includeMetadata` (type: `boolean`):

Title, channel, duration, views, likes, publish date, thumbnail. Costs one extra page request per video; turn off for very large runs.

## `includeSegments` (type: `boolean`):

Add `segments` (start, duration, text) to each item in addition to the plain `text`.

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

YouTube rate-limits and bot-checks single IPs, so Apify Proxy is on by default and sessions rotate automatically. Known limitation: YouTube often answers "Sign in to confirm you're not a bot" for recent videos when the request comes from any datacenter IP (including Apify Proxy). For reliable bulk runs, enter your own residential proxy URLs under "Custom proxies" (proxyUrls) — they take precedence and are rotated. Videos that fail the bot check are reported as error items and are never charged.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "channelSource": "uploads",
  "maxVideosPerChannel": 20,
  "maxVideosPerPlaylist": 50,
  "languages": [
    "en"
  ],
  "preferManual": true,
  "fallbackToAnyLanguage": true,
  "includeMetadata": true,
  "includeSegments": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ],
    "languages": [
        "en"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kyungminlee/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 = {
    "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
    "languages": ["en"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("kyungminlee/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 '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "languages": [
    "en"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call kyungminlee/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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