# YouTube Transcript Batch Scraper (`dami0089/youtube-transcript-batch-scraper`) Actor

Transcripts de YouTube en batch: hasta 50 videos por corrida con metadata, segmentos con timestamps y texto completo. Para RAG, agentes IA y SEO. $0.003 por video.

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

## Pricing

$3.00 / 1,000 video 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 Transcript Batch Scraper: captions in bulk

Get **full transcripts from up to 50 public YouTube videos in a single run** — with video metadata, timestamped segments, and ready-to-use plain text. Built for RAG pipelines, AI agents, content repurposing, and SEO research.

Single-video scrapers force you to start one run per video. This actor takes a list of URLs and returns one dataset item per video, each billed as a single $0.003 event.

### What it does

For each video URL it returns:

- **Metadata** — title, channel name + ID, duration (seconds), view count
- **Caption track list** — every language track YouTube offers for the video
- **Segments** — `[{ start, dur, text }]` with second-precision timestamps
- **Full text** — the whole transcript joined into one string, ready for embeddings, prompts, or summaries
- **Language handling** — request an ISO 639-1 language (`en`, `es`, …). If the video has a native track in that language you get it; if not, the actor uses YouTube's machine translation when available; otherwise it falls back to the native track and tells you which language it delivered via the `language` field
- Videos with no captions (or private/restricted/deleted ones) don't break the run — they come back as metadata-only items with `hasCaptions: false`

No browser, no login, no API key. Pure HTTP against YouTube's internal player + timedtext endpoints, with retries, residential-proxy support, and 5-way concurrency — each video takes seconds.

### Who it's for

- **RAG & AI agents** — feed video knowledge into embeddings or context windows with timestamps intact
- **Content repurposing** — turn videos into blog posts, threads, newsletters, shorts scripts
- **SEO & market research** — mine what competitors actually say in their videos, in bulk
- **Accessibility/QA teams** — audit captions across a whole channel backlog in one run

### How to use

1. Paste 1–50 public YouTube video URLs (`watch`, `youtu.be`, `embed`, `shorts`, `live` links all work).
2. Optionally set `targetLanguage` (e.g. `es`), `maxItems` to cap the run, and `includeMetadata`.
3. Run. Each video becomes one row in the dataset; download as JSON/CSV or pull via API.

#### Example input

```json
{
  "videoUrls": ["https://www.youtube.com/watch?v=aqz-KE-bpKQ"],
  "targetLanguage": "en",
  "maxItems": 5,
  "includeMetadata": true
}
```

#### Example output (real run, trimmed)

Input video: TED talk "How to stay calm when you know you'll be stressed" (`8jPQjjsBbIc`), 260 segments:

```json
{
  "videoUrl": "https://www.youtube.com/watch?v=8jPQjjsBbIc",
  "videoId": "8jPQjjsBbIc",
  "title": "How to stay calm when you know you'll be stressed | Daniel Levitin | TED",
  "channelName": "TED",
  "channelId": "UCAuUUnT6oDeKwE6v1NGQxug",
  "durationSecs": 740,
  "viewCount": 20030865,
  "language": "en",
  "isAutoGenerated": false,
  "hasCaptions": true,
  "availableTracks": [
    { "language": "ar", "name": "Arabic" },
    { "language": "en", "name": "English" },
    { "language": "es", "name": "Spanish" }
  ],
  "segments": [
    { "start": 13.24, "dur": 2.56, "text": "A few years ago, I broke into my own house." },
    { "start": 16.88, "dur": 1.216, "text": "I had just driven home," },
    { "start": 18.096, "dur": 4.904, "text": "it was around midnight in the dead of Montreal winter," }
  ],
  "fullText": "A few years ago, I broke into my own house. I had just driven home, it was around midnight...",
  "targetLanguage": "en"
}
```

(`segments` holds all 260 cues, `fullText` the complete ~12-minute talk; truncated here for readability. Set `includeMetadata: false` for a lighter payload: `fullText` and `availableTracks` come back `null` while `segments` are always included.)

A video without captions (e.g. Big Buck Bunny `aqz-KE-bpKQ`, which ships no caption tracks) returns:

```json
{
  "videoUrl": "https://www.youtube.com/watch?v=aqz-KE-bpKQ",
  "videoId": "aqz-KE-bpKQ",
  "title": "Big Buck Bunny 60fps 4K - Official Blender Foundation Short Film",
  "channelName": "Blender",
  "channelId": "UCSMOQeBJ2RAnuFungnQOxLg",
  "durationSecs": 635,
  "viewCount": 23412167,
  "language": null,
  "isAutoGenerated": null,
  "hasCaptions": false,
  "availableTracks": [],
  "segments": [],
  "fullText": null,
  "targetLanguage": "en"
}
```

### Languages & translation

- `targetLanguage` accepts ISO 639-1 codes (`en`, `es`, `pt`, `fr`, …).
- Priority: exact native track → same-base-language track (`en` matches `en-US`) → machine translation off a translatable track → native track fallback.
- The `language` field always tells you what you actually got; `isAutoGenerated` tells you whether the source track is YouTube's auto-captions; `targetLanguage` echoes your request.
- Machine translation depends on YouTube offering it for that video/track. When it isn't available you still get the native transcript (never an empty run).

### Pricing

Pay-per-event, one event type:

| Event | Content | Price |
|---|---|---|
| `transcript-item` | One processed video: metadata + transcript (or metadata-only when captions don't exist) | **$0.003** |

- **1,000 videos ≈ US$ 3.00.** A 50-video run costs at most **US$ 0.15**.
- Every submitted video URL produces exactly one dataset item = one event — including `hasCaptions: false` items (they still cost a caption lookup + metadata fetch).
- Invalid (non-YouTube) URLs and exact duplicates are skipped **free of charge**.
- Control spend with `maxItems` (0 = all URLs). The actor also stops by itself when your max-charge budget is hit, so a run can never overspend.

### Limitations (honest)

- **Only public videos with captions yield text.** Music-only films, videos whose owner disabled captions, private/deleted/age-restricted videos, and live streams return `hasCaptions: false` with whatever metadata is available.
- **Translation is best-effort.** YouTube throttles its auto-translation endpoint aggressively; when it's unavailable the actor transparently returns the native track.
- **Captions belong to their creators.** The actor only reads the public caption tracks YouTube itself serves — same content a viewer sees when enabling subtitles. Respect creators' rights and YouTube's Terms when reusing text.
- **YouTube may throttle datacenter IPs.** The actor retries with exponential backoff and routes everything through your proxy when you configure one — residential proxies are recommended for large batches.

### FAQ

**How is this different from single-video transcript scrapers?**
One run processes up to 50 videos instead of one, returns metadata + track list + segments + full text (not just raw cues), and charges per video at a flat micropayment.

**Why did a video come back with `hasCaptions: false`?**
YouTube offers no caption tracks for it (common for music/animation uploads), or the video is private, restricted, or gone. The rest of the batch is unaffected.

**The `language` I got isn't the one I asked for — why?**
The video has no track (or translatable track) in your `targetLanguage`, so the actor returned the native track. Check `availableTracks` to see what exists.

**Do I need a proxy?**
Not for small runs. For dozens of videos per run or frequent schedules, set up Apify Proxy (residential) in the input — YouTube rate-limits anonymous datacenter traffic on caption endpoints.

**Which URL shapes are accepted?**
`youtube.com/watch?v=…`, `youtu.be/…`, `/embed/…`, `/shorts/…`, `/live/…`, `music.youtube.com`, and bare 11-character IDs. Anything else is skipped with a warning.

**What are common errors?**
`Invalid input: "videoUrls" is required` (empty list), `maximum per run is 50` (split the job), bad `targetLanguage` codes, and per-video warnings for unavailable videos — none of which stop the run.

### Integrations

- **Apify API** — trigger runs and fetch the dataset from any language (`POST /v2/acts/<actor-id>/runs`, then read the default dataset).
- **Make / Zapier / n8n** — use the Apify modules: run actor → rows with `fullText` ready for OpenAI/summarizer modules.
- **MCP / LLM agents** — the dataset schema is fixed (see `dataset_schema.json`): `videoUrl, videoId, title, channelName, channelId, durationSecs, viewCount, language, isAutoGenerated, hasCaptions, availableTracks, segments, fullText, targetLanguage`.
- **Webhooks** — notify your pipeline when the run finishes; results stream row-by-row into the dataset.

### Support

Found a video shape that fails, or a track selection that looks wrong? Open a run with just that URL and report the video ID — track/endpoint issues are fixed against real cases.

# Actor input Schema

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

URLs of public YouTube videos, 1 to 50 per run. Supports watch, youtu.be, embed, shorts and live URLs.

## `targetLanguage` (type: `string`):

ISO 639-1 code of the requested language (e.g. en, es). If the video has no native track in that language, automatic translation is used when YouTube offers it; otherwise the native track is returned and the 'language' field tells you which one.

## `maxItems` (type: `integer`):

Maximum videos to process (0 = all URLs in videoUrls). Controls spend.

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

If true, also includes the joined full text and the list of available caption tracks.

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

Residential proxies are required on the platform: YouTube blocks datacenter IPs on the caption endpoints. Locally the actor works without a proxy.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "targetLanguage": "en",
  "maxItems": 5,
  "includeMetadata": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

All scraped items in the 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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ],
    "targetLanguage": "en",
    "maxItems": 5,
    "includeMetadata": true,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami0089/youtube-transcript-batch-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=jNQXAC9IVRw"],
    "targetLanguage": "en",
    "maxItems": 5,
    "includeMetadata": True,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("dami0089/youtube-transcript-batch-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=jNQXAC9IVRw"
  ],
  "targetLanguage": "en",
  "maxItems": 5,
  "includeMetadata": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call dami0089/youtube-transcript-batch-scraper --silent --output-dataset

```

## MCP server setup

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