# YouTube Transcript Scraper · 500 videos per run (`doesaiknow/youtube-transcript-scraper-500-videos-per-run`) Actor

One channel or playlist URL in, up to 500 transcripts out: full text, timestamped segments, subtitle language, publish label, duration and channel fields. Set a date to re-run incrementally and skip the videos you already pulled. One delivered video is one billed row.

- **URL**: https://apify.com/doesaiknow/youtube-transcript-scraper-500-videos-per-run.md
- **Developed by:** [David S](https://apify.com/doesaiknow) (community)
- **Categories:** Videos, AI, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.0035 / transcript row

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/platform/actors/running/actors-in-store#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 — full transcripts for a whole channel

Paste one **YouTube channel or playlist URL** and get up to **500 transcripts in a single run**: the
full text of every video, timestamped segments, the subtitle language, the publish label, the
duration and the channel fields — one flat dataset row per video.

Built for content and SEO teams archiving competitor channels, and for anyone loading a video library
into a RAG index or a search corpus. **One delivered video is one billed row**; videos you already
pulled in a previous run of the same source are skipped for free.

Run it from the Apify Console, the API, the CLI, or through the Apify MCP server
(`mcp.apify.com`) so an AI assistant can fetch a channel's transcripts on its own.

### What does the YouTube Transcript Scraper do?

It expands one source — a channel handle, a channel ID, a channel URL or a playlist URL — into its
long-form videos, then fetches the subtitles of each video in the language you asked for. Output is a
flat table: one row per video, 18 fixed columns, carrying both the plain-text transcript and the
timestamped segments behind it. You can also skip expansion entirely and hand it an explicit list of
video IDs or watch URLs.

It does not summarise, translate or rewrite anything. It answers one question per run: what was said
in these videos, and when.

### Why use the YouTube Transcript Scraper

- **One URL instead of 500 runs.** Up to 500 videos per run from a single channel or playlist link,
  100 by default. No separate step to build the video-ID list.
- **Text and timestamps in the same row.** `transcriptText` for search and embeddings, `segments[]`
  with `start`, `duration` and `end` in seconds for citations and clipping.
- **Incremental re-runs by date.** Set **Published after** to your last run's date and the run
  delivers only videos you do not already have — repeats are skipped without a fetch and without a
  charge.
- **28 subtitle languages, honestly reported.** `transcriptLanguage` echoes the language actually
  returned, not the one you requested, so a silent fallback to another language shows up in the data
  instead of hiding inside a "successful" row.
- **Missing subtitles are an answer, not a crash.** A video with no subtitles in your language comes
  back as a row with `notFound: true` and an empty transcript, so a 500-video job never dies on
  video 137.
- **Free cache, free repeats.** A video fetched in the last 24 hours is re-delivered from cache at no
  charge, with its original `finishedAt`.

### How to scrape YouTube transcripts from a channel

1. Put the channel or playlist in **Channel or playlist** — `@McCannDogs`, a `UC…` channel ID, a
   channel URL or a playlist URL.
2. Set **Maximum videos per run** (default 100, maximum 500). One returned video is one row.
3. Pick the **Subtitle language** — English by default, 28 languages in the dropdown.
4. For a weekly top-up, set **Published after** to the date of your previous run. Leave it empty to
   pull the full archive.
5. Optionally set a **Country** if you want the data requested from a specific market.
6. Run the Actor — rows stream into the dataset as each video finishes. Open the **Overview** view
   for a readable table, then export to JSON, CSV or Excel, or read the rows from the API.

Minimal input:

```json
{ "sourceUrl": "@McCannDogs", "maxVideos": 100 }
```

Incremental Spanish top-up: `{ "sourceUrl": "@McCannDogs", "publishedAfter": "2026-07-01",
"language": "es" }`. Known videos only, no expansion: `{ "videoIds": ["dQw4w9WgXcQ",
"https://www.youtube.com/watch?v=UF8uR6Z6KLc"] }` — an explicit list always wins over
**Channel or playlist**.

### What transcript data you get per video

One dataset row = one video, always the same 18 keys. Example row (segment list truncated for space):

```json
{
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "How to teach loose lead walking",
  "channelId": "UC8dGqrQ6cQoT1nZ2sDdZAOA",
  "channelHandle": "@McCannDogs",
  "channelTitle": "McCann Dog Training",
  "publishedAt": null,
  "publishedText": "3 days ago",
  "durationSec": 1104,
  "transcriptLanguage": "en",
  "transcriptText": "Hey there, welcome back to the channel. Today we are working on loose lead walking ...",
  "segments": [
    { "text": "Hey there, welcome back to the channel.", "start": 0.16, "duration": 3.2, "end": 3.36 },
    { "text": "Today we are working on ...", "start": 3.36, "duration": 2.72, "end": 6.08 }
  ],
  "segmentCount": 61,
  "wordCount": 1873,
  "notFound": false,
  "source": "primary",
  "contentHash": "3f0a1c7d5b9e2a64c8d1f0b7e6a4539c2d81ff40a7b6c5d4e3f2a1b0c9d8e7f6",
  "finishedAt": "2026-07-29T09:14:02.371Z"
}
```

| Field | Type | What it tells you |
|---|---|---|
| `videoId` / `url` | string | Canonical 11-character ID, and the watch URL built from it |
| `title` | string | null | Video title as listed on the source |
| `channelId` / `channelHandle` / `channelTitle` | string | null | Channel `UC…` ID, `@handle` and display name; `null` for playlists and explicit video lists |
| `publishedAt` | string | null | ISO date **only when the source states a real date**; `null` when it lists a relative label |
| `publishedText` | string | null | The raw label, e.g. `"3 days ago"`, `"4 years ago"` |
| `durationSec` | integer | null | Video length in seconds, from the listing or from the last segment |
| `transcriptLanguage` | string | null | Language of the subtitles **actually returned** |
| `transcriptText` | string | Full transcript as one plain-text string (`""` on a not-found row) |
| `segments` | array | `{text, start, duration, end}` in seconds, `end = start + duration` |
| `segmentCount` / `wordCount` | integer | Segments in the row, and words in the transcript text |
| `notFound` | boolean | `true` = no subtitles in the requested language; still a delivered, billed row |
| `source` | string | null | `primary`, or `fallback` when the backup path produced the transcript |
| `contentHash` / `finishedAt` | string | Deterministic hash of the segments, and when the row was produced |

The **Issues** dataset view narrows a run down to the rows worth a second look: `notFound: true` and
`source: "fallback"`.

### How incremental re-runs work

Re-running the same channel with **Published after** set to your previous run's date does two things.
First, the date filter drops older videos before anything is fetched. Second, videos already
delivered and billed for this source in this language are skipped even when the relative publish
label ("1 month ago") still passes the filter — the run keeps a record of what it already handed you,
so a weekly top-up returns new videos only, and the log states how many were skipped and why.

Without **Published after** nothing is de-duplicated: a run with no date threshold is a request for
the full archive, and quietly dropping rows would leave holes in a corpus you paid for.

### Input parameters

| Field | Required | Notes |
|---|---|---|
| `sourceUrl` | one of the two | Channel handle (`@name`), channel ID (`UC…`), channel URL or playlist URL |
| `videoIds` | one of the two | Up to 500 video IDs or watch URLs; wins over `sourceUrl` and skips expansion |
| `maxVideos` | no | Videos per run, 1-500 (default 100). One video = one row |
| `publishedAfter` | no | `YYYY-MM-DD`; keeps only videos published on or after this date |
| `language` | no | Subtitle language, 28 options, `en` by default |
| `country` | no | Market the data is requested from; "Auto" lets YouTube choose |

### How much does the YouTube Transcript Scraper cost?

Pay-per-event billing: you pay per **delivered row**, never per request and never per channel.

| Event | When | Price |
|---|---|---|
| Run start | Once per run | $0.005 (flat, one-time) |
| Transcript row | Per delivered row | $0.009 (Free plan) → $0.0035 on Gold, down to $0.0014 on Diamond |

Minimum run budget: **$0.014** — one run start plus one row at the Free-plan price. A 500-video run on
the Free plan is $0.005 + 500 × $0.009.

You are never charged for **cache hits** (a video fetched within 24 hours is re-delivered free), for
**repeats skipped** in incremental mode, for **failures** (no row, no charge), or for retries and
backup paths inside a row — those are included in the per-row price. A `notFound` video **is** a
delivered answer and bills as one row. A run with nothing to process does not even bill the run
start, and when the remaining run budget drops below the minimum the run stops **before** its first
request instead of half-delivering.

### YouTube Transcript Scraper vs single-video transcript tools

| | This actor | Single-video transcript actors | Manual copy-paste |
|---|---|---|---|
| Input | Channel or playlist URL | One video URL per item | One video at a time |
| Videos per run | Up to 500 | As many URLs as you assemble | Whatever you can stand |
| Building the video list | Included | Your job, with another tool | Your job |
| Incremental "only new videos" | Yes, by date | No | No |
| Timestamped segments | Yes: `start` / `duration` / `end` | Usually | Rarely |
| Cost model | Per delivered video | Per delivered video | Your time |

### What this actor does not do

- It does **not** summarise, translate or clean transcripts — you get the text as published.
- It does **not** generate subtitles for videos that have none; those rows come back `notFound`.
- It does **not** invent a `publishedAt` when the source lists only "4 years ago" — the raw label is
  reported instead of a made-up date.
- It does **not** include Shorts, comments, chapters, likes or view counts in this version.
- It does **not** download audio or video files, or touch members-only and private videos.
- It does **not** go past 500 videos in one run — split larger archives by date across runs.

### FAQ

#### How many transcripts can I get in one run?

Up to 500 videos per run, 100 by default. For a bigger archive, run the same channel several times
with different **Published after** dates.

#### Why is publishedAt null while publishedText says "3 days ago"?

Because the channel listing carries a relative label, not a date. Writing "3 days ago" into an ISO
field would invent a precision the source never gave. The filter uses the label internally, and the
raw label stays in `publishedText` so you can see exactly what was published.

#### What happens to videos without subtitles?

They come back as normal rows with `notFound: true`, an empty `transcriptText` and `segments: []`.
They are delivered answers and bill as one row each, so a channel that captions nothing shows up in
your data instead of failing the run.

#### I asked for Spanish but got English — why?

Some videos only carry subtitles in another language. `transcriptLanguage` always reports the
language actually returned, so you can filter or re-queue those rows. It is reported, never masked.

#### Do cache hits and skipped repeats cost anything?

No. A video fetched in the last 24 hours is re-delivered from cache, and in incremental mode a video
an earlier run already delivered is not fetched at all. Neither is charged.

#### Can I run it from an AI agent or on a schedule?

Yes. Use the Apify API, the CLI, or the Apify MCP server (`mcp.apify.com`), and schedule weekly runs
with **Published after** for a transcript corpus that maintains itself.

### Related actors

- [YouTube Research Digest](https://apify.com/doesaiknow/youtube-research-digest) — when you want an
  AI digest of what the top videos for a keyword say, rather than the raw transcripts of one channel.
- [YouTube Influencer Finder](https://apify.com/doesaiknow/youtube-influencer-discovery-contact-api)
  — find the channels worth transcribing, with subscriber stats and social profiles.

### Legal & support

- This actor collects **publicly available video information**: the video list a channel or playlist
  publishes, and the subtitle tracks published with those videos. It does not access private,
  members-only or log-in-gated content, and downloads no audio or video files.
- Transcripts are returned **verbatim**, with the timestamps as published — nothing is rewritten,
  translated or inferred, so you can verify every line against the source video.
- Transcripts and video metadata are **third-party content**. You remain responsible for how you use
  them, including copyright, YouTube's terms and any quoting, attribution or storage rules that apply
  to you — especially when transcripts feed a public product or an AI index.
- Output is provided **as-is** for research, archiving and internal analysis; subtitle tracks are
  point-in-time content that the publisher can change or remove at any time.
- Questions or a bug? Open an issue on the Actor's **Issues** tab, or see the **API** tab.

### SEO keywords

youtube transcript scraper, youtube transcript api, youtube transcript tool, download youtube
transcripts, youtube channel transcripts, bulk youtube transcripts, youtube subtitles scraper,
youtube captions api, extract youtube transcript, youtube playlist transcripts, video transcript
dataset, youtube transcript with timestamps, scrape youtube subtitles, transcripts for rag,
youtube video text extraction, youtube transcript export csv, youtube transcript json,
multilingual youtube transcripts, incremental youtube scraping, youtube content analysis.

Built by doesaiknow.

# Actor input Schema

## `sourceUrl` (type: `string`):

One source to expand into videos: a channel handle (@McCannDogs), a channel ID (UCxxxxxxxxxxxxxxxxxxxxxx), a channel URL, or a playlist URL. Leave it empty when you already know which videos you want and fill in "Video IDs or URLs" instead.

## `videoIds` (type: `array`):

Exact videos to transcribe — 11-character video IDs (dQw4w9WgXcQ) or watch URLs. When this list is not empty it wins over "Channel or playlist": the run transcribes exactly these videos and skips source expansion.

## `maxVideos` (type: `integer`):

How many videos to return in one run. One returned video is one dataset row. Maximum 500.

## `publishedAfter` (type: `string`):

Only return videos published on or after this date (YYYY-MM-DD). Use it for incremental re-runs: set it to the date of your previous run to skip videos you already have.

## `language` (type: `string`):

Subtitle language requested for every video. Videos without subtitles in this language are returned as rows marked "not found".

## `country` (type: `string`):

Market the video data is requested from. Leave on "Auto" to let YouTube pick.

## Actor input object example

```json
{
  "sourceUrl": "@McCannDogs",
  "maxVideos": 100,
  "language": "en",
  "country": ""
}
```

# Actor output Schema

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

All delivered rows: videoId, url, title, channelId, channelHandle, channelTitle, publishedAt, publishedText, durationSec, transcriptLanguage, transcriptText, segments, segmentCount, wordCount, notFound, source.

## `cache` (type: `string`):

Per-video rows cached for 24h; a cache hit re-delivers the row free, and the record of already delivered videos powers incremental re-runs.

# 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 = {
    "sourceUrl": "@McCannDogs",
    "language": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("doesaiknow/youtube-transcript-scraper-500-videos-per-run").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 = {
    "sourceUrl": "@McCannDogs",
    "language": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("doesaiknow/youtube-transcript-scraper-500-videos-per-run").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sourceUrl": "@McCannDogs",
  "language": "en"
}' |
apify call doesaiknow/youtube-transcript-scraper-500-videos-per-run --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=doesaiknow/youtube-transcript-scraper-500-videos-per-run",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/Okpf3hUPW8Y0FkV0R/builds/vLjnDuDOzt5p25Uvf/openapi.json
