# YouTube Transcript Scraper (`datahamster/youtube-transcript-scraper`) Actor

YouTube transcript scraper with no API key and no login. Get the full transcript, subtitles and timed caption segments for any video, channel or search, with human captions preferred over auto-generated ones. Monitor mode returns transcripts of new uploads on a channel.

- **URL**: https://apify.com/datahamster/youtube-transcript-scraper.md
- **Developed by:** [Viktor Dubnytskiy](https://apify.com/datahamster) (community)
- **Categories:** Other
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 result items

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?

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

Get the subtitle text of YouTube videos as one flat row per video — the full transcript, the caption type (human-written or auto-generated) and the timed segments behind it. Feed it single videos, whole channels or a keyword search. No API key, no login, no video downloads.

**Try it:** the prefilled example returns 2 rows in about 10 seconds and costs about $0.004. Results cost $2 per 1,000.

### What you get

Real rows from the example dataset (transcript text truncated here):

| video | source | captions | words / segments |
|---|---|---|---|
| Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster) | `videoUrls` | `en`, human-written | 487 / 61 (213s) |
| Apify Agent Skills Makes It Easy To Scrape Websites! | `searches: "apify tutorial"` | `en`, auto-generated | 2588 / 359 (863s) |
| How to scrape anything with Apify and Make #aiautomation #nocode | `searches: "apify tutorial"` | `en`, auto-generated | 111 / 17 (40s) |

`segments` on the first row: `[{"start": 1.36, "duration": 1.68, "text": "[♪♪♪]"}, …]`; `availableLanguages` `["en", "de-DE", "ja", "pt-BR", "es-419"]`.

Full row: `id` / `videoId`, `url`, `title`, `channelId`, `channelName`, `durationSec`, `language`, `languageName`, `isAutoGenerated`, `availableLanguages`, `segments` (`start`, `duration`, `text` — seconds), `text` (all segments joined), `wordCount`, `segmentCount`, `source` (`url` / `channel` / `search`), `query`, `scrapedAt`.

### Use cases

- **Monitor a channel's new uploads, weekly** — put a competitor's or your own channel handle into monitor mode; each scheduled run returns transcripts only for videos published since the last check, ready for a newsletter or content calendar without re-pulling the whole channel.
- **Scheduled research or SEO feed on a keyword** — run a search keyword on a weekly schedule in monitor mode to catch new videos ranking for that term as they appear, instead of re-scraping the same result page from scratch each time.
- **One-off transcript pull for a single video** — grab the transcript and timed segments for one video right now, no schedule needed.

### Try it in 10 seconds

**One-off** — hit **Start**/**Try it**, the input already works: two example videos preloaded in `videoUrls`, `languages: ["en"]`, `maxItems: 20`, nothing required. Swap in your own URLs, a `channels` handle or a `searches` keyword and run again.

**Monitor mode** — save the input as a Task, then set:

```json
{
  "mode": "monitor",
  "monitorStateId": "nasa-channel-watch",
  "channels": ["@NASA"],
  "webhookUrl": "https://your-endpoint.example.com/hook",
  "telegramBotToken": "",
  "telegramChatId": ""
}
```

and put it on a schedule (Apify → Schedules → cron `0 8 * * 1` for weekly). Each monitor run charges one `monitor-check` event ($0.006) and returns only transcripts that are new or changed since the previous run, each billed as one `change` event ($0.002) — a run with nothing new charges only the check.

### How it works

1. **Video URLs / channels / searches** are resolved into a list of video ids.
2. For each video the caption track list is fetched and one track is chosen by your `languages` order, human-written before auto-generated (`preferManual`), with `fallbackAnyLanguage` as the last resort.
3. The chosen track is downloaded as timed text and split into `segments`; `text` is those segments joined.
4. YouTube shows a "sign in to confirm you're not a bot" wall to some exits, so the actor starts without a proxy and escalates one rung at a time (`tier: auto`).

### Input

| Field | Meaning | Default |
|---|---|---|
| `videoUrls` | Watch URLs, `youtu.be` / `/shorts/` links, or bare 11-char ids | two example videos |
| `channels` | `@handle`, channel URL or `UC…` id — latest videos are transcribed | empty |
| `searches` | Keywords; the videos of the first result page are transcribed | empty |
| `languages` | Preference order of caption languages, e.g. `["en","de"]` | `["en"]` |
| `preferManual` | Human-written track before the auto-generated one | `true` |
| `includeAutoGenerated` | Allow YouTube's speech-recognition track | `true` |
| `fallbackAnyLanguage` | Take any track when none of your languages exists | `false` |
| `maxVideosPerChannel` | Latest videos taken from each channel | `50` |
| `includeShorts` | List the Shorts tab instead of Videos | `false` |
| `tier` | Proxy rung: `auto`, `none`, `datacenter`, `residential` | `auto` |
| `maxItems` | Stop after this many transcripts | `20` |
| `mode` | `scrape` or `monitor` (only new/changed since last run) | `scrape` |
| `monitorStateId`, `webhookUrl`, `telegramBotToken`, `telegramChatId` | Monitor-mode state key and alert targets | empty |

### Pricing

| Event | Price |
|---|---|
| result | $0.002 per transcript ($2 per 1,000) |
| monitor-check | $0.006 per monitor run |
| change | $0.002 per new/changed transcript |

**Videos without a usable caption track are not pushed and not charged.** They are counted in the `ADAPTER_STATS` record of the run's key-value store (`noCaptions`, `unplayable`, `loginWalls`, `listingWalls`, `escalations`, `tierUsed`), next to the standard `RUN_SUMMARY` record — so an empty run tells you whether the videos simply had no captions or whether YouTube refused the requests.

### Why this actor

- No API key, no login, no cookies — nothing to register and nothing to keep alive.
- Timed `segments` come with every row, not just a wall of text, so you can cite or seek back into the video.
- Human-written captions before auto-generated, `isAutoGenerated` tells you which kind you got — most competitors don't distinguish the two in the output.
- A video with no usable captions is never pushed and never charged, so a batch of mixed videos costs only what it produced.
- The run tells you *why* it was empty: `ADAPTER_STATS` separates "no captions" from "YouTube wall" from "video unplayable".
- Monitor mode watches a channel or a search keyword on a schedule and returns only what's new, so a weekly check costs one `monitor-check` plus the transcripts that actually changed — not a full re-pull.
- Starts with no proxy and escalates only when YouTube actually walls the exit, so most runs carry no proxy cost.

### Limits

- Auto-generated tracks are speech recognition: no punctuation to speak of, occasional wrong words, and word timings grouped into short lines. `isAutoGenerated` tells you which kind you got.
- No machine translation: you get the tracks the video actually publishes, never a translated one.
- Videos with captions disabled, private, removed or age-restricted videos return nothing (and cost nothing).
- YouTube shows a "sign in to confirm you're not a bot" wall to some exit IPs. The actor starts without a proxy and escalates to datacenter and then residential proxy when it sees the wall; residential traffic is billed by Apify on top of the per-result price.
- Member-only and unlisted videos are not supported.

### FAQ

**Does it need a YouTube API key or a login?** No. There is no key field and no cookie field. Captions are read from the public player payload.

**What happens to videos that have no subtitles?** They are skipped: no row, no charge. The count lands in `noCaptions` inside the `ADAPTER_STATS` record of the run's key-value store.

**Can I get a transcript in a language the video does not publish?** No. The actor never machine-translates. Ask for your languages in `languages`, and set `fallbackAnyLanguage` to true if you would rather have the video's own language than nothing.

**How do I get alerted only when a channel posts something new?** Save the input as a Task with `channels` set, `mode: "monitor"` and a `monitorStateId`, put it on a weekly schedule, and add a `webhookUrl` or Telegram target — see "Try it in 10 seconds" above.

### Changelog

- 0.1: initial release — transcripts from video URLs, channels and search; language preference, manual/auto track choice, timed segments.

***

If this actor saved you time, a short review on its Store page genuinely helps other people find it. Found a bug or need a field that is missing? Open a ticket on the **Issues** tab.

# Actor input Schema

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

Stop after this many transcript rows, e.g. 20. You are charged only for rows actually pushed to the dataset.

## `mode` (type: `string`):

"scrape" returns every matching row. "monitor" returns only rows that are new or whose transcript text changed since the previous run of the same saved task. Example: "scrape".

## `monitorStateId` (type: `string`):

Optional state id when not running as a saved task (monitor mode)

## `webhookUrl` (type: `string`):

Optional HTTPS URL that receives a POST with a JSON summary of the changes found in monitor mode, e.g. "https://hooks.zapier.com/hooks/catch/123/abc".

## `telegramBotToken` (type: `string`):

Optional Telegram bot token used to send monitor-mode change summaries, e.g. "123456789:AAE...". Leave empty to skip Telegram alerts.

## `telegramChatId` (type: `string`):

Optional Telegram chat id that receives the monitor-mode summaries, e.g. "-1001234567890" for a channel or "123456789" for a direct chat.

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

YouTube videos to transcribe, one per line: a watch URL "https://www.youtube.com/watch?v=dQw4w9WgXcQ", a "https://youtu.be/dQw4w9WgXcQ" link, a "/shorts/<id>" link, or a bare 11-character id "dQw4w9WgXcQ".

## `channels` (type: `array`):

Channels whose latest videos should be transcribed, one per line, as a handle, a channel URL or a UC id, e.g. "@NASA", "https://www.youtube.com/@NASA" or "UCLA_DiR1FfKNvjuUpBHmylQ".

## `searches` (type: `array`):

Keywords to search YouTube for, one per line, e.g. "apify tutorial". Every video on the first result page is transcribed.

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

Caption languages in preference order, one per line, e.g. "en" then "de". The first language the video actually publishes wins; "en" also matches "en-GB" and "en-US".

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

true = use the uploaded, human-written caption track before YouTube's auto-generated one in the same language. false = take whichever comes first. Example: true.

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

true = accept YouTube's speech-recognition track when no human-written one exists (most videos only have this). false = human-written captions only, other videos are skipped and not charged. Example: true.

## `fallbackAnyLanguage` (type: `boolean`):

true = if none of your Languages exists, take whatever caption track the video does have. false = skip the video (no row, no charge). Example: false.

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

How many of the latest videos to take from each channel, e.g. 50. Range 1-500. Only used together with Channels.

## `includeShorts` (type: `boolean`):

true = list the channel's Shorts tab instead of its Videos tab. false = regular videos. Example: false.

## `tier` (type: `string`):

Which proxy rung to use. "auto" starts with no proxy and moves one rung up only when YouTube answers with its "sign in to confirm you're not a bot" wall, which keeps most runs free of proxy cost. "none", "datacenter" and "residential" pin one rung. Example: "auto".

## Actor input object example

```json
{
  "maxItems": 20,
  "mode": "scrape",
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=8S0FDjFBj8o"
  ],
  "languages": [
    "en"
  ],
  "preferManual": true,
  "includeAutoGenerated": true,
  "fallbackAnyLanguage": false,
  "maxVideosPerChannel": 50,
  "includeShorts": false,
  "tier": "auto"
}
```

# Actor output Schema

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

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

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

RUN_SUMMARY record

# 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 = {
    "maxItems": 20,
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/watch?v=8S0FDjFBj8o"
    ],
    "languages": [
        "en"
    ]
};

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

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

```

## MCP server setup

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