YouTube Transcript Scraper — Never Silent, LLM-Ready
Pricing
from $8.00 / 1,000 transcript delivereds
YouTube Transcript Scraper — Never Silent, LLM-Ready
Public YouTube transcript scraper with full batch accounting: every input returns a transcript or a typed, actionable failure — the run is never silent. Supports direct videos plus playlist/channel expansion, caption-availability auditing, and LLM-ready output as JSON, text, SRT, WebVTT, or chunks.
Pricing
from $8.00 / 1,000 transcript delivereds
Rating
0.0
(0)
Developer
Catalin Ionut Iliescu
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 days ago
Last modified
Share
Extract public YouTube captions at scale with honest batch accounting. In a normally completed run with functioning platform storage, every accepted input returns either a transcript or a typed, actionable failure. Feed it public video URLs, playlists, or channels and get back clean text, timestamped segments, SRT/WebVTT subtitles, or LLM-ready chunks.
Python-only runtime. No Node, YouTube account, retained cookies, API key, audio download, or browser automation.
Why this Actor
Most transcript scrapers quietly drop the videos they can't handle: you send 100 URLs, get 63 rows back, and have no idea what happened to the other 37. This one is built around a different promise — complete accounting:
- Never silent. In a normally completed run with functioning platform storage, every input
produces one accounted result. Successes carry the transcript; failures carry a typed status
(
no_captions,video_unavailable,rate_limited,blocked,language_unavailable, …) plus a plain-English remediation. - Typed, actionable failures. Each failure says what went wrong and whether retrying will help, so you can react programmatically instead of guessing.
- Playlist & channel expansion. Give it a public playlist or channel and it lists the videos
(a bounded
maxItemsprefix, newest-first for channels) and extracts each transcript, with a parent-levelsourceExpansionRecordaccounting for what was discovered, admitted, and omitted. - Caption‑availability audit mode. A text‑free mode that reports which caption tracks and languages a video advertises — manual vs. automatic — without downloading any transcript.
- LLM‑ready output. Deterministic overlapping chunks with token estimates, ready to embed or feed to a model.
- Run‑level accountability report. A machine‑readable summary in the key‑value store reconciles every expected input against what was actually written.
⚠️ Proxy: use Residential
YouTube blocks datacenter IP ranges, and the Apify cloud runs on datacenter IPs. Because of
this, proxyMode defaults to residential, which is the only mode that reliably reaches
YouTube from the cloud. direct, apify_auto, and datacenter are frequently rate-limited
or blocked — and when that happens the Actor tells you so with a typed blocked / rate_limited
result rather than silently returning nothing.
The residential proxy cost is included in the per-transcript price. You are not billed separately for proxy traffic, and you do not need to supply your own proxy. Measured on real runs, residential transfer is 85–96% of what this Actor actually costs to operate — that cost is carried in the price rather than passed through to you.
What you are charged for
Charging is driven by what actually lands in your dataset, so you pay for delivered output and never for work the Actor didn't do. See the pricing box on the Store page for exact figures.
Two rules decide every video row:
- No attempt, no charge. If the Actor never made a network request for an input, that row is free.
- You pay for answers about the video, never for our routing problems. A transcript, a caption inventory, or a definitive verdict about the video itself is chargeable. A blocked, rate-limited, or timed-out request is not.
| Outcome | Charged |
|---|---|
| Delivered transcript | Full transcript price |
Caption-audit answer (available / none) | Audit price, below a transcript |
Definitive verdict about the video — no_captions, language_unavailable, video_unavailable, age_restricted, region_blocked | Half the transcript price |
| Playlist/channel listing that returned videos | Listing price, never a transcript price |
| Duplicate video already extracted in this run | An eighth of the transcript price |
blocked, rate_limited, timeout, upstream/proxy errors | Free — our problem, not yours |
Inputs beyond maxVideos, invalid URLs | Free — no network work was performed |
| Inputs skipped after a charge limit or a dead route | Free — never attempted |
| Listing that returned no videos | Free |
| Rows the dataset never confirmed | Free |
When a run stops early
Two conditions stop the Actor from spending on work it cannot deliver or bill:
- Charge limit reached. If you set a maximum charge for the run, extraction stops rather than
producing output past your limit. Remaining inputs return
extraction_error/CHARGE_LIMIT_REACHED. - Route definitively blocked. After three consecutive
blocked/rate_limitedresults with no success in between, the Actor stops rather than repeating a failing request for every remaining video. Remaining inputs returnblocked/REQUEST_BLOCKEDwithattempts: [].
In both cases every remaining input still gets its own typed, unbilled row explaining why it was
not attempted, and the ACCOUNTABILITY_REPORT carries a runStop section (stopped, stopReason,
skippedInputCount). The OUTPUT summary surfaces runStopped and runStopReason.
Input
Transcript mode (default)
{"videoUrls": ["https://www.youtube.com/watch?v=VIDEO_ID","https://youtu.be/VIDEO_ID"],"sources": [{ "type": "playlist", "value": "PUBLIC_PLAYLIST_ID", "maxItems": 25 },{ "type": "channel", "value": "@public_handle", "maxItems": 25 }],"languages": ["en"],"languageFallback": true,"outputFormat": "llm","includeMetadata": true,"includeSegments": true,"includeTranscriptText": true,"includeChunks": true,"maxVideos": 25,"maxConcurrency": 3,"perVideoTimeoutSecs": 30,"proxyMode": "residential"}
videoUrls accepts watch, youtu.be, Shorts, embed, and live URL shapes. sources is an
optional ordered list of video, playlist, or channel entries; videoUrls and sources
can be combined.
Expansion behavior. Playlist and channel expansion reads YouTube's current public listing format and returns each source's videos as a bounded
maxItemsprefix (channels newest‑first), with a parentsourceExpansionRecordaccounting for discovery. Sources larger thanmaxItemsare reportedlimited. Because it relies on an unofficial listing surface, a future YouTube format change fails safe with a typedsource_protocol_errorrather than dropping videos silently; directvideoUrlsare always unaffected.
Caption‑audit mode
{"operationMode": "caption_audit","videoUrls": ["https://youtu.be/VIDEO_ID"],"languages": ["en", "de"],"includeMetadata": true,"proxyMode": "residential"}
Audit mode never downloads a transcript. Each row reports the advertised caption inventory —
track count, available languages, and whether captions are manual, automatic, or of unknown
generation — or a fail‑closed unknown when a complete inventory can’t be safely established.
languages expresses optional interests and is reported as available/unavailable without
filtering the full inventory.
Key options
| Option | Default | Notes |
|---|---|---|
outputFormat | llm | json, text, srt, vtt, or llm (text + chunks). |
languages | ["en"] | Priority‑ordered caption language codes. |
languageFallback | true | Use another advertised track if the preferred language is missing. |
includeSegments / includeTranscriptText | true / true | At least one must stay enabled; current successful rows always retain both core payloads. |
includeChunks / chunkMaxTokens / chunkOverlapTokens | true / 1000 / 100 | LLM chunking; overlap ≤ half of max. |
maxVideos | 25 | Fetch budget. Inputs beyond it still get a typed MAX_VIDEOS_LIMIT row. |
maxConcurrency | 3 | Videos processed at once. |
perVideoTimeoutSecs | 30 | One absolute deadline per video (max 120). |
proxyMode | residential | See the proxy note above. |
proxyCountry | "" | Optional two‑letter country code. |
debugMode | false | Adds safe per‑attempt caption/HTTP diagnostics (never secrets). |
Output
Each result is pushed to the dataset as soon as it finishes, so dataset order is completion
order — sort by inputIndex if you need input order. A successful transcript row looks like:
{"inputIndex": 0,"videoId": "M7lc1UVf-VE","canonicalUrl": "https://www.youtube.com/watch?v=M7lc1UVf-VE","status": "ok","statusDetail": "Extracted 466 caption segments in 'en' through youtube-transcript-api.","errorCode": "","remediation": { "actionCode": "OK_NO_ACTION", "retryClass": "none", "retryRecommended": false },"languageUsed": "en","languageAvailable": ["en"],"isAutoGenerated": false,"title": "YouTube Developers Live: Embedded Web Player Customization","channelName": "Google for Developers","durationSeconds": 1344,"viewCount": 1642048,"publishedAt": "2013-04-10T10:25:04-07:00","transcriptText": "JEFF POSNICK: Hey, everybody. Welcome to this week's show ...","segments": [ { "text": "JEFF POSNICK: Hey, everybody.", "startSeconds": 10.349, "durationSeconds": 1.0, "endSeconds": 11.349 } ],"chunks": [ /* deterministic overlapping LLM chunks with token estimates */ ],"transcriptCharCount": 20676,"tokenCountEstimate": 5169,"extractionStrategy": "youtube_transcript_api","proxyModeUsed": "residential","fetchedAt": "2026-07-14T18:06:05Z"}
A failed row carries empty transcript payloads plus the typed diagnosis:
{"inputIndex": 2,"videoId": "…","status": "video_unavailable","errorCode": "VIDEO_UNAVAILABLE","statusDetail": "The library reports that the public video is unavailable.","remediation": { "actionCode": "CHECK_PUBLIC_AVAILABILITY", "retryClass": "manual_review", "retryRecommended": false },"transcriptText": "", "segments": [], "chunks": []}
Playlist/channel sources emit a sourceExpansionRecord describing what was discovered
(expansionStatus, admittedChildren, discoveryCompleteness, ordering, and a typed
primaryError + remediation on failure). Expanded child videos are ordinary transcript rows
tagged with their sourceType and sourceInputIndex.
Statuses
ok, no_captions, language_unavailable, video_unavailable, age_restricted,
region_blocked, blocked, rate_limited, timeout, invalid_url, extraction_error,
unknown_error. Read status, errorCode, statusDetail, and remediation together —
null metadata alone never means success.
Accountability report
After processing, the Actor writes an ACCOUNTABILITY_REPORT record to the default key‑value
store: expected vs. accounted vs. missing inputIndex values, status/error/strategy tallies,
and (for expansion runs) source‑ and child‑level counts. accountingComplete is true only
when every expected input has a confirmed dataset row.
It also carries a charging section (what the run billed, and what it would have billed) and a
runStop section describing any early stop.
Extraction architecture
One proven primary engine, with corroborating fallbacks that share code with the audit feature:
youtube-transcript-api— the primary, battle‑tested engine. It fetches captions through YouTube’s anonymous InnerTube player surface and wins the vast majority of transcripts.- Anonymous InnerTube fallbacks (
android_innertube,direct_timedtext,innertube_player) — Actor‑owned public player/timed‑text paths tried only if the primary can’t produce a usable transcript. They also produce the positive caption‑availability evidence that powers caption‑audit mode, so they’re not dead weight.
All paths use anonymous public surfaces only, run through the selected proxy, discard cookies,
and never accept or emit keys, tokens, caption URLs, or proxy credentials. The chain stops at the
first usable transcript and records every attempt (visible with debugMode).
Limitations
- Public captions only. Age‑restricted, private, deleted, members‑only, and login‑required
videos are intentionally unsupported. Translations are not generated —
languageFallbackselects an existing alternate track, it does not machine‑translate. - YouTube can change. These are anonymous web/player surfaces with no official public transcript API; upstream changes can require maintenance.
- Playlist/channel expansion is bounded. Each source returns up to a
maxItemsprefix (1–50, newest‑first for channels); larger sources are reported aslimited. It reads YouTube's current public listing surface, so upstream format changes fail safe with a typed error rather than wrong data; direct video URLs are unaffected. - Proxy‑dependent. Reliable cloud extraction requires residential egress (see above).
- Token counts are approximate and deterministic, not tied to a specific tokenizer.
- Live/premiere/just‑uploaded videos and very recently changed captions are less predictable.
Use from the Apify Console, API, or integrations
Run the Actor from its Input tab, save reusable tasks, call it through the Apify API, or connect it to your automation and AI workflows.
Results are available in the default dataset, while the bounded run summary and full accountability report are available from the run output and key-value store.
Only use this Actor for content you are entitled to process. Public availability does not grant ownership or remove copyright, privacy, or platform‑policy obligations.