# YouTube Transcript Scraper (timestamps, channels, search) (`datahamster/youtube-transcripts`) Actor

YouTube transcript scraper with no API key and no login: the full subtitle text of any video plus the timed segments behind it, as one row per video. Feed single videos, a whole channel or a keyword search. Picks the caption language you ask for, human-written before auto-generated.

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

## Pricing

from $2.00 / 1,000 result items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## YouTube Transcript Scraper (timestamps, channels, search)

Get the subtitle text of YouTube videos as one flat row per video — the full transcript **and** the timed segments behind it. Feed it single videos, whole channels or a keyword search. No API key, no login, no video downloads.

### What you get

`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`.

### Example output

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

| Field | Example value |
|---|---|
| `url` | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` |
| `title` | `Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)` |
| `channelName` / `channelId` | `Rick Astley` / `UCuAXFkgsw1L7xaCfnd5JJOw` |
| `language` / `languageName` | `en` / `English` |
| `isAutoGenerated` | `false` |
| `availableLanguages` | `["en", "de-DE", "ja", "pt-BR", "es-419"]` |
| `segments` | `[{"start": 1.36, "duration": 1.68, "text": "[♪♪♪]"}, …]` |
| `wordCount` / `segmentCount` / `durationSec` | `487` / `61` / `213` |

### Use cases

- Feed a RAG pipeline or build a training dataset from talks, lectures and podcasts, with timestamps kept for citation.
- Summarise or translate a channel's back catalogue without downloading a single video file.
- SEO, content research and accessibility checks: read what a video actually says, in bulk.

### 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` |
| `monitorKey`, `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.
- 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".
- 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.

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

## `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".

## `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".

## `monitorKey` (type: `string`):

Optional state key for monitor mode when the actor is not run as a saved task, e.g. "nasa-transcripts". Runs sharing a key share the comparison state.

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

## Actor input object example

```json
{
  "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",
  "maxItems": 20,
  "mode": "scrape"
}
```

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

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datahamster/youtube-transcripts"
        }
    }
}
```

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/3H3Mb5jcswU4S4fjs/openapi.json
