# YouTube Transcripts Scraper — Bulk Transcripts & Subtitles (`astravalabs/youtube-transcripts-scraper`) Actor

Turn YouTube videos, whole channels and playlists into clean transcripts: full text plus timestamped lines, in your preferred language with fallback to the original, with title, channel, length and views. SRT/VTT export, no charge for videos without captions.

- **URL**: https://apify.com/astravalabs/youtube-transcripts-scraper.md
- **Developed by:** [astravalabs](https://apify.com/astravalabs) (community)
- **Categories:** Videos, AI, Social media
- **Stats:** 2 total users, 1 monthly users, 88.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 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.

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 Transcripts Scraper — Bulk Transcripts & Subtitles

Turn YouTube videos, whole channels and playlists into clean transcripts: the
full text plus timestamped lines, in your preferred language with a fallback
to the video's original language, together with the title, channel, length
and view count. Up to 10,000 videos per run. Proxies and retries are handled
for you.

Use it to feed RAG pipelines and AI agents, repurpose videos into articles,
research interviews and lectures at scale, or build subtitle files.

### Quick start

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=arj7oStGLkU",
    "https://youtu.be/dQw4w9WgXcQ",
    "jNQXAC9IVRw"
  ],
  "languages": ["en"]
}
```

A channel's or a playlist's latest videos:

```json
{
  "videos": [
    "https://www.youtube.com/@3blue1brown",
    "https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi",
    "https://www.youtube.com/@veritasium/shorts"
  ],
  "maxVideosPerSource": 100
}
```

`videos` takes watch URLs, `youtu.be` links, Shorts, embed and live URLs, bare
11-character video IDs, **channel URLs** (`/@handle`, `/channel/UC…`,
`/c/…`, `/user/…`), and **playlist URLs**. You can mix them freely in one
run.

- **Channels and playlists** are listed newest first, up to
  `maxVideosPerSource` videos each. Each row's `source` field says which
  channel or playlist it came from.
- **Channel tabs:** a channel's Videos tab is used by default. Pick Shorts
  or Live streams with `channelContent`, or paste the tab's own URL (e.g.
  `…/@veritasium/shorts`).
- **Duplicates** are removed, including a video that is both pasted
  directly and listed in a channel.
- **Bad entries** are skipped and listed in the run report, so one doesn't
  fail the batch. Search URLs are not supported yet.

### What you get

One row per video:

```json
{
  "videoId": "arj7oStGLkU",
  "url": "https://www.youtube.com/watch?v=arj7oStGLkU",
  "title": "Inside the Mind of a Master Procrastinator | Tim Urban | TED",
  "channelName": "TED",
  "channelId": "UCAuUUnT6oDeKwE6v1NGQxug",
  "lengthSeconds": 844,
  "viewCount": 62000128,
  "language": "en",
  "languageName": "English",
  "isAutoGenerated": false,
  "availableLanguages": [
    {"language": "en", "name": "English", "isAutoGenerated": false},
    {"language": "en", "name": "English (auto-generated)", "isAutoGenerated": true}
  ],
  "text": "So in college, I was a government major, which means I had to write a lot of papers. ...",
  "wordCount": 2277,
  "segmentCount": 315,
  "segments": [
    {"start": 12.645, "duration": 1.37, "text": "So in college,"},
    {"start": 15.349, "duration": 1.564, "text": "I was a government major,"}
  ],
  "subtitles": null,
  "description": "...",
  "keywords": ["TED Talk", "TED Talks", "Tim Urban", "procrastination"],
  "thumbnailUrl": "https://i.ytimg.com/vi_webp/arj7oStGLkU/sddefault.webp",
  "collectedAt": "2026-10-06T04:08:16+00:00"
}
```

The example is real data from a collected transcript, abridged: `text`,
`segments`, `availableLanguages` (50 entries) and `description` are shortened.

### Options

| Field | Default | What it does |
|---|---|---|
| `maxVideosPerSource` | `50` | How many of each channel's or playlist's most recent videos to collect (up to 5,000). |
| `channelContent` | `["videos"]` | Channel tabs to list: `videos`, `shorts`, `streams`. |
| `languages` | `["en"]` | Language codes in order of preference. `en` also matches `en-US` and `en-GB`. The first one a video has is used. |
| `fallbackToOriginalLanguage` | `true` | If a video has none of your languages, collect its original-language transcript. Turn off to skip it instead (skipped videos are not charged). |
| `includeAutoGenerated` | `true` | Most videos only have YouTube's auto-generated captions. Turn off for creator-uploaded captions only. Uploaded captions are always preferred when both exist. |
| `includeSegments` | `true` | Timestamped lines. Turn off for smaller rows when you only need `text`. |
| `subtitleFormat` | `none` | `srt` or `vtt` adds a ready-to-use subtitle file in `subtitles`. Cues are clipped so they never overlap. |
| `maxTranscripts` | `10000` | Whole-run limit. |

### Pricing

**$3 per 1,000 transcripts** ($0.003 each), plus Apify's small Actor-start
fee. Video details (title, channel, length, views, description, keywords) are
included at no extra cost.

### You only pay for transcripts

A row is written only when a transcript was collected. Videos where the
uploader disabled transcripts, removed or private videos, age-restricted
videos, and videos without your requested language are reported in the run
report and **never charged**.

### Reliability

Measured on 370 real videos in more than ten languages (October 2026):

- **99% of videos that have a transcript were delivered** (312 of 315). Each
  video is retried on a fresh residential connection when YouTube asks to
  "confirm you're not a bot", which is what makes cheaper datacenter-based
  scrapers fail.
- Transcript text was identical to YouTube's own caption track on every
  video we compared it on, and every "transcripts disabled" verdict was
  confirmed independently.
- About 100 videos per minute.
- Channels and playlists: 300 videos listed from one channel in 10 pages,
  and 298 of them delivered (99.3%). Playlists were complete against their
  stated video count, except for videos YouTube hides from logged-out
  viewers.

### Run report

The `RUN_REPORT` record in the run's key-value store lists each channel and
playlist (how many videos were found and whether the limit was reached, or
"channel not found"), then every video with its
outcome — collected, transcripts disabled, unavailable, private,
age-restricted, language not available — the languages it does have, and how
many attempts it took. Use it to see exactly why a video has no row.

### Notes

- Auto-generated transcripts are YouTube's own speech recognition, served as
  is. Punctuation and accuracy vary by video.
- Translation into other languages is not offered: YouTube refuses automated
  translation requests.
- Only public videos are supported. No login, cookies or API key are needed.
- Transcripts are creators' content. Respect copyright and YouTube's terms in
  how you use them.

# Actor input Schema

## `videos` (type: `array`):

YouTube video URLs or 11-character IDs, channel URLs (https://www.youtube.com/@TED, /channel/UC..., or a channel's /shorts or /streams tab) and playlist URLs. Channels and playlists are listed newest first, up to 'Videos per channel or playlist'. Duplicates are removed.

## `maxVideosPerSource` (type: `integer`):

How many of each channel's or playlist's most recent videos to collect. Videos without captions are skipped and not charged.

## `channelContent` (type: `array`):

Which tabs of a channel to list. A /shorts or /streams channel URL lists just that tab.

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

Language codes in order of preference, such as en, es, pt-BR. 'en' also matches en-US and en-GB. The first language a video has is used.

## `fallbackToOriginalLanguage` (type: `boolean`):

When a video has none of your preferred languages, collect its original-language transcript instead. Turn off to skip such videos (they are not charged).

## `includeAutoGenerated` (type: `boolean`):

Most videos only have YouTube's auto-generated captions. Turn off to collect creator-uploaded captions only. Uploaded captions are always preferred when both exist.

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

Adds a 'segments' list with start time, duration and text per line. Turn off for smaller rows when you only need the full text.

## `subtitleFormat` (type: `string`):

Also return the transcript as a ready-to-use subtitle file in a 'subtitles' field.

## `maxTranscripts` (type: `integer`):

Whole-run limit on transcripts collected. Videos without a transcript do not count.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=arj7oStGLkU"
  ],
  "maxVideosPerSource": 50,
  "channelContent": [
    "videos"
  ],
  "languages": [
    "en"
  ],
  "fallbackToOriginalLanguage": true,
  "includeAutoGenerated": true,
  "includeSegments": true,
  "subtitleFormat": "none",
  "maxTranscripts": 10000
}
```

# Actor output Schema

## `transcripts` (type: `string`):

The dataset: one row per video with full text, timestamped lines, language and video details.

## `runReport` (type: `string`):

Per-video outcome (collected, transcripts disabled, unavailable, language not available) and request counts.

# 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 = {
    "videos": [
        "https://www.youtube.com/watch?v=arj7oStGLkU"
    ],
    "languages": [
        "en"
    ]
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,astravalabs/youtube-transcripts-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/o0SKz6F2aSB0TwjXu/builds/1WGQdfbXHJoxDdxmL/openapi.json
