YouTube Transcript Scraper with Chapters & Heatmap avatar

YouTube Transcript Scraper with Chapters & Heatmap

Pricing

from $4.00 / 1,000 transcript extracteds

Go to Apify Store
YouTube Transcript Scraper with Chapters & Heatmap

YouTube Transcript Scraper with Chapters & Heatmap

Get YouTube transcripts from videos, channels, playlists or searches: timed segments, plain text, SRT or VTT in your preferred language, every caption and dubbed audio track, chapters, most-replayed moments and LLM-ready chunks with timestamp links. No API key.

Pricing

from $4.00 / 1,000 transcript extracteds

Rating

0.0

(0)

Developer

CHEN-SHU-CHEN

CHEN-SHU-CHEN

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

5 days ago

Last modified

Categories

Share

What is YouTube Transcript Scraper with Chapters & Heatmap?

You want what was said in a video, not the video: to search it, quote it, summarise it, or feed it to an LLM. YouTube shows the transcript one panel at a time and has no export button.

Paste video links, channels, playlists or search queries and get one row per video with the transcript as timed segments, plain text, SRT or VTT, in the language you ask for. Each row also lists every caption track and dubbed audio track the video has, the title, description, channel, publish date, views, likes and category, and, if you turn it on, the video's chapters with the transcript of each one and YouTube's "Most replayed" moments with what is said there.

For LLM work, it can cut each transcript into chunks by chapter or by length, each with a start time and a &t= link that opens the video at that moment.

No API key and no login. Because it runs on Apify, you can schedule it to pick up a channel's new videos every week, call it through the API, and export as JSON, CSV or Excel.

How to get YouTube transcripts

  1. Open the Input tab and paste links into Videos, channels or playlists, one per line: https://www.youtube.com/watch?v=..., youtu.be/..., /shorts/..., @handle, /channel/UC... or /playlist?list=.... Or type a search into Search queries.
  2. Set Preferred languages, for example en then es. If none exists, the video's own language is returned instead, and the row says so.
  3. For channels, playlists and searches, set Videos per channel, playlist or search. To read only a channel's new videos, set Only videos published after to 7 days and schedule the run weekly.
  4. Click Start. A video usually takes one to three seconds; five are read at a time.
  5. Open the Output tab or export the dataset. Videos without a transcript are in the table too, with a status and the reason.

How much does it cost to scrape YouTube transcripts?

You pay per video, and only for what was delivered.

EventCharged whenPricePer 1,000 videos
transcript-extractedThe transcript and metadata of one video were returned (status: ok)$0.004$4.00
tracks-listedIn caption track list only mode: the track list and metadata of one video were returned$0.001$1.00
chapters-heatmapWith Chapters, heatmap and channel extras on, on top of the row, only when that data was actually returned$0.001$1.00
video-unavailableYouTube answered, but there is no transcript to give: no_captions, language_unavailable, not_found, private, age_restricted, live_now, upcoming, unplayable, login_required. The row still carries whatever metadata YouTube showed$0.0005$0.50

Never charged: blocked (YouTube kept refusing the request), date_unknown, parse_error, invalid_input, no_videos (a channel, playlist or search that turned up nothing) and not_processed (never reached because the run stopped). Rows for a channel, playlist or search as a whole, rather than for one of its videos, are always free.

Proxy traffic is included. Apify's free plan ($5 a month) covers about 1,250 transcripts.

How do I keep a run from costing more than I expect?

Set a maximum charge when you start the run. Before reading each video, the Actor checks that what is left of it covers the most that video could cost (a transcript, plus the extras if they are on), counting the videos already being read, and stops if not. Each row is then written and charged in one step by Apify's own SDK, which writes it only if the limit still covers it, so you never pay for a row you did not get. The extras are charged only after their row is written, once per video. Videos the run did not reach come back not_processed. If Apify cannot record a charge, the run stops the same way rather than keep working unbilled.

The run also stops reading 45 seconds before its timeout, including in the middle of retries and channel listings, and marks what is left not_processed.

Input

FieldDefaultNotes
urlsVideos, channels and playlists, mixed. A watch link that also names a playlist (watch?v=...&list=...) is read as the one video. A bare @handle needs its @
searchQueriesEach query returns the top videos for it
maxVideosPerSource20Per channel, playlist and search, up to 5,000
publishedAfter2026-01-31, or 7 days, 2 weeks, 3 months. Filters what channels, playlists and searches turn up; video links you list yourself are always read
channelContentTypevideosvideos, shorts, streams (past live streams) or all uploads, newest first
languages["en"]In order of preference. en also takes en-US or en-GB. Empty: the video's default track
fallbackToAnyLanguagetrueOff: a video without any of your languages comes back language_unavailable with the tracks it does have
preferManualCaptionstrueWithin one language, captions uploaded by the creator beat YouTube's auto-generated ones
modetranscripttracksOnly returns the track lists and metadata, without downloading captions
outputFormatssegments, textAdd srt and vtt for subtitle files. Formats you leave out are null in the row
includeWordTimestampsfalseStart and end of every word. Auto-generated captions only; uploaded captions have no word timings
includeChaptersAndHeatmapfalseOne more request per video, charged as chapters-heatmap
llmChunkingnonechapters or length. chunkMaxCharacters (4,000) caps each chunk; a caption line is never split
useResidentialFallbacktrueSee "Why is a video blocked?" below
abortAfterConsecutiveFailures10Stops the run when YouTube refuses everything. Videos without captions do not count
{
"urls": [
"https://www.youtube.com/watch?v=aircAruvnKk",
"@3blue1brown"
],
"maxVideosPerSource": 10,
"publishedAfter": "30 days",
"languages": ["en"],
"outputFormats": ["segments", "text", "srt"],
"includeChaptersAndHeatmap": true,
"llmChunking": "chapters"
}

Output

One row per video, plus one row for each input line that turned up no video.

FieldNotes
status, error, notesSee the statuses below. notes explains anything left out or substituted, such as a fallback language
videoId, url, title, description, keywords
channelName, channelHandle, channelId, channelUrl
publishedAt, uploadedAtISO 8601, UTC, to the second
lengthSeconds, viewCount, likeCountExact. null on a stream that is live now, or when YouTube hid the figure
category, isShort, isLive, isLiveContent, isUpcoming, isUnlisted, isFamilySafe, isPrivate, allowRatings, availableCountriesisLiveContent is true for recordings of past streams
liveStartedAt, liveEndedAtFor streams
thumbnailUrl, thumbnailsLargest first in thumbnailUrl; every size in thumbnails
language, languageName, isAutoGeneratedThe track returned
languageMatchpreferred (one of your languages) or fallback (the video's own default, because none of yours exists)
segments[{start, end, duration, text}], seconds. With word timestamps, each segment also has words: [{start, end, text}]
text, srt, vttThe whole transcript as one string, or as subtitle files
segmentCount, wordCount, characterCount
isMusicOnlyTrue when the captions hold nothing but sound tags such as [Music] or ♪: nobody speaks
captionTracksEvery track: languageCode, name, kind (manual or asr), isAutoGenerated, isTranslatable, vssId, trackName
captionTrackCount, hasManualCaptions, hasAutoCaptions
spokenLanguageThe language of YouTube's auto-generated track, which is the language YouTube heard. null when there is none
audioTracks, hasDubbedAudioDubbed audio tracks: id, languageCode, name, isDefault, isOriginal
chapters, chapterSource[{title, start, end, startTimestamp, url, text}], where text is the transcript of that chapter. chapterSource: description (timestamps in the description, read with YouTube's own rules, free), or with the extras on youtube_description / youtube_auto (chapters YouTube generated itself)
llmChunks[{index, chapterTitle, start, end, startTimestamp, url, characterCount, text}]
heatmapWith the extras: [{start, end, intensity}], YouTube's replay intensity from 0 to 1 over about 100 slices of the video
mostReplayedWith the extras: YouTube's "Most replayed" ranges, [{start, end, peakAt, label, url, transcriptExcerpt}]. When YouTube marks none, the hottest heatmap slice, with source: "heatmap_peak"
channelIsVerified, channelIsVerifiedArtistWith the extras
subscriberCount, subscriberCountText, commentCount, commentCountTextWith the extras. Rounded by YouTube (8.7M subscribers, 9.4K); the number is parsed from the text, and the text is kept as shown
extrasIncludedWhether the extras were returned, and so charged
playabilityReasonYouTube's own message when it would not play the video
fetchedViadatacenter, residential or direct
source, sourceType, inputIndexThe input line the video came from (video, channel, playlist, search) and its position
scrapedAt

Unknown values are null, never 0. Caption text is decoded: you get It's, never It's.

Example row from a run on 2026-10-18, shortened:

{
"status": "ok",
"videoId": "aircAruvnKk",
"title": "But what is a neural network? | Deep learning chapter 1",
"channelName": "3Blue1Brown",
"channelHandle": "@3blue1brown",
"publishedAt": "2017-10-05T15:11:25.000Z",
"lengthSeconds": 1120,
"viewCount": 24860971,
"likeCount": 566677,
"category": "Education",
"language": "en",
"isAutoGenerated": false,
"languageMatch": "preferred",
"segmentCount": 286,
"wordCount": 3357,
"text": "This is a 3. It's sloppily written and rendered at an extremely low resolution of 28x28 pixels…",
"segments": [{"start": 4.22, "end": 5.4, "duration": 1.18, "text": "This is a 3."}],
"captionTrackCount": 31,
"spokenLanguage": "en",
"hasDubbedAudio": true,
"chapterSource": "youtube_description",
"chapters": [{"title": "Introduction example", "start": 0.0, "end": 67.0, "startTimestamp": "0:00",
"url": "https://www.youtube.com/watch?v=aircAruvnKk&t=0s", "text": "This is a 3. It's sloppily…"}],
"mostReplayed": [{"start": 504.0, "end": 582.4, "peakAt": 515.2, "label": "Most replayed",
"url": "https://www.youtube.com/watch?v=aircAruvnKk&t=504s",
"transcriptExcerpt": "determine the activations in the next. The goal is to have some mechanism…"}],
"channelIsVerified": true,
"subscriberCount": 8700000,
"subscriberCountText": "8.7M subscribers",
"commentCount": 9400,
"commentCountText": "9.4K"
}

Statuses

  • ok — transcript returned.
  • tracks_listed — track list returned (track list only mode).
  • no_captions — the video has no captions at all, neither uploaded nor auto-generated. Common for music without lyrics, very new videos (YouTube adds auto-captions some hours after upload) and channels that turned them off.
  • language_unavailable — none of your languages, with fallback off. error lists the languages there are.
  • not_found, private, age_restricted, live_now, upcoming, unplayable, login_required — YouTube's own answer about the video; playabilityReason has its message. live_now: captions of a stream exist only once it has ended.
  • blocked — YouTube kept answering with a bot check or a rate limit, or the caption file could not be downloaded. Not charged; try again later.
  • parse_error — YouTube answered in a shape the Actor does not recognise. Not charged; please report it.
  • date_unknown — Only videos published after is set, and the video's exact publish date could not be read (YouTube's WEB player request failed). The video is not read and not charged, rather than charged when it might be older than asked. Run again later.
  • no_videos — a channel, playlist or search that turned up no video (or none newer than publishedAfter).
  • invalid_input — the line is not a YouTube video, channel or playlist.
  • not_processed — never reached: the run hit its maximum charge, its timeout, or a failure streak.

The rows without a transcript are also listed in FAILED_VIDEOS in the key-value store.

Why is a video blocked?

YouTube asks some datacenter IP addresses to "Sign in to confirm you're not a bot". Measured on Apify in October 2026: 10 of 50 videos got an answer on the first datacenter IP, 44 of 50 within six retries on fresh IPs, and 5 of 5 on residential IPs.

The same happens, less often, to the request that carries the publish date, category and likes. So each of the two requests is retried on fresh datacenter IPs first, up to seven attempts, and then, with useResidentialFallback on (the default), once or twice through a residential IP. Every request asks YouTube for only the fields it reads, so a residential retry costs a few kilobytes, and it is included in the price. A video that is still refused comes back blocked and is not charged. If many videos in a run are blocked, YouTube is limiting this proxy pool for now: run again later.

What it cannot do

  • Translate. YouTube's own translated captions answered with HTTP 429 (too many requests) in testing, so they are not offered. Every caption track the video has in its own right is listed and can be chosen. Use isTranslatable to see which ones YouTube could translate, and translate the text yourself.
  • Transcribe videos without captions. No speech-to-text here. Those videos come back no_captions, with their metadata, at the low video-unavailable price.
  • Captions of a stream while it is live, members-only videos, private videos, and age-restricted videos (YouTube asks for a signed-in adult).
  • Exact subscriber and comment counts. YouTube rounds them for everyone but the channel owner; its official API rounds subscribers too.
  • Comment text.

FAQ

The Actor reads only what any visitor can see on a public video without signing in, and uses no account. Captions are the creator's work: check copyright and YouTube's terms before republishing them. This is not legal advice.

Why did I get the auto-generated transcript when the video has uploaded captions?

Uploaded captions win only within the same language. If you ask for es and the video has uploaded English and auto-generated Spanish, you get the Spanish. Put the languages in the order you want them, or turn off fallback.

How do I get only new videos from a channel?

Put the channel in urls, set publishedAfter to 7 days, and schedule the run every week. Channels list newest first, so the Actor stops paging at the first video that is certainly older, and checks every video it keeps against its exact publish date before reading the transcript. Older videos are left out and not charged.

Where do the chapters come from?

Without the extras, from the timestamps in the description, read with YouTube's rules: the first is 0:00, there are at least three, in ascending order, each at least ten seconds long. With the extras on, from YouTube's own chapter list, which also includes chapters YouTube generated itself for videos whose description has none.

What happens if Apify restarts the run?

Every row is recorded in the key-value store right after it is written. A restart lists the channels and playlists again, skips the videos already written, and reads again the ones that were still in progress, so a written row is neither repeated nor charged twice. When Apify announces a migration, the Actor stops writing at once and leaves the videos in progress to the restart.

Something broke or looks wrong

Open an issue on the Issues tab with the video link and the run link. YouTube changes its internal API without notice; when a response stops making sense, rows come back parse_error instead of empty text, so the problem shows.