YouTube Transcript Scraper with Chapters & Heatmap
Pricing
from $4.00 / 1,000 transcript extracteds
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
Maintained by CommunityActor 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
- 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. - Set Preferred languages, for example
enthenes. If none exists, the video's own language is returned instead, and the row says so. - 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 daysand schedule the run weekly. - Click Start. A video usually takes one to three seconds; five are read at a time.
- Open the Output tab or export the dataset. Videos without a transcript are in the table too, with a
statusand the reason.
How much does it cost to scrape YouTube transcripts?
You pay per video, and only for what was delivered.
| Event | Charged when | Price | Per 1,000 videos |
|---|---|---|---|
transcript-extracted | The transcript and metadata of one video were returned (status: ok) | $0.004 | $4.00 |
tracks-listed | In caption track list only mode: the track list and metadata of one video were returned | $0.001 | $1.00 |
chapters-heatmap | With Chapters, heatmap and channel extras on, on top of the row, only when that data was actually returned | $0.001 | $1.00 |
video-unavailable | YouTube 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
| Field | Default | Notes |
|---|---|---|
urls | Videos, 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 @ | |
searchQueries | Each query returns the top videos for it | |
maxVideosPerSource | 20 | Per channel, playlist and search, up to 5,000 |
publishedAfter | 2026-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 | |
channelContentType | videos | videos, 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 |
fallbackToAnyLanguage | true | Off: a video without any of your languages comes back language_unavailable with the tracks it does have |
preferManualCaptions | true | Within one language, captions uploaded by the creator beat YouTube's auto-generated ones |
mode | transcript | tracksOnly returns the track lists and metadata, without downloading captions |
outputFormats | segments, text | Add srt and vtt for subtitle files. Formats you leave out are null in the row |
includeWordTimestamps | false | Start and end of every word. Auto-generated captions only; uploaded captions have no word timings |
includeChaptersAndHeatmap | false | One more request per video, charged as chapters-heatmap |
llmChunking | none | chapters or length. chunkMaxCharacters (4,000) caps each chunk; a caption line is never split |
useResidentialFallback | true | See "Why is a video blocked?" below |
abortAfterConsecutiveFailures | 10 | Stops 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.
| Field | Notes |
|---|---|
status, error, notes | See 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, uploadedAt | ISO 8601, UTC, to the second |
lengthSeconds, viewCount, likeCount | Exact. null on a stream that is live now, or when YouTube hid the figure |
category, isShort, isLive, isLiveContent, isUpcoming, isUnlisted, isFamilySafe, isPrivate, allowRatings, availableCountries | isLiveContent is true for recordings of past streams |
liveStartedAt, liveEndedAt | For streams |
thumbnailUrl, thumbnails | Largest first in thumbnailUrl; every size in thumbnails |
language, languageName, isAutoGenerated | The track returned |
languageMatch | preferred (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, vtt | The whole transcript as one string, or as subtitle files |
segmentCount, wordCount, characterCount | |
isMusicOnly | True when the captions hold nothing but sound tags such as [Music] or ♪: nobody speaks |
captionTracks | Every track: languageCode, name, kind (manual or asr), isAutoGenerated, isTranslatable, vssId, trackName |
captionTrackCount, hasManualCaptions, hasAutoCaptions | |
spokenLanguage | The language of YouTube's auto-generated track, which is the language YouTube heard. null when there is none |
audioTracks, hasDubbedAudio | Dubbed 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}] |
heatmap | With the extras: [{start, end, intensity}], YouTube's replay intensity from 0 to 1 over about 100 slices of the video |
mostReplayed | With 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, channelIsVerifiedArtist | With the extras |
subscriberCount, subscriberCountText, commentCount, commentCountText | With the extras. Rounded by YouTube (8.7M subscribers, 9.4K); the number is parsed from the text, and the text is kept as shown |
extrasIncluded | Whether the extras were returned, and so charged |
playabilityReason | YouTube's own message when it would not play the video |
fetchedVia | datacenter, residential or direct |
source, sourceType, inputIndex | The 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.errorlists the languages there are.not_found,private,age_restricted,live_now,upcoming,unplayable,login_required— YouTube's own answer about the video;playabilityReasonhas 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 thanpublishedAfter).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
isTranslatableto 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 lowvideo-unavailableprice. - 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
Is it legal to scrape YouTube transcripts?
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.