Answers
How do I download transcripts for hundreds of YouTube videos at once?
Short answer
Put the video IDs in a list and call the YouTube transcript endpoint for each one with a few workers in parallel; okdata absorbs the YouTube side, so there is no IP to get banned. Each transcript costs $0.004, 1,000 videos come to $4.00 at most, and videos without captions are free.
A media researcher has a spreadsheet of 800 lecture videos and wants their transcripts in one file by Friday. With youtube-transcript-api, the run dies after a few dozen videos with 429 Too Many Requests, and the retry logic people add tends to hammer the same IP again. A batch this size needs three things: a clean list of IDs, a client that backs off politely, and a way to resume.
Step 1: the list of IDs
- You already have links. Take the
v=value, or the part afteryoutu.be/or/shorts/. The transcript endpoint wants the 11-character ID. - You have a topic. YouTube video search returns about 20 videos a page with views and channel, so 100 candidates cost $0.02.
- You have a channel. The official Data API's
playlistItems.liston the channel's uploads playlist costs 1 quota unit per page of up to 50 videos, which is the cheapest way to list a whole channel.
Step 2: a resumable runner
import json, os, time, requests
from concurrent.futures import ThreadPoolExecutor
URL = "https://api.okdata.online/v1/youtube/videos/transcript/get"
KEY = os.environ["OKDATA_API_KEY"]
OUT = "transcripts.jsonl"
def fetch(vid):
while True:
r = requests.get(URL, params={"id": vid}, timeout=90, headers={
"Authorization": f"Bearer {KEY}", "Idempotency-Key": f"transcript-{vid}"})
body = r.json()
if body["code"] == "rate_limited":
time.sleep(body.get("retry_after_seconds") or 2)
continue
return vid, body
ids = [line.strip() for line in open("video_ids.txt") if line.strip()]
done = {json.loads(l)["video_id"] for l in open(OUT)} if os.path.exists(OUT) else set()
with ThreadPoolExecutor(max_workers=4) as pool, open(OUT, "a", encoding="utf-8") as out:
for vid, body in pool.map(fetch, [i for i in ids if i not in done]):
if body["code"] == "ok":
out.write(json.dumps(body["data"]["item"], ensure_ascii=False) + "\n")
else:
print(vid, body["code"]) # not_found = no captions, not charged
Run it again after any crash; finished videos are skipped. The Idempotency-Key means a retried call that already succeeded within 24 hours is replayed, not billed twice.
What the run costs
| Batch | Cost at most |
|---|---|
| 100 videos | $0.40 |
| 800 videos | $3.20 |
| 1,000 videos | $4.00 |
Every captionless video and every error lowers the bill, since neither is charged. New keys start with 0.10 of credit, enough to test the script on a slice first.
Limits
- Four workers is a sensible start. Your key's burst size is in the
X-RateLimit-Limitheader;rate_limitedanswers are free. - No speech-to-text: videos without captions need Whisper on audio you may use.
- One language per call. See
langin the response.
When to do it differently
If the batch is small and occasional, youtube-transcript-api from a home connection with a pause between calls usually gets through. For your own channel, the official API downloads your caption files. For the whole workflow, from search to summary, see the video transcript solution page.
Updated