YouTube Transcript Bulk Scraper
Get YouTube transcripts in bulk from video links, a channel or a playlist. Full text, timed cues, language, title and duration. No API key.
How it works
- 1Open it on Apify
Hit Run on Apify — it opens the tool in the cloud, no install.
- 2Set the inputs
Adjust
videoUrls,maxItems,maxVideosPerSource(sensible defaults are pre-filled). - 3Click Run
The tool runs on Apify’s cloud and collects the data for you.
- 4Export the results
Download as JSON, CSV or Excel, or pipe straight into your app, Google Sheets, or an AI agent.
Pricing
$0.0004 per transcript = $0.4 per 1,000
| You are charged for | When | Price |
|---|---|---|
| Transcript extracted | One video transcript delivered as a dataset row. Videos with no captions, private or deleted videos, and channel or playlist listing are never charged. | $0.0004 |
Pay-per-event pricing: you are billed per result, not per subscription. Billing is handled by Apify on your own account. These are the live Apify store prices, in effect since 2026-08-16, and they are what you are actually charged.
Inputs
| Field | What it does | Type |
|---|---|---|
videoUrls | Mix and match: watch / Shorts / youtu.be / embed links, bare 11-character video IDs, a channel URL or @handle, or a playlist link. Channels and playlists are expanded into their videos for you, and that expansion is never charged. | array |
maxItems | Hard ceiling on how many transcripts this run returns in total, across everything you pasted. This is what you pay for, so keep it low while testing. | integer |
maxVideosPerSource | How deep to go into each channel or playlist before moving to the next one. Channels are read newest first. Ignored for links that already point at a single video. | integer |
languages | Language codes in priority order, for example en, es, pt-BR. The first one the video actually has is used; pt matches pt-BR. Leave empty to take whatever caption track the video ships with. | array |
preferAutoGenerated | Off by default, so a human-written caption track wins whenever the video has one - it has punctuation and speaker turns, which auto captions do not. Turn this on if you specifically want the machine transcript. | boolean |
includeSegments | Keep the per-cue array of start / duration / end / text on each row. Turn it off if you only want the plain transcript text and smaller rows. | boolean |
concurrency | How many videos are fetched at the same time. Higher is faster and cheaper per row; lower is gentler if you are pulling several thousand transcripts in one run. | integer |
proxyUrls | Leave this empty for a normal run. Fill it in only if you want the traffic to leave through proxy servers you already pay for, one URL per line, in the form http://user:pass@host:port. | array |
What you get
A structured dataset — each result includes fields like:
videoIdtitlechannelNamedurationSecondslanguagelanguageNameisAutoGeneratedsegmentCountwordCounttexturlsourceTypesourceUrlExport every run as JSON, CSV or Excel, or send it to your app, a database, Google Sheets, or an AI agent.
Related tools in Developer & Research Tools
Other ready-to-run tools in the same category — all pay-per-use on the Apify cloud.
GitHub Scraper
Search GitHub repos and users: stars, forks, language, topics, licence, plus user bio, company and followers. No token needed. $0.90 per 1,000 rows.
Stack Overflow / Stack Exchange Scraper
Search Stack Overflow and Stack Exchange by keyword or tag. Score, answer count, views, reputation and body text. $2 per 1,000 questions.
Package Registry Scraper (npm + PyPI)
Get npm and PyPI package metadata as JSON. Version, license, author, repo, keywords and npm monthly downloads. $2 per 1,000 packages.
arXiv Scraper
Search arXiv papers by title, author, abstract or category. Get full abstracts, authors, categories, DOI, dates and PDF links. $2 per 1,000 papers.
OpenAlex Scholarly Works Scraper
Search 250M+ OpenAlex papers with no API key. Get titles, authors, venue, year, citations, DOI, OA links and full abstracts. $2.00 per 1,000 papers.
Crossref Scholarly Works Scraper
Search 150M+ papers on Crossref: DOI, title, authors, journal, publisher, date, citations and abstract. No API key. $1.00 per 1,000 works.
Where this tool sits
- Categories
- Developer & Research Tools
YouTube Transcript Bulk Scraper: full text and timed cues from a list, a channel or a playlist
Paste video links, a channel or a playlist and get one row per video with the whole transcript as plain text, the per-cue timings, the language, and the video's own details next to it. Channels and playlists are expanded into their videos for you, and that expansion costs nothing.
It reads the caption tracks YouTube already holds. There is no speech recognition here, so a video that was uploaded without captions has nothing to return and comes back as an uncharged diagnostic row instead. You pay for transcripts, not for the videos a channel turned out to have.
| Input | Video links, bare 11-character ids, a channel URL or @handle, or a playlist link |
| Output | One row per transcript |
| Ceiling | 500 entries in, up to 5,000 transcripts out |
| Account needed | None from you |
| Price | $0.40 per 1,000 transcripts, flat on every plan |
🔍 What YouTube Transcript Bulk Scraper does
Mix whatever you have in one list. Watch links, Shorts links, youtu.be links, embed links, bare video ids, a channel URL, an @handle, a playlist link. Channels are read newest first, playlists in their own order, and a video that shows up twice across two sources is fetched once.
You get the transcript two ways on the same row. text is the whole thing as one readable string, already joined up, which is what you want if it is going into a model or a search index. segments keeps every cue with its start, duration and end, which is what you want for jumping to a moment in the video or building captions.
Language is a preference list, not a filter. Put en, es, pt-BR in and the first one the video actually has is used; pt matches pt-BR. A human-written track wins over the machine one unless you say otherwise, because it has punctuation and speaker turns. Whichever track was used is named on the row, and availableLanguages lists everything the video offered.
📥 What you give it
{
"videoUrls": [
"https://www.youtube.com/watch?v=aircAruvnKk",
"https://www.youtube.com/@3blue1brown"
],
"maxItems": 10,
"languages": ["en"]
}
| Field | Default | What it is |
|---|---|---|
videoUrls | none | Up to 500 entries, in any of the forms above, mixed freely. |
maxItems | none | Hard ceiling on transcripts returned by the whole run. This is what you pay for, so keep it small while testing. The form starts you at 10. |
maxVideosPerSource | none | How deep to go into each channel or playlist before moving on. Ignored for links that already point at one video. The form starts you at 25. |
languages | none | Language codes in priority order. Leave it empty to take whatever track the video ships with. |
preferAutoGenerated | false | Turn it on when you specifically want the machine transcript even though a written one exists. |
includeSegments | true | Keeps the per-cue array on each row. Turn it off for plain text and much smaller rows. |
concurrency | none | How many videos are read at once, 1 to 10. The form starts you at 6. |
proxyUrls | none | Optional. Your own servers, as http://user:pass@host:port. Leave it empty otherwise. |
Run it with the input empty and you get one labelled sample row, uncharged, so you can look at the shape before you queue a real list.
📤 What you get back
A real row from a real run. text and segments are cut short here; on the real row text runs to 822 characters and segments has all 13 cues:
{
"ok": true,
"charged": true,
"recordType": "transcript",
"videoId": "6XPlmCDNLNc",
"url": "https://www.youtube.com/watch?v=6XPlmCDNLNc",
"title": "The 64 sugar cubes puzzle",
"channelName": "3Blue1Brown",
"channelId": "UCYO_jab_esuFRV4b17AJtAw",
"channelUrl": "https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw",
"durationSeconds": 55,
"viewCount": 322213,
"thumbnailUrl": "https://i.ytimg.com/vi/6XPlmCDNLNc/sddefault.jpg?sqp=...",
"language": "en",
"languageName": "English",
"isAutoGenerated": false,
"segmentCount": 13,
"wordCount": 160,
"characterCount": 822,
"text": "It's time for a new puzzle of the month. This one comes from a problem on the 2024 British Math Olympiad. ...",
"segments": [
{ "start": 0, "duration": 1.7, "end": 1.7, "text": "It's time for a new puzzle of the month." },
{ "start": 2.08, "duration": 3.56, "end": 5.64, "text": "This one comes from a problem on the 2024 British Math Olympiad." }
],
"availableLanguages": [
{ "languageCode": "en", "languageName": "English", "isAutoGenerated": false },
{ "languageCode": "en", "languageName": "English (auto-generated)", "isAutoGenerated": true }
],
"sourceType": "channel",
"sourceUrl": "https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw",
"playlistId": "UUYO_jab_esuFRV4b17AJtAw",
"scrapedAt": "2026-09-21T01:27:33.286Z"
}
| Field | What it is |
|---|---|
text | The transcript joined into one string. wordCount and characterCount measure this field. |
segments | Every cue with start, duration and end in seconds. Present only while includeSegments is on. |
isAutoGenerated | Whether the track actually used was the machine one. Worth keeping, since punctuation and accuracy differ a lot. |
availableLanguages | Every track the video offered, so you can see what else you could have asked for. |
sourceType, sourceUrl | Which of your entries produced this row: video, channel or playlist. Useful when you pasted a mixed list. |
viewCount, durationSeconds | Read off the video itself at the time of the run. |
🧾 Reading the output
Three kinds of row land in your dataset, and recordType separates them.
| Row | How to spot it | Billed |
|---|---|---|
| A real transcript | recordType: "transcript", ok: true | yes |
| The sample row | recordType: "sample", _sample: true | no |
| A diagnostic | recordType: "diagnostic", ok: false, an errorCode | no |
Filter on recordType == "transcript" and you have your transcripts. The charged flag is written onto the row as it is built, so treat it as the marker that tells a real row from a sample or a diagnostic, not as a receipt for what was billed.
| Code | What it means |
|---|---|
BAD_INPUT | That entry was not a video, channel or playlist link. Only the first 25 rejects get a row. |
NOT_FOUND | The handle, channel or video does not exist any more. |
NO_RESULTS | Nothing to return: a video with no captions, a channel with no uploads, a private playlist. |
BLOCKED | YouTube refused that video on this run. |
RATE_LIMITED | YouTube asked us to slow down on that video. |
SERVER_ERROR | YouTube answered with an error of its own. |
NETWORK | The connection failed, or the video would not open for a reason it did not name. |
TIME_BUDGET | The run ran out of time before reaching that video. One row per video left over. |
PROXY_INPUT_ADJUSTED | A setting you gave in proxyUrls was not available here and was replaced. |
CHARGE_ERROR | Billing failed on a row that was already delivered. Tell us if you see it. |
UNEXPECTED_ERROR | Something we did not classify. Send the run id. |
▶️ How to run it
1. Open YouTube Transcript Bulk Scraper and click Try for free. 2. Paste links into Videos, channels or playlists, one per line. 3. Set Maximum transcripts low for the first run, since that is what you pay for. 4. Put your language codes into Preferred languages in the order you want them tried. 5. Click Start, then download the dataset as JSON, CSV or Excel, or pull it from the Apify API.
💰 How much does it cost?
$0.40 per 1,000 transcripts. Flat on every Apify plan, no volume tiers.
Only a delivered transcript counts. Expanding a channel or a playlist into its videos adds nothing, however many it finds. A video with no captions, a rejected entry, a video that would not open and the sample row are all uncharged rows.
A video that appears in two of your sources is fetched once and counted once.
💡 What people use it for
- Feeding a model. Pull a whole channel, keep
text, and you have a corpus that needs no audio
handling at all.
- Making a video searchable. Keep
segmentsand a hit gives you the second to jump to. - Repurposing. Long-form episodes become newsletter drafts, show notes and clip candidates faster
from text than from video.
- Research across creators. Run ten channels and count who talks about what, with dates and view
counts already on the row.
🚧 What it does not do
- Captions only. There is no speech recognition. If the uploader published no caption track, no
amount of retrying will produce text.
- No translation. You get the tracks the video already carries. Asking for
fron a video with
only English gets you the English track, not a translation.
- Channels come from the uploads list, newest first. That misses unlisted videos and sometimes
misses Shorts, and there is no date filter, so reach older videos with maxVideosPerSource rather than a date range.
- A watch link carrying
list=is treated as one video, not as the playlist. Paste the
playlist link itself if you want the playlist.
- The 5,000 ceiling is the schema's, not a measured promise. One run was measured carrying
about 830 transcripts before memory became the limit. Long videos make big rows: a 3 hour 13 minute podcast came back as a single 160 KB row.
- Only the first 25 rejected entries get a
BAD_INPUTrow. Beyond that they are dropped
quietly, so check your list if the count looks short.
- Private and age-restricted videos are reported as a failure to open rather than as a distinct
reason of their own.
🧭 Which YouTube actor do you need?
| If you want | Use |
|---|---|
| Transcripts in bulk from links, channels or playlists | This one |
| The list of videos on a channel, or a keyword search | YouTube Scraper |
| Shorts from a channel or a keyword | YouTube Shorts Scraper |
| Only the new uploads from a watch list | YouTube Upload Monitor |
| The video, audio or subtitle file itself | YouTube Downloader Pro |
❓ Questions people ask
Do I need a YouTube API key? No. No key, no OAuth, no quota to manage.
What if the video has no captions? You get a NO_RESULTS row naming that video and the run moves on. That row is not charged.
Auto-generated or written captions, which do I get? The written one when the video has it, because it is punctuated. Tick Prefer auto-generated captions to flip that.
Can I get the timings? Yes, that is segments, and it is on by default. Turn it off when you want smaller rows and only the plain text.
Is scraping YouTube legal? This reads publicly available caption tracks. Transcripts are somebody's work, so what you may do with them afterwards is a copyright question rather than a scraping one. Apify's write-up on the legality of web scraping is a reasonable starting point, and we are not lawyers.
Can I run it on a schedule? Yes. Point it at a channel with a small maxVideosPerSource and each run picks up what is new at the top.
🆘 If something breaks
Open the Issues tab on the actor page. Include the links you used and the run id. The diagnostic rows in your dataset usually name the reason already.