Youtube Transcript Scraper avatar

Youtube Transcript Scraper

Pricing

from $3.00 / 1,000 results

Go to Apify Store
Youtube Transcript Scraper

Youtube Transcript Scraper

Pricing

from $3.00 / 1,000 results

Rating

0.0

(0)

Developer

Gorav Agarwal

Gorav Agarwal

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Bulk YouTube Transcript Scraper

Extract transcripts and subtitles from hundreds of YouTube videos in a single run — by video URL, playlist, channel, @handle or search query. Returns timestamped segments, clean plain text, optional SRT, the full list of available caption languages, and a per-video status code so a partial failure is visible instead of silent.

You are only charged for transcripts that are actually returned. A video with no captions, a private video, or one YouTube refuses to serve costs you nothing.

Why this one

Most YouTube transcript Actors take one video per run. If you have 500 videos, that is 500 runs to orchestrate, 500 sets of overhead, and no consolidated output. This one takes a list — or a whole channel — and gives you a single dataset.

The other common failure is silence. When YouTube declines a request, a naive scraper writes an empty string and moves on, so you discover months later that a chunk of your corpus is blank. Here every video carries an explicit status: OK, NO_TRANSCRIPT, NO_MATCHING_LANGUAGE, VIDEO_UNAVAILABLE, AGE_RESTRICTED, LIVE_STREAM, EMPTY_TRANSCRIPT, IP_BLOCKED, FETCH_FAILED or INVALID_INPUT — and failures are written to a separate failures dataset with a human-readable reason.

Read this before you run it: YouTube blocks datacenter IPs

This is the single most important thing to understand, and most listings in this category do not tell you.

YouTube now answers transcript requests from cloud/datacenter IP ranges with "Sign in to confirm you're not a bot". I measured this directly from two independent datacenter networks (Google Cloud and Cloudflare) against five different endpoints — watch page, mobile watch page, embed, youtube-nocookie, and the watch page with consent parameters. Every one returned LOGIN_REQUIRED and zero caption tracks. The same requests from a residential connection returned a full 28-language track list.

Apify runs on exactly those cloud ranges. So:

  • This Actor defaults to Apify Proxy with the RESIDENTIAL group. Without it, expect most videos to come back IP_BLOCKED.
  • Residential proxy bandwidth is billed to your own Apify account, separately from this Actor's per-result price.
  • Measured consumption is 51.4 KB per video on the wire. At Apify's residential rate of $8/GB that is about $0.39 per 1,000 transcripts.

So the honest all-in cost is roughly $2.89–$3.39 per 1,000 transcripts, of which ~$0.39 is proxy bandwidth on your account. I would rather you see that here than on your invoice.

If you already run your own residential or mobile proxy, point proxyConfiguration at it and the bandwidth cost disappears.

How it works

  1. Expand every input into video IDs. Playlists, channels, @handles and search queries are paged through YouTube's internal API.
  2. Resolve each video through the InnerTube player endpoint using a mobile client context.
  3. Select the best caption track: your preferred languages in order, human-written before auto-generated, with an optional fallback to any available language.
  4. Download and parse the caption document into timestamped segments.
  5. Write successes to the default dataset and failures to the failures dataset.

A technical note on why the mobile client matters

The traditional approach — scrape the watch page, read captionTracks[].baseUrl, fetch it — is broken in a way that is easy to miss. Those WEB-client caption URLs now return HTTP 200 with a zero-length body unless they carry a Proof-of-Origin token. It looks exactly like success and yields nothing, which is why so many transcript tools quietly started returning empty strings. This Actor uses the iOS and Android client contexts, whose caption URLs still return real content, and treats an empty document as an explicit EMPTY_TRANSCRIPT failure rather than a blank result.

It also handles both caption schemas YouTube serves — the legacy <transcript><text start= dur=> form used by older videos and the newer millisecond-based timedtext format="3" form — which is a common source of "works on some videos, not others".

Input

FieldTypeDefaultDescription
videosarray1 demo videoURLs, bare IDs, playlists, channels, @handles, or search:your query
languagesarray["en"]Preferred language codes, in priority order
preferManualCaptionsbooleantrueHuman-written before auto-generated
allowAutoGeneratedbooleantrueAccept ASR captions
anyLanguageFallbackbooleantrueUse any track if preferred languages are missing
includeSegmentsbooleantrueTimestamped segment array
includeTimestampedTextbooleanfalse[mm:ss]-prefixed single string
includeSrtbooleanfalseReady-to-use .srt
maxItemsinteger1000Total videos this run
maxVideosPerSourceinteger200Cap per playlist/channel/search

Example input

{
"videos": [
"https://www.youtube.com/watch?v=aircAruvnKk",
"https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi",
"@3blue1brown",
"search:transformer architecture explained"
],
"languages": ["en", "en-GB"],
"maxVideosPerSource": 50,
"includeSrt": true
}

Output example

One item per successful transcript (text abbreviated here):

{
"position": 1,
"videoId": "aircAruvnKk",
"url": "https://www.youtube.com/watch?v=aircAruvnKk",
"status": "OK",
"title": "But what is a neural network?",
"channel": "3Blue1Brown",
"channelId": "UCYO_jab_esuFRV4b17AJtAw",
"durationSeconds": 1120,
"viewCount": 19528431,
"language": "en",
"languageName": "English",
"isAutoGenerated": false,
"availableLanguages": [
{ "languageCode": "en", "languageName": "English", "isGenerated": false, "isTranslatable": true },
{ "languageCode": "de", "languageName": "German", "isGenerated": false, "isTranslatable": true }
],
"segmentCount": 286,
"characterCount": 18342,
"wordCount": 3357,
"text": "…full transcript as a single clean string…",
"segments": [
{ "start": 4.2, "duration": 3.1, "end": 7.3, "text": "…" },
{ "start": 7.3, "duration": 2.8, "end": 10.1, "text": "…" }
],
"scrapedAt": "2026-09-21T09:14:02Z"
}

A failed video, written to the failures dataset and not billed:

{
"videoId": "XXXXXXXXXXX",
"url": "https://www.youtube.com/watch?v=XXXXXXXXXXX",
"status": "NO_TRANSCRIPT",
"error": "video has no caption tracks",
"title": "Some video",
"availableLanguages": [],
"sourceInput": "@somechannel"
}

Each run also writes a RUN_SUMMARY record to the key-value store with the status breakdown, request count, bytes transferred and the estimated residential-proxy cost for that run.

Pricing

Pay per result, and only for a transcript actually returned. Failures go to a separate dataset that is not billed.

Actor price$3.00 per 1,000 transcripts
Residential proxy bandwidth (your account)~$0.39 per 1,000
Realistic all-in~$3.39 per 1,000

Use cases

Feeding LLMs and RAG pipelines. Clean plain text plus timestamps is exactly the shape a chunker wants. Turn a 200-video channel into a searchable corpus in one run.

Content repurposing. Pull transcripts for a whole playlist and generate summaries, blog drafts, show notes or clip suggestions.

Research and media analysis. Quantitative study of what was said across a channel or topic over time, with timestamps for citation.

Subtitle workflows. Export SRT for videos where captions exist but the file was never published.

Accessibility auditing. The availableLanguages array plus isAutoGenerated shows which videos have real human captions and which rely on ASR.

FAQ & Support

Can it do a whole channel? Yes. Pass the channel URL or @handle and it pages through the Videos tab, optionally including Shorts. Use maxVideosPerSource to cap it.

Do I need a YouTube API key? No. This uses no authentication, no cookies and no login. It also does not touch the official Data API, so it does not consume your quota.

Why did I get IP_BLOCKED? YouTube refused the request because it came from a datacenter IP. Enable Apify Proxy with the RESIDENTIAL group — that is the default, so this usually means it was switched off.

Why EMPTY_TRANSCRIPT? YouTube returned a caption document with no cues. This is normally a Proof-of-Origin rejection. Retrying through a residential proxy usually resolves it. It is reported rather than silently returned as empty text.

Can I get auto-generated captions only, or human-written only? Yes — preferManualCaptions and allowAutoGenerated control this independently.

What about videos with no captions at all? They return NO_TRANSCRIPT in the failures dataset and cost you nothing. No scraper can generate captions that YouTube does not have; anything claiming otherwise is transcribing the audio, which is a different and far more expensive job.

How fast is it? Around 5 videos in parallel by default. Concurrency is deliberately conservative because YouTube rate-limits aggressively and a throttled run is worse than a slightly slower one. Raise maxConcurrency if your proxy pool can take it.

Is this legal? It reads publicly available caption tracks from public videos — no login, no DRM circumvention, no personal data. Transcripts are the copyrighted work of their creators; you are responsible for how you use them, and fair-use limits apply to reproduction.

Something looks wrong. Open an issue on the Issues tab with your input JSON and the run ID. Include the RUN_SUMMARY record if you can — it contains the status breakdown that usually identifies the cause immediately.