# Bulk YouTube Transcript Scraper (`asif-ali-dev/youtube-transcript-scraper`) Actor

Transcripts for hundreds of YouTube videos in one run, not one run per video. Free. Every video returns a row: successes carry the text, failures carry a reason code you can act on. Segments, plain text, SRT and VTT.

- **URL**: https://apify.com/asif-ali-dev/youtube-transcript-scraper.md
- **Developed by:** [Asif Ali](https://apify.com/asif-ali-dev) (community)
- **Categories:** Open source
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## Bulk YouTube Transcript Scraper

**Hundreds of videos in one run. Free.**

Most YouTube transcript tools take one video per run. Fine if you want one
video. Painful if you have a playlist, a channel backlog, or a research corpus —
that's one run per video, every time, and you pay per run.

Paste your whole list. Get one dataset back.

```
500 videos  →  1 run  →  1 dataset
```

### What you get for every video

A row. Always. That sounds obvious until you've used a scraper that silently
returns 430 rows for 500 inputs and leaves you to work out which 70 are missing
and why.

Successful rows carry the transcript. Failed rows carry a **stable error code**:

| Code | What it means | Worth retrying? |
|---|---|---|
| `CAPTIONS_DISABLED` | The uploader turned captions off | **No** — never will work |
| `NO_TRANSCRIPT_IN_LANGUAGE` | No track in the languages you asked for | **No** |
| `VIDEO_UNAVAILABLE` | Private, deleted, or an invalid id | **No** |
| `AGE_RESTRICTED` | Requires a signed-in account | **No** |
| `IP_BLOCKED` | YouTube throttled us | **Yes** — retry later |

Four of those are facts about the video that no tool on earth can fix. One is
temporary. Knowing which is which is the difference between a pipeline you can
automate and a mystery you have to babysit.

### Output formats

Pick any combination — `segments`, `text`, `srt`, `vtt`.

```json
{
  "videoId": "dQw4w9WgXcQ",
  "success": true,
  "languageCode": "en",
  "isAutoGenerated": false,
  "segmentCount": 61,
  "durationSeconds": 211.32,
  "characterCount": 2089,
  "text": "...",
  "segments": [{ "start": 0.0, "duration": 2.5, "text": "..." }]
}
```

Subtitle files come out ready to use — SRT for editors, VTT for the web. Plain
text is single-line and clean, ready to drop into a RAG index or an LLM prompt
without further tidying.

### Input — paste links however you have them

Watch URLs, `youtu.be` share links, embeds, Shorts, live URLs and bare
11-character video ids. In a list, or pasted as a block of text, one per line.
Mixed formats are fine. Duplicates are removed automatically, across both
fields.

**Options worth knowing:**

- **`languages`** — ordered preference, e.g. `["en","es"]`. Asking for `es`
  matches a native `es-419` track instead of paying for a machine translation.
- **`allowAutoGenerated`** — human-written captions are always preferred over
  machine ones. Set `false` to reject auto-generated captions entirely, which
  matters if you're quoting rather than summarising.
- **`allowTranslated`** — off by default, deliberately. YouTube blocks its
  translation endpoint far harder than plain caption fetches, so switching this
  on lowers your success rate.
- **`concurrency`** — 1 to 20. Faster, but trips YouTube's rate limit sooner.

### Built to be cheap to run

YouTube's client key is cached rather than refetched for every video, cutting
transfer from roughly **1.5 MB to 0.2 MB per video** after the first. On a
1,000-video run that's over a gigabyte of proxy traffic you don't pay for — and
it halves the requests per video, so you hit YouTube's rate limit far later.

### Honest limits

YouTube rate-limits caption downloads per IP and answers HTTP 429 after roughly
twenty fetches from one address. This Actor rotates its proxy session on every
429 and retries up to four times per video.

**Measured success rate on Apify datacenter proxies: 75–88%.** Residential
proxies should take that close to 99%. `IP_BLOCKED` rows are not broken videos —
re-run just those ids and they usually come back.

Use a proxy. It's on by default. Without one, expect failures past about twenty
videos.

# Actor input Schema

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

YouTube video links. Watch, share, embed, shorts and live URLs all work, as do bare 11-character video ids.

## `videoUrlsText` (type: `string`):

A simpler alternative to the field above. Duplicates across both fields are removed.

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

Ordered by preference, as ISO codes such as en, es, de. A request for 'es' will match a native 'es-419' track rather than paying for a translation.

## `formats` (type: `array`):

Which representations to include on each result.

## `allowAutoGenerated` (type: `boolean`):

Manually written captions are always preferred. Turn this off to accept only human-written transcripts.

## `allowTranslated` (type: `boolean`):

Off by default. YouTube blocks its translation endpoint far more aggressively than plain caption fetches, so enabling this lowers your success rate.

## `concurrency` (type: `integer`):

Higher is faster but trips YouTube's per-IP rate limit sooner.

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

Strongly recommended. YouTube answers HTTP 429 on the caption endpoint after roughly twenty fetches from one IP; rotating proxy sessions is what keeps a large run alive. Datacenter proxies are sufficient and far cheaper than residential for this workload.

## Actor input object example

```json
{
  "videoUrls": [
    {
      "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    },
    {
      "url": "https://www.youtube.com/watch?v=kJQP7kiw5Fk"
    },
    {
      "url": "https://www.youtube.com/watch?v=JGwWNGJdvx8"
    },
    {
      "url": "https://www.youtube.com/watch?v=OPf0YbXqDm0"
    }
  ],
  "languages": [
    "en"
  ],
  "formats": [
    "segments",
    "text"
  ],
  "allowAutoGenerated": true,
  "allowTranslated": false,
  "concurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per requested video. Successful rows carry the transcript; failed rows carry a stable errorCode explaining why.

## `transcriptsCsv` (type: `string`):

The same rows as a spreadsheet-friendly download.

## `runSummary` (type: `string`):

Counts of requested, succeeded, failed and charged, plus any inputs that could not be parsed as a YouTube video.

# 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": [
        {
            "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
        },
        {
            "url": "https://www.youtube.com/watch?v=kJQP7kiw5Fk"
        },
        {
            "url": "https://www.youtube.com/watch?v=JGwWNGJdvx8"
        },
        {
            "url": "https://www.youtube.com/watch?v=OPf0YbXqDm0"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("asif-ali-dev/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": [
        { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
        { "url": "https://www.youtube.com/watch?v=kJQP7kiw5Fk" },
        { "url": "https://www.youtube.com/watch?v=JGwWNGJdvx8" },
        { "url": "https://www.youtube.com/watch?v=OPf0YbXqDm0" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("asif-ali-dev/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": [
    {
      "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    },
    {
      "url": "https://www.youtube.com/watch?v=kJQP7kiw5Fk"
    },
    {
      "url": "https://www.youtube.com/watch?v=JGwWNGJdvx8"
    },
    {
      "url": "https://www.youtube.com/watch?v=OPf0YbXqDm0"
    }
  ]
}' |
apify call asif-ali-dev/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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