YouTube Transcript Scraper - Subtitles & Timestamps avatar

YouTube Transcript Scraper - Subtitles & Timestamps

Pricing

$0.80 / 1,000 per video transcript returneds

Go to Apify Store
YouTube Transcript Scraper - Subtitles & Timestamps

YouTube Transcript Scraper - Subtitles & Timestamps

Get YouTube transcripts with timestamps from any video, playlist, channel or search. Auto-generated (ASR) and human captions, any language, optional machine translation, plus SRT/VTT and full video metadata. No API key, no login.

Pricing

$0.80 / 1,000 per video transcript returneds

Rating

0.0

(0)

Developer

Scrapers Delight

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

🎬 YouTube Transcript Scraper

Pull the transcript of any YouTube video β€” as plain text and as timestamped cues β€” from a video URL, a whole playlist, a channel, or a search query. Auto-generated (ASR) captions and human-written subtitles, in any language the video publishes, with optional machine translation into 18+ more.

No API key. No login. No cookies. No YouTube Data API quota.

What does YouTube Transcript Scraper do?

You give it videos. It gives you back, one row per video:

  • πŸ“ transcript β€” the whole thing as clean, joined plain text, ready to paste into an LLM
  • ⏱️ segments β€” every caption cue with start, duration and end in seconds
  • 🎞️ srt / vtt β€” the same transcript rendered as a ready-to-use subtitle file (optional)
  • 🌍 languageCode + isAutoGenerated β€” exactly which track you got, and whether a human wrote it
  • πŸ—‚οΈ availableLanguages β€” every other caption track the video offers, so you know what else you could have asked for
  • πŸ” translateTo β€” have YouTube translate the transcript before it is returned
  • πŸ“Ί video metadata β€” title, channel, channel ID, duration, views, likes, publish date, YouTube category, description, keywords, thumbnail
  • 🚦 transcriptStatus β€” ok, no-captions, no-transcript-in-requested-language, video-unavailable. A row is never silently empty.

Quick start

{
"videoUrls": [
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"https://youtu.be/jNQXAC9IVRw",
"dQw4w9WgXcQ"
]
}

Or point it at a whole channel, a playlist and a search at once:

{
"channelUrls": ["https://www.youtube.com/@veritasium"],
"playlistUrls": ["https://www.youtube.com/playlist?list=PLbpi6ZahtOH6Blw3RGYpWkSByi_T7Rygb"],
"searchQueries": ["noticias en espaΓ±ol hoy"],
"maxVideosPerSource": 25,
"maxItems": 75
}

URL forms accepted: watch?v=, youtu.be/, /shorts/, /embed/, /live/, music.youtube.com, youtube-nocookie.com, and bare 11-character video IDs. Channels accept @handle, /channel/UC…, /c/, /user/ or a bare UC… ID. All twelve URL forms are covered by the parser's test cases.

Two things most transcript scrapers get wrong

1. Auto-generated captions come back duplicated. YouTube's ASR json3 stream is a rolling caption: every real cue is followed by an aAppend redraw event whose only content is a newline. Parsers that ignore the flag emit every line twice. Measured on a real ASR track: 133 caption events, of which 66 were redraws β€” this actor keeps the 67 real ones.

2. A Spanish video comes back blank because you asked for English. By default fallbackToAnyLanguage is on: if the video publishes captions but not in your preferred languages, you get the best track it does have, correctly labelled, instead of an empty row. In a 15-video mixed run, that is what turned four Spanish news videos from blanks into full transcripts.

And when a transcript genuinely cannot be produced, the row says so in transcriptStatus and transcriptError rather than shipping an empty string.

Output

One row per video. Measured over a 32-video corpus (music, tutorials, TED talks, Spanish and Korean news, DIY, Shorts, 24/7 livestreams) parsed offline from captured bytes on 2026-09-02: 28 of 32 produced a transcript β€” 27,678 caption cues and 226,853 words β€” and the other 4 genuinely publish no captions at all (three are 24/7 live news streams).

{
"videoId": "dQw4w9WgXcQ",
"videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
"channelName": "Rick Astley",
"channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
"channelUrl": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw",
"durationSeconds": 213,
"viewCount": 1810788783,
"likeCount": 19368027,
"publishedAt": "2009-10-24T23:57:33-07:00",
"uploadedAt": "2009-10-24T23:57:33-07:00",
"category": "Music",
"keywords": ["rick astley", "never gonna give you up", "…"],
"description": "The official video for β€œNever Gonna Give You Up”…",
"thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/sddefault.jpg",
"isLiveContent": false, "isPrivate": false, "isUnlisted": false, "isFamilySafe": true,
"transcriptStatus": "ok",
"language": "English",
"languageCode": "en",
"isAutoGenerated": false,
"isTranslated": false,
"translatedTo": null,
"transcript": "[β™ͺβ™ͺβ™ͺ] β™ͺ We're no strangers to love β™ͺ β™ͺ You know the rules and so do I β™ͺ …",
"segments": [
{ "start": 1.36, "duration": 1.68, "end": 3.04, "text": "[β™ͺβ™ͺβ™ͺ]" },
{ "start": 18.64, "duration": 3.24, "end": 21.88, "text": "β™ͺ We're no strangers to love β™ͺ" }
],
"segmentCount": 61,
"wordCount": 487,
"characterCount": 2089,
"availableLanguages": [
{ "languageCode": "en", "languageName": "English", "kind": "manual" },
{ "languageCode": "en", "languageName": "English (auto-generated)", "kind": "asr" },
{ "languageCode": "de-DE", "languageName": "German (Germany)", "kind": "manual" },
{ "languageCode": "ja", "languageName": "Japanese", "kind": "manual" },
{ "languageCode": "pt-BR", "languageName": "Portuguese (Brazil)", "kind": "manual" },
{ "languageCode": "es-419", "languageName": "Spanish (Latin America)", "kind": "manual" }
],
"availableLanguageCount": 6,
"translationLanguages": ["ar", "zh-Hant", "nl", "en", "fr", "de", "hi", "id", "…"],
"scrapedAt": "2026-09-02T04:22:21.784Z"
}

Measured field fill

Over the same 32-video corpus. "With a transcript" is the 28 rows that produced one.

FieldAll 32 rowsRows with a transcript
videoId, videoUrl, title, channelName, channelId, channelUrl100%100%
durationSeconds, viewCount, thumbnailUrl100%100%
publishedAt, uploadedAt, category100%100%
isLiveContent, isPrivate, isUnlisted, isFamilySafe100%100%
transcriptStatus, segmentCount, wordCount, characterCount100%100%
likeCount97%96%
description91%93%
keywords81%86%
transcript, segments, languageCode, language, isAutoGenerated88%100%

likeCount is missing where the uploader hides it; keywords and description where the uploader left them empty. Nothing here is estimated β€” these are counts over real rows.

Options that matter

  • languages β€” ordered preference list, e.g. ["en", "es", "pt-BR"]. en also matches en-US and en-GB. Use ["any"] to always take the video's own default track.
  • fallbackToAnyLanguage (on) β€” never return blank just because your preferred language is missing.
  • preferAutoGenerated (off) β€” human captions win by default because they are punctuated; switch on for ASR, which is usually more complete on long videos.
  • translateTo β€” a language code (es, fr, de, ja…) to have YouTube translate the transcript. Each row lists its own valid targets in translationLanguages.
  • includeSegments (on) / includeSrt / includeVtt β€” which representations you want. Turn includeSegments off for much smaller rows when you only need the text.
  • includeVideoMetadata (on) β€” publish date, category and like count come from a second, small request; turn it off to shave a request per video.
  • skipVideosWithoutTranscript (off) β€” leave off to see every video and why it failed; switch on to deliver only successes.
  • maxVideosPerSource (50) and maxItems (200) β€” both bounded by default so a 5,000-upload channel cannot run away.

Pricing

Pay per result. You are charged for rows you actually receive, and never for a video that was blocked.

EventPriceWhen it fires
transcript-scraped$0.0008One per video row pushed to the dataset

That is $0.80 per 1,000 videos β€” no run start fee, and cheaper than every other YouTube transcript actor on the Store with real traction (the lane sits between $0.001 and $0.01 per video). Videos are deduplicated across playlists, channels and searches before billing, so the same video is never charged twice in one run.

Worked examples: a 200-video channel β‰ˆ $0.16. Ten thousand videos β‰ˆ $8.00. A single video β‰ˆ $0.0008.

Rows are billed through Apify's gated pushData form, so at a spend cap the run stops cleanly and you are never charged for rows you did not receive.

FAQ

Does this need a YouTube API key or a Google account? No. It talks to YouTube's own public InnerTube endpoint with YouTube's own public web key. No login, no cookies, no OAuth, no Data API quota.

Does it transcribe videos that have no captions? No β€” and it says so plainly rather than pretending. This actor reads the captions YouTube already publishes (human-written or YouTube's own ASR). If a video has neither, the row comes back with transcriptStatus: "no-captions". Speech-to-text on the audio is a different, much more expensive product.

Do I get auto-generated captions, or only human ones? Both. Human captions are preferred for the same language because they are punctuated and accurate, and YouTube's ASR track is used whenever that is what exists. isAutoGenerated tells you which one you got, every time.

Why does it need a residential proxy? Because YouTube blocks datacenter IPs on this endpoint. Measured 2026-09-02 across three videos and seven InnerTube client profiles: through a datacenter pool every single request answered LOGIN_REQUIRED β€” "Sign in to confirm you're not a bot" with zero caption tracks; through residential it returned full transcripts on all three. Apify Proxy with the RESIDENTIAL group is the default and needs no configuration from you.

What happens when a video is blocked? It is retried on fresh proxy exits (the wall is per-IP, not per-video), then β€” if it still will not open β€” it is counted and named in the run's status message, not delivered as an empty row and not charged. A blocked page reported as "no results" is the failure mode this actor exists to avoid.

Can I get transcripts for a whole channel? Yes. Put the channel URL or @handle in channelUrls. It walks the channel's Videos tab with continuation tokens, bounded by maxVideosPerSource (default 50 β€” raise it for a full back catalogue).

Can I translate the transcript? Yes. Set translateTo to a language code and YouTube translates it server-side before it is returned. Each row lists its own available targets in translationLanguages (18 for the sample video above).

Does it work on YouTube Shorts and livestreams? Shorts, yes β€” pass the /shorts/ URL. Livestreams only once the stream has captions; 24/7 news channels streaming live usually publish none, and come back no-captions. Three of the four caption-less videos in our corpus were exactly that.

How long can a transcript be? The longest in our corpus was a 12-hour Python course: 15,121 cues, 92,922 words, a 1.89 MB row. Everything fits in one dataset item; on the rare video large enough to exceed Apify's 9 MB item limit the timestamped representations are dropped and the plain text kept, flagged with oversizedRow.

Are the timestamps accurate? They come straight from YouTube's own cue timings in milliseconds, converted to seconds. start, duration and end are all provided, and the SRT/VTT renderers use the same numbers.

What if YouTube changes the caption format? The parser reads json3 first and falls back to YouTube's srv1 XML shape. Verified on the same video: both produce 61 identical cues with identical text.

Is the output stable enough to diff between runs? Yes β€” field names and types do not change per video, and every row carries scrapedAt. Counts like viewCount and likeCount are a snapshot at that moment.

Notes and honest limits

  • Captions only. This reads what YouTube publishes. It does not run speech recognition on the audio, so a video with no captions produces no transcript β€” by design, and reported as such.
  • Residential proxy is required, not optional. Datacenter IPs get the bot wall on this endpoint. It is the default; leave it alone unless you are supplying your own residential proxy.
  • Private, deleted, age-restricted and members-only videos return transcriptStatus: "video-unavailable" with the reason YouTube gave. This actor has no login and does not attempt one.
  • Translations are YouTube's machine translation, not a human or an LLM. Quality is whatever YouTube's is.
  • en matches en-US/en-GB, and both an en manual and an en ASR track can exist on the same video β€” isAutoGenerated disambiguates.
  • You are responsible for your use of this data. Review YouTube's Terms of Service and respect copyright in the transcripts you collect.