YouTube Transcript & Subtitle Scraper avatar

YouTube Transcript & Subtitle Scraper

Pricing

from $1.00 / 1,000 transcript fetcheds

Go to Apify Store
YouTube Transcript & Subtitle Scraper

YouTube Transcript & Subtitle Scraper

Extract transcripts and subtitles from YouTube videos in bulk using video, playlist, channel URLs, or keyword search. Returns timed transcript segments, plain text, SRT, and WebVTT subtitle files, with optional auto-translation to other languages.

Pricing

from $1.00 / 1,000 transcript fetcheds

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

68

Total users

9

Monthly active users

13 days ago

Last modified

Share

YouTube Transcript Scraper: Captions, Timestamps & Subtitles

Turn any YouTube video into readable text. Give this actor a video link, a playlist link, a channel link, or a search keyword, and it returns each video's transcript as timed segments, a single block of plain text, and ready to use SRT and WebVTT subtitle files, with optional translation into another language. Export the results to JSON, CSV or Excel, or pull them straight into your app through the API.

Why This Scraper?

  • Four ways in. Mix direct video links or IDs, playlist links, and channel links (@handle, /channel/UC..., /c/..., /user/...) in one run, or switch to search mode to find videos by keyword.
  • Three formats per video. Every record carries the transcript as timed segments, a single plain text transcript field, and srt plus vtt strings you can save straight to disk.
  • Language control. List your preferred languages in priority order, or translate the result into another language YouTube supports.
  • Handles both caption types. Works with human written captions and auto generated ones, and falls back to whatever the video offers when none of your preferred languages are available.
  • Time windows on demand. Trim every transcript to a specific second range, and pay only for the trimmed text.
  • Built for large pulls. Resume an interrupted playlist or channel crawl, or schedule the actor to run on a recurring basis and get only what's new, updated, or gone since last time.
  • Pay only for transcripts you get. Failed, captions disabled, or incremental mode suppressed videos are never billed.

Use Cases

  • Content repurposing: turn a video's spoken words into blog posts, show notes, or social captions without watching it end to end.
  • Research and note taking: skim long lectures, podcasts, or interviews as searchable text instead of sitting through the video.
  • Localization and accessibility: generate subtitle files or translated transcripts for viewers who need them.
  • SEO and content indexing: feed video transcripts into your own search tool or content database.
  • Media monitoring: track new uploads from a channel or keyword and pull their transcripts automatically on a schedule.

Data You Get

Sample shape: values are illustrative placeholders, not from a live record.

FieldExample
videoId"EXAMPLE_ID1"
videoUrl"https://www.youtube.com/watch?v=EXAMPLE_ID1"
videoTitle"Sample Video Title"
channelName"Sample Channel"
channelUrl"https://www.youtube.com/@SampleChannel"
durationSeconds213
source"python tutorial" (the URL or search query that produced this video)
sourceType"search" (also "video", "playlist", "channel")
language"English"
languageCode"en"
isGeneratedfalse (auto generated caption when true)
isTranslatedfalse
translatedTonull
charCount5234
segmentCount142
transcript"Hello and welcome to this sample transcript..."
segments[{ "text": "Hello and welcome", "start": 0.0, "duration": 1.84 }, ...]
data[{ "start": 0.0, "dur": 1.84, "text": "Hello and welcome" }, ...] (same snippets as segments, in the start / dur / text shape other transcript tools use; numeric)
srt"1\n00:00:00,000 --> 00:00:01,840\nHello and welcome\n\n..."
vtt"WEBVTT\n\n00:00:00.000 --> 00:00:01.840\nHello and welcome\n\n..."
trimmedStart / trimmedDuration0 / 0 (the time window applied to this video; 0 means no trim)
successtrue
errornull (a short message when a transcript could not be fetched)
fetchedAt"2026-07-30T08:13:20Z" (when this row was fetched, ISO 8601 UTC)

changeType, changedFields, firstSeenAt and lastSeenAt are added only when Incremental mode is on; a normal run's rows keep the shape above without them.

How to Use

  1. Pick a mode: url (paste video, playlist, or channel links) or search (find videos by keyword).
  2. Fill in the URLs or search terms for that mode, and set your preferred languages.
  3. Optionally trim the time window, cap how many videos to process, or turn on translation.
  4. Click Start, then download the dataset as JSON, CSV, or Excel, or read it through the API.

Fetch one video's transcript:

{
"mode": "url",
"videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"languages": ["en"]
}

Search YouTube by keyword and transcribe the top results:

{
"mode": "search",
"searchQueries": ["langgraph tutorial", "apify actor development"],
"maxVideosPerSource": 5,
"languages": ["en"]
}

Expand a playlist and a channel, capped per source, translated to English:

{
"mode": "url",
"videoUrls": [
"https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx",
"https://www.youtube.com/@SomeChannel"
],
"maxVideosPerSource": 25,
"maxVideos": 100,
"languages": ["en"],
"translateToLanguage": "en"
}

Trim every transcript to a specific time window:

{
"mode": "url",
"videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"startSec": 30,
"durationSec": 30,
"languages": ["en"]
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/youtube-transcript-scraper").call(run_input={
"mode": "url",
"videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"languages": ["en"],
})
for video in client.dataset(run["defaultDatasetId"]).iterate_items():
print(video["videoTitle"], video["charCount"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/youtube-transcript-scraper').call({
mode: 'url',
videoUrls: ['https://www.youtube.com/watch?v=dQw4w9WgXcQ'],
languages: ['en'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted crawl. Paste its run or dataset ID and this run skips every video already collected there, so a large playlist or channel pull picks up where it left off without re fetching, or re billing, the same videos.
  • Incremental mode (incrementalMode) is for scheduling this actor on the same videoUrls/searchQueries setup again and again (daily, weekly) and getting only what changed. Every video is classified NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED. By default only NEW, UPDATED, and REAPPEARED are returned and billed; turn on emitUnchanged or emitExpired to also get those. stateKey names or shares one monitoring campaign's saved state. A video is UPDATED when any returned field differs from the last run; the run timestamp fetchedAt and anything you list in ignoreFieldsForChanges are never compared.
  • EXPIRED is only ever produced in mode: search, and only once a run has scanned every tracked query to its end. In mode: url the tracked set is whatever links you pasted, so a missing one simply wasn't pasted this run, not confirmed gone.

URL shapes

#ShapeExample
1Watchhttps://www.youtube.com/watch?v=dQw4w9WgXcQ
2youtu.be short linkhttps://youtu.be/dQw4w9WgXcQ
3Mobile watchhttps://m.youtube.com/watch?v=dQw4w9WgXcQ
4Shortshttps://www.youtube.com/shorts/jNQXAC9IVRw
5Live (replay)https://www.youtube.com/live/IoL51xLkMSY
6Embedhttps://www.youtube.com/embed/dQw4w9WgXcQ
7Watch with list= (treated as that one video)https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI
8Playlisthttps://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI
9Channel @handlehttps://www.youtube.com/@YouTube
10Channel @handle/videos tabhttps://www.youtube.com/@YouTube/videos
11Channel /channel/UC...https://www.youtube.com/channel/UCBR8-60-B28hp2BmDPdntcQ
12Legacy /user/https://www.youtube.com/user/YouTube
13Legacy /c/https://www.youtube.com/c/YouTubeCreators
14Search results page (expanded like a keyword search)https://www.youtube.com/results?search_query=python+tutorial

Default sample (behavior change): an untouched URL mode run, or an API call that omits videoUrls, now scrapes a sample of all 14 shapes instead of failing with an empty input. Videos are deduplicated, so the 5 single video shapes on dQw4w9WgXcQ count once. Billing is per 4,000 character transcript unit, not per video, and long videos bill many units each (one 500,000 character tutorial alone is 125 units). To keep an untouched run cheap, while the 14 sample URLs are exactly as shipped, each playlist, channel, or search source is capped at 1 video (the log says so). Measured on a local test run through the Apify proxy: 7 videos, 73 billed units (about $0.16 at the $0.0022 unit price shown in Pricing), 67 of them from the single top "python tutorial" search hit. Edit the list in any way (add, remove, or replace a URL) and the cap lifts, so maxVideosPerSource (default 10) applies and a search or channel source can bill hundreds of units. Set maxVideosPerSource / maxVideos to bound your own run. A URL that is not a recognised shape is skipped with a warning in the log. If an API call sends only videoUrl (or only searchQueries), the platform still fills in the 14-URL sample, so the actor drops the untouched sample: only your videoUrl is scraped, and when only searchQueries is given the run switches to keyword search mode. Your own videoUrls, if you send any, are never dropped.

Send results into your apps (MCP connectors)

Optionally pipe the scraped transcripts into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestring"url""url" (read videoUrls) or "search" (read searchQueries). The other field is ignored.
videoUrlstring(none)Alias for a single URL; added to videoUrls. URL mode only.
videoUrlsarraysample of 14 URLs (see "URL shapes")Used when mode = url. Watch, youtu.be, mobile, shorts, live, and embed URLs, playlist URLs, channel URLs (@handle, @handle/videos, /channel/, /c/, /user/), YouTube search results URLs, or 11 character video IDs. Playlists, channels, and search results pages are expanded to their videos.
searchQueriesarray[]Used when mode = search. Keywords to search YouTube; top results per query, up to Max videos per source, are fetched.
startSecinteger0Trim every transcript to start at this second. 0 keeps from the beginning of each video.
durationSecinteger0Trim every transcript to this many seconds from startSec. 0 keeps until the end of each video.
maxVideosPerSourceinteger10How many videos to take from each playlist, channel, or search query.
maxVideosinteger100000Hard cap on the total number of videos processed across all sources. Up to 100000; 0 (from older saved tasks) is read as 100000.
languagesarray["en"]Preferred transcript language codes in priority order. The first available one is used; if none are available, any transcript YouTube offers is returned.
translateToLanguagestring""Optional language code to translate the transcript into using YouTube's auto translation. Empty keeps the original language.
targetLanguagestring""Alias of translateToLanguage; used only when that field is empty.
preserveFormattingbooleanfalseKeep inline formatting tags (such as italics) in the transcript text instead of stripping them.
resumeFromRunIdstring(none)Continue one interrupted run: paste a previous run ID or dataset ID and this run skips videos already collected there.
incrementalModebooleanfalseTurn on for recurring monitoring of the same setup. Classifies each video as NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED.
emitUnchangedbooleanfalseIncremental mode only. Also return (and bill) videos whose transcript has not changed since the last run, marked UNCHANGED.
emitExpiredbooleanfalseIncremental mode only, mode: search only. Also return videos no longer found, marked EXPIRED. Never billed.
ignoreFieldsForChangesarray[]Incremental mode only. Field names (top-level, or dotted for nested, e.g. segments.start) that never count as a change; fetchedAt is always ignored. Ignored fields still appear in the output.
stateKeystring(none)Optional name for this monitoring campaign's saved state. Leave empty to derive one automatically from the setup.
proxyConfigurationobjectApify Proxy, RESIDENTIALConnection settings. RESIDENTIAL is the default; datacenter groups also fetched transcripts in a small low-volume test (US only), so you may pick one to lower proxy cost and switch back if many videos return errors.
mcpConnectorsarray(none)Optional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: page under which records are created.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.

Output Example

Sample shape: values are illustrative placeholders, not from a live video.

{
"videoId": "EXAMPLE_ID1",
"videoUrl": "https://www.youtube.com/watch?v=EXAMPLE_ID1",
"videoTitle": "Sample Video Title",
"channelName": "Sample Channel",
"channelUrl": "https://www.youtube.com/@SampleChannel",
"durationSeconds": 213,
"source": "https://www.youtube.com/watch?v=EXAMPLE_ID1",
"sourceType": "video",
"language": "English",
"languageCode": "en",
"isGenerated": false,
"isTranslated": false,
"translatedTo": null,
"charCount": 5234,
"segmentCount": 142,
"transcript": "Hello and welcome to this sample transcript.\nThis is the second line of the transcript.",
"segments": [
{ "text": "Hello and welcome to this sample transcript.", "start": 0.0, "duration": 2.32 },
{ "text": "This is the second line of the transcript.", "start": 2.32, "duration": 2.08 }
],
"srt": "1\n00:00:00,000 --> 00:00:02,320\nHello and welcome to this sample transcript.\n",
"vtt": "WEBVTT\n\n00:00:00.000 --> 00:00:02.320\nHello and welcome to this sample transcript.\n",
"trimmedStart": 0,
"trimmedDuration": 0,
"success": true,
"error": null,
"fetchedAt": "2026-07-30T08:13:20Z"
}

With incrementalMode on, rows also carry changeType, changedFields, firstSeenAt, and lastSeenAt.

Output views and run summary

The dataset has two views. Overview lists the video, channel, language, character count and success columns. Changes (Incremental mode) adds changeType, changedFields, firstSeenAt and lastSeenAt. On a failed or blocked row (success: false), language, languageCode, isGenerated and translatedTo are null, the text fields (transcript, srt, vtt) are empty strings, segments and data are empty lists, and charCount and segmentCount are 0; error explains what happened. Every run that finds at least one video also saves a run summary under the OUTPUT key of the key-value store (inputs, videos discovered, transcripts fetched, videos failed, billed transcript units, rows pushed, videos per source; searchQueriesResolved counts keyword searches in search mode only).

Plan Requirement

This actor needs a live connection to YouTube for every transcript fetch, through an Apify proxy. The RESIDENTIAL group is selected by default. Datacenter groups also fetched full pages and transcripts in a small, low-volume test from US exits, so you may pick one to lower proxy cost. That test was a few requests on one day: it does not prove datacenter holds at high volume or from other countries, and YouTube is known to block many datacenter IPs. If many videos return errors, switch back to RESIDENTIAL.

On a free Apify plan (no residential proxy) the run logs this notice and continues on a backup connection:

The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.

Videos served that way log "Served over the backup connection (the default connection was unavailable)." If no page could be read at all, the run ends as FAILED (never a silent success with zero rows) with one of these messages:

  • Proxy refused for this account: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required." If you supplied your own proxy URLs and every one of them was refused, the run ends with this same message: check that their credentials are valid.
  • Residential unavailable and backup failed: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested YouTube pages."
  • Requests failed: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

Pricing

Pay per event, as registered on the Store page (Bronze tier shown; higher tiers (Silver, Gold, Platinum, Diamond) are cheaper; the Free tier price is in brackets):

EventPriceWhen it is charged
transcript$0.002 per unit ($0.0022)One unit per 4,000 characters (rounded up, minimum one unit) of each successfully fetched transcript that is returned in the dataset, measured on the trimmed text
search-query$0.0018 ($0.002)One per keyword search (search mode only) that YouTube answered, even if it returned no videos; a search that errors is not charged
apify-actor-start$0.05 ($0.06)Per run start, per GB of memory (minimum one)

Failed, captions disabled, blocked, and Incremental-mode suppressed (UNCHANGED) videos are never charged.

FAQ

How much does it cost?

You pay per run, plus a small fee per keyword search YouTube answers (search mode only), plus a length based unit per fetched transcript, roughly one unit per 4,000 characters of transcript text. Failed or captions disabled videos are never billed, and if you trim a transcript with startSec/durationSec you only pay for the trimmed text. The Pricing tab on this actor's Store page shows the current rates.

This actor reads publicly available captions attached to a video, the same ones YouTube's own player can display. You are responsible for how you use the results: check YouTube's Terms of Service and the copyright rules that apply to the video's spoken content in your jurisdiction before republishing or redistributing a transcript, especially at scale or for commercial use.

What's the difference between Resume and Incremental mode?

Resume (resumeFromRunId) continues one specific interrupted run, so a large playlist or channel pull that got cut off keeps going without re fetching (and re billing) videos it already collected. Incremental mode is for scheduling the same videoUrls/searchQueries setup again and again and getting only what's new or changed since the last run. They solve different problems and are not normally combined.

Why did I get an auto generated transcript instead of the creator's captions?

The actor prefers a human written transcript over an auto generated one whenever both exist in one of your Preferred transcript languages. If the creator did not upload captions in any of your preferred languages, the actor falls back to whatever the video offers, which may be auto generated.

Why did my run fail instead of returning an empty or partial dataset?

The run fails outright when no page could be read at all (see Plan Requirement), when a resumeFromRunId that cannot be resolved to a run or dataset you own, or when you turn on Incremental mode together with resumeFromRunId when that monitoring setup already has saved state from an earlier run. Otherwise, including empty videoUrls/searchQueries, a video with disabled captions, a private or region locked video, or a temporary block from YouTube on some of the videos, still finishes the run and adds a dataset row explaining what happened, with success set to false, so a partial run always gives you every transcript that could be read. The exception is a run in which not one video got an answer from YouTube (for example every video was temporarily blocked): that run ends FAILED with the connection message described under Plan Requirement, though the rows describing each blocked video are still in the dataset.

Can I get only new or changed transcripts on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then classifies every video, and only new, updated, and reappeared rows are returned and billed by default.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to send a summary of each transcript to Notion, Linear, or Airtable at the end of each run.

🔗 Want more video data?

Pair this actor with these related scrapers from the same team:

📱 Universal Media Extractor
Extract videos, audio, and metadata from 1000+ websites including YouTube, TikTok...
📱 Lemon8 Media Scraper
Lightweight Lemon8 scraper for extracting image and video URLs from posts, with optional...
📱 ViewStats Scraper
Scrape YouTube channel analytics from ViewStats. Look up channels by handle, URL, or...
📱 TikTok Profile, Hashtag, Search, Video & Trending Scraper
Scrape TikTok without login. Extract profiles, bios, follower stats and videos; search by...
🎬 Twitch ALL IN ONE URL
From $1/1K. Scrape Twitch data at scale, including channels, live streams, clips, VODs...
📱 Tiktok Live Recorder
Record TikTok live streams to MP4 with full metadata, all stream quality URLs, and...

👉 Browse all abotapi scrapers

💬 Support

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.