# YouTube Transcript Scraper - Bulk Channels, Playlists, Search (`mochiboo/youtube-transcript-bulk-scraper`) Actor

Transcripts for YouTube videos, Shorts, whole channels, playlists and search results in one run: transcript text, timestamped segments, optional SRT and WebVTT, plus title, channel, views, duration, publish date and keywords. You pay only for transcripts delivered.

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

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## YouTube Transcript Scraper - bulk transcripts from videos, channels, playlists and search

Get the transcript of a YouTube video, or of every video in a channel, playlist or search result, in one run. Each video becomes one row: the full transcript text, timestamped segments, optional SRT and WebVTT subtitle text, and the video's details (title, channel, views, duration, publish date, category, keywords, description).

- **Paste any mix of inputs.** Video, Shorts and youtu.be links, bare 11-character video IDs, channel URLs (`@handle`, `/channel/UC...`, `/c/...`, `/user/...`), playlist URLs and search queries all go in one run. Video IDs are de-duplicated across sources.
- **Pick your languages.** List language codes in order of preference (`en, es, de`). Human-made captions are used when the video has them, auto-generated captions are the fallback. `en` also matches `en-GB`. If none of your languages exist, the video's default caption track is used and the row says which language you got.
- **Pay only for transcripts delivered.** A video without captions, a removed video, or a video YouTube refuses to serve is never a charged row. Those go to a separate `FAILURES` record with a status and a reason.

### What it does NOT do

- **No translation.** You get the transcript in a language the video already has. YouTube answers translated-caption requests with HTTP 429, so the feature is not offered.
- **No speech-to-text.** A video with no captions at all is reported as `no_captions`; its audio is not transcribed.

### Known limitation: YouTube's "sign in to confirm you're not a bot" check

Some requests are answered with *Sign in to confirm you're not a bot* instead of video data. This actor never signs in, never uses cookies and never retries such a video in the same run. The video is written to `FAILURES` with the status `blocked_login_required` and is not charged.

Measured on 2026-10-19 with the default setting (Apify residential proxy, a new IP for every video): two checks on 2026-10-19 (97 videos requested in total, mixing single videos, channels, playlists and searches). YouTube refused 3 of the 97 (3%), and some videos had no captions. The same list on Apify's datacenter proxy was refused for 50 of 62 videos (81%) on 2026-10-17, so datacenter proxies are not recommended. A refused video is never retried in the same run; run the list again to get it.

### Input

| Field | Default | What it does |
|---|---|---|
| `startUrls` | none (the form is prefilled with two examples) | Videos, Shorts, channels, playlists, or bare video IDs. |
| `searchQueries` | `[]` | Transcribe the top videos of a YouTube search (videos only). |
| `maxVideosPerSource` | `20` (max `5000`) | Cap for each channel, playlist or search. Single video URLs are never capped. |
| `languages` | `["en"]` | Caption languages in order of preference. |
| `preferManualCaptions` | `true` | Prefer human-made captions; turn off to prefer auto-generated ones. |
| `includeSegments` | `true` | Add `segments`: `{start, duration, text}` in seconds. |
| `includeSrt` | `false` | Add `srt`: the transcript as SubRip subtitles. |
| `includeVtt` | `false` | Add `vtt`: the transcript as WebVTT subtitles. |
| `proxyConfiguration` | Apify residential proxy, one IP per video | Proxy used for all requests. |

Provide at least one URL, video ID or search query, otherwise the run fails with a message.

### Output

One dataset row per video that has a transcript. Example (shortened):

```json
{
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
  "channelName": "Rick Astley",
  "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
  "channelUrl": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw",
  "publishDate": "2009-10-24T23:57:33-07:00",
  "category": "Music",
  "durationSeconds": 213,
  "viewCount": 1827615962,
  "isLive": false,
  "description": "The official video for “Never Gonna Give You Up” by Rick Astley. ...",
  "keywords": ["rick astley", "Never Gonna Give You Up", "nggyu", "..."],
  "thumbnailUrl": "https://i.ytimg.com/vi_webp/dQw4w9WgXcQ/sddefault.webp",
  "transcriptLanguage": "en",
  "transcriptLanguageName": "English",
  "isAutoGenerated": false,
  "availableLanguages": [
    {"code": "en", "name": "English", "isAutoGenerated": false},
    {"code": "en", "name": "English (auto-generated)", "isAutoGenerated": true}
  ],
  "wordCount": 487,
  "transcriptText": "[♪♪♪] ♪ We're no strangers to love ♪ ♪ You know the rules and so do I ♪ ...",
  "segments": [
    {"start": 1.36, "duration": 1.68, "text": "[♪♪♪]"},
    {"start": 18.64, "duration": 3.24, "text": "♪ We're no strangers to love ♪"}
  ],
  "sourceType": "video",
  "sourceInput": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "scrapedAt": "2026-10-17T17:40:32.653Z"
}
```

With `includeSrt` / `includeVtt` the row also has `srt` / `vtt` (placed after `segments`); with `includeSegments` off, `segments` is omitted.

Notes on individual fields:

- `publishDate` and `category` come from a second request per video. If that request fails or YouTube refuses it, the row is still delivered with both set to `null`. In our latest test runs it happened for 0 of 61 delivered rows.
- `availableLanguages` lists every caption track the video offers, so you can re-run for another language.
- `keywords` is an empty array when the video has none.
- `sourceType` / `sourceInput` tell you which of your inputs (`video`, `channel`, `playlist`, `search`) produced the row. A video found by several inputs appears once, under the first.

### The FAILURES record

Everything that did not become a row is saved in the key-value store under the key `FAILURES`, as a JSON array of `{input, videoId, status, reason}`. The run still finishes as succeeded, and the last log line reads like `OK 5 / failed 26 by status: blocked_login_required 25, no_captions 1`.

| status | meaning |
|---|---|
| `blocked_login_required` | YouTube asked to sign in to confirm you're not a bot. Not retried in the same run. |
| `unavailable` | Removed, private or otherwise not playable; `reason` has YouTube's wording. |
| `no_captions` | The video has no caption track. |
| `empty_transcript` | The caption track came back empty or unreadable. |
| `source_failed` | A channel, playlist or search could not be read (or was empty); `videoId` is `null`. |
| `unsupported_url` | The input is not a YouTube video, Shorts, channel or playlist URL. |
| `request_failed` | A request kept failing after 3 retries (HTTP 429/5xx or network error). |

### Pricing

Pay per result, at the rate shown in the pricing panel of this page, plus a very small per-run start fee. You are charged only for the results delivered.

Proxy cost (measured on 2026-10-19 over 66 to 97 videos in several runs): about 70 to 130 KB of residential traffic per video requested, which is $0.0006 to $0.0010 per video at Apify's residential rate of $8 per GB. That is roughly $0.8 to $1.0 of proxy cost per 1,000 transcripts delivered, counting videos without captions and the channel, playlist and search pages. Long videos and videos with more captions cost more.

### Search keywords

youtube transcript, youtube transcript scraper, youtube subtitles, youtube captions download, bulk youtube transcripts, channel transcripts, playlist transcripts, youtube to text, srt download.

# Actor input Schema

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

Video, Shorts, youtu.be, channel (@handle, /channel/UC..., /c/, /user/) or playlist URLs. Bare 11-character video IDs work too.

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

Transcribe the top videos of a YouTube search.

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

Single video URLs are always processed. Hard cap 5000.

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

Language codes in order of preference, e.g. en, es, de. 'en' also matches en-GB. Falls back to the video's default caption track.

## `preferManualCaptions` (type: `boolean`):

Use a human-made caption track when the video has one in a matching language, and fall back to auto-generated captions. Turn off to prefer auto-generated captions.

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

Add a segments array of {start, duration, text} objects (seconds) to every row.

## `includeSrt` (type: `boolean`):

Add an srt field with the transcript as SubRip subtitles.

## `includeVtt` (type: `boolean`):

Add a vtt field with the transcript as WebVTT subtitles.

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

Apify residential proxy is on by default (each video gets its own IP). YouTube asks most datacenter IPs to sign in, so datacenter proxies are not recommended. You can pick another group or your own proxy URLs.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/@RickAstleyYT"
  ],
  "searchQueries": [],
  "maxVideosPerSource": 20,
  "languages": [
    "en"
  ],
  "preferManualCaptions": true,
  "includeSegments": true,
  "includeSrt": false,
  "includeVtt": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One row per video that has a transcript: transcript text, timestamped segments, optional SRT and WebVTT, plus title, channel, views, duration and publish date.

## `failures` (type: `string`):

The FAILURES record in the default key-value store: videos without captions, refused by YouTube, or unsupported URLs. Not charged.

# 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": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/@RickAstleyYT"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mochiboo/youtube-transcript-bulk-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/QGcgufA9Z73OjfSAn/builds/Wh6ITWaGaUHbWcaoY/openapi.json
