Getting one YouTube transcript is easy. Getting 500 of them for a RAG pipeline, a research project or a weekly channel digest is where things get annoying.
In this post we show a simple way to pull transcripts in bulk with Python, get clean JSON back, and split the text into chunks ready for embeddings.
The usual options
Copy from the YouTube UI. Fine for one video. Not an option for a channel.
The official YouTube Data API. It can list captions, but downloading caption tracks only works for videos you own. For public videos from other channels it does not help.
Open source libraries like youtube-transcript-api. Great for local scripts. The catch: YouTube often blocks requests from cloud and datacenter IPs, so the same code that works on your laptop starts failing on a server or in a scheduled job. The library docs themselves describe this and suggest using proxies.
A hosted scraper. You send a list of videos or a channel URL and get structured data back. Someone else deals with blocks, retries and format changes. This is what we use below.
Bulk download with Python
We will use our YouTube Transcript Scraper on Apify. You need a free Apify account and an API token (Console > Settings > API & Integrations).
pip install apify-client from apify_client import ApifyClient client = ApifyClient("YOUR_APIFY_TOKEN") run_input = { "videos": [ "https://www.youtube.com/watch?v=Pba4OZjn3cY", "https://www.youtube.com/watch?v=46v8M4RsCzg", "https://www.youtube.com/watch?v=tu6tF-lVDJI", ], "language": "en", } run = client.actor("wiseus-labs/youtube-transcript-scraper").call(run_input=run_input) items = client.dataset(run["defaultDatasetId"]).list_items().items for item in items: print(item["status"], item["title"], len(item.get("text") or "")) Want a whole channel instead of a list? Swap the input:
run_input = { "channelOrPlaylistUrls": ["https://www.youtube.com/@TED"], "maxVideosPerSource": 50, "language": "en", } Playlists work the same way.
What you get back
One row per video:
{ "videoId": "Pba4OZjn3cY", "title": "Why It's So Hard to Listen When You Disagree | Emily Kasriel | TED", "language": "en", "captionType": "manual", "status": "ok", "segmentCount": 194, "text": "You know, there's something quite strange going on...", "url": "https://www.youtube.com/watch?v=Pba4OZjn3cY" } captionType tells you whether the track was written by a human (manual) or generated by YouTube (auto). Timestamped segments come in the segments field if you need to link answers back to a moment in the video.
Videos without subtitles do not crash the run. They come back with status: "no_captions", so you can filter them out.
Chunk it for RAG
Most embedding models work better with chunks of a few hundred words than with a 10,000 word transcript. A minimal splitter:
def chunk_text(text, size=300, overlap=50): words = text.split() step = size - overlap return [" ".join(words[i:i + size]) for i in range(0, len(words), step)] docs = [] for item in items: if item["status"] != "ok": continue for n, chunk in enumerate(chunk_text(item["text"])): docs.append({ "id": f'{item["videoId"]}-{n}', "text": chunk, "source": item["url"], "title": item["title"], }) print(len(docs), "chunks ready for embeddings") From here, send docs to your vector store of choice. Keeping source and title on every chunk means your chatbot can cite the exact video it answered from.
What it costs
$1.50 per 1,000 transcripts on the Apify Free plan, less on paid plans. Videos without subtitles and failed videos are not charged. The free plan includes $5 of monthly credit, which covers a few thousand transcripts.
No code option
If you do not want to write any code, open the ready-made task, paste your video links and press Start. Results export to CSV, JSON or Excel:
- Download YouTube transcripts in bulk from a list of videos
- Download all transcripts from a YouTube channel
The Actor page with all options: YouTube Transcript Scraper.
If something breaks or you need a field we do not return yet, open an issue on the Actor page. We read all of them.
