如何無需 YouTube Data API 金鑰取得整個 YouTube 頻道或播放清單的字幕
How to Get Transcripts for a Whole YouTube Channel or Playlist (No API Key)
這份教學提供三種無需 YouTube Data API 金鑰的方法,可批量取得 YouTube 頻道或播放清單內影片的字幕,並輸出為帶時間戳記的資料。方案包括透過 Apify actor 免寫程式操作、用 Python 呼叫 Apify,以及自行使用開源庫 youtube-transcript-api。
Getting the transcript of one YouTube video is easy: open the video, click Show transcript, copy. Getting transcripts for every video on a channel, or a 200-video playlist, as clean timed data you can feed into a script or an LLM, is where it gets annoying.
The usual routes need a YouTube Data API key and some glue code, or they work on your laptop and then fail as soon as you run them on a server. This tutorial covers three ways to do it: no code, a few lines of Python, and a do-it-yourself route with the open-source youtube-transcript-api library. For the DIY route I'll show what happened when I ran it from a cloud server.
Disclosure: I built the Apify actor used in options 1 and 2 (YouTube Transcript & Subtitles Scraper). Option 3 doesn't use it.
What you get per video
One row per video. These are real field names and values from a run on 26 Oct 2026 (Steve Jobs' Stanford commencement speech):
| Field | Example |
|---|---|
videoId, url
|
UF8uR6Z6KLc |
title |
Steve Jobs' 2005 Stanford Commencement Address |
channelName, channelId
|
Stanford, UC-EnprmCZ3OXyAoG7vjVNCA
|
durationSeconds, viewCount
|
904, 49320612
|
language, languageName
|
en, English - English
|
isAutoGenerated |
false (a human-made caption track) |
segmentCount, wordCount
|
244, 2298
|
transcript |
[{"start": 22.492, "duration": 7.527, "text": "Thank You. I am honored to be with you today at your commencement"}, …] |
availableLanguages |
every caption track on the video: Arabic, English (manual and auto-generated), Italian, … |
error, errorReason
|
null when it worked (see the FAQ for the codes) |
When a video has both a human-made and an auto-generated track in your language, the human-made one wins.
Option 1: No code (Apify Console)
- Open the actor page and click Try for free. Apify's free plan includes monthly platform credit; the actor itself is pay-per-event (see the Store page for the current price, and videos without captions aren't charged).
- Paste into Videos, playlists or channels any mix of:
- video URLs (
watch?v=,youtu.be/,/shorts/,/live/) or bare 11-character IDs, - playlist URLs or IDs (
PL…), - channel URLs (
/@TED,/channel/UC…). Playlists and channels are expanded to their videos (channels newest first) up to Max videos.
- video URLs (
- Set Preferred languages (e.g.
en,es,pt-BR) and the Transcript format: timed segments, plain text, or both. - Click Start and export the results as CSV, JSON or Excel, or send them to Google Sheets.
For a sense of speed: the 30 newest videos of @TED took 16 seconds and returned 29 transcripts. The 30th came back with error: bot_detected (YouTube refused that one request) and wasn't charged.
Option 2: Python
pip install apify-client, then take your API token from Apify Console → Settings → Integrations.
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("axlymxp/youtube-transcript-scraper").call(run_input={
"videoUrls": [
"https://www.youtube.com/@TED", # a whole channel
"https://www.youtube.com/watch?v=UF8uR6Z6KLc", # a single video
],
"languages": ["en"],
"maxVideos": 50,
"outputFormat": "both", # segments in `transcript`, joined text in `transcriptText`
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["error"]:
print("skip", item["videoId"], item["error"])
continue
print(item["title"], "|", item["wordCount"], "words |", "auto" if item["isAutoGenerated"] else "manual")
Save one text file per video
Handy for search indexes, RAG pipelines or just reading:
import pathlib, re
out = pathlib.Path("transcripts")
out.mkdir(exist_ok=True)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["error"]:
continue
name = re.sub(r"[^\w\- ]", "", item["title"])[:80].strip() or item["videoId"]
(out / f"{name} [{item['videoId']}].txt").write_text(item["transcriptText"], encoding="utf-8")
If you only need text, set "outputFormat": "text": transcript then holds one string instead of segments, which is the most compact input for an LLM.
Example output
One real row (trimmed to three segments; transcriptText, description, keywords and thumbnailUrl removed):
{
"videoId": "UF8uR6Z6KLc",
"url": "https://www.youtube.com/watch?v=UF8uR6Z6KLc",
"title": "Steve Jobs' 2005 Stanford Commencement Address",
"channelName": "Stanford",
"durationSeconds": 904,
"viewCount": 49320612,
"language": "en",
"languageName": "English - English",
"isAutoGenerated": false,
"isTranslated": false,
"segmentCount": 244,
"wordCount": 2298,
"transcript": [
{"start": 7.47, "duration": 2.899, "text": "This program is brought to you by Stanford University."},
{"start": 10.47, "duration": 3.973, "text": "Please visit us at stanford.edu"},
{"start": 22.492, "duration": 7.527, "text": "Thank You. I am honored to be with you today at your commencement"}
],
"error": null
}
Option 3: DIY with youtube-transcript-api
If you'd rather run everything yourself, youtube-transcript-api is the standard open-source library. With version 1.2.4:
from youtube_transcript_api import YouTubeTranscriptApi
api = YouTubeTranscriptApi()
transcript = api.fetch("UF8uR6Z6KLc", languages=["en"])
for s in transcript.snippets[:3]:
print(s.start, s.text)
That works on most home connections. Cloud servers are a different story. I ran exactly this on an Apify cloud server on 26 Oct 2026:
-
5 of 5 videos failed with
RequestBlocked("YouTube is blocking requests from your IP") when sent directly from the server. - 5 of 5 worked once the same requests went through a residential proxy, which the library supports out of the box:
from youtube_transcript_api.proxies import GenericProxyConfig
api = YouTubeTranscriptApi(proxy_config=GenericProxyConfig(
http_url="http://user:pass@your-residential-proxy:8000",
https_url="http://user:pass@your-residential-proxy:8000",
))
For whole channels you also need the list of video IDs. scrapetube does that without an API key, but watch out: on that same cloud server, scrapetube.get_channel(channel_url="https://www.youtube.com/@TED") returned an empty list with no error. If your channel job reports zero videos, check where it runs before you debug your code.
So the DIY route is free in software but you'll manage proxies, retries, language selection and resuming yourself. That plumbing is what the actor handles: residential proxy by default, manual-over-auto track selection, free rows for videos without captions, and resuming an interrupted run.
Scheduling and integrations
To keep a channel's transcripts up to date, save your input as an Apify task with the channel URL and a modest Max videos (say 20), and schedule it daily or weekly. Send the dataset to Google Sheets, Slack, Make, Zapier or a webhook after each run. An interrupted run resumes where it stopped when you start it again with the same input.
FAQ
Do I need a YouTube API key? No. Neither the actor nor the DIY route uses the YouTube Data API.
What if a video has no captions? It comes back with error: no_captions and isn't charged. Transcripts come from YouTube's own caption tracks (human-made or auto-generated); the actor doesn't do speech-to-text.
Can I get a specific language or a translation? Set Preferred languages in order of preference; en also matches en-US and en-GB. If none exists, Fall back to any available language returns the video's default track (or error: language_not_available if you turn it off). Translate to uses YouTube's own caption translation; YouTube throttles that per IP, so when it refuses, the row keeps the original language and says translationError: rate_limited.
Shorts and live streams? Shorts work like normal videos. On clips without speech, the auto-generated track may hold only tags like [Music] or [Laughter].
Is it legal? These are publicly available captions, fetched without logging in. How you use them is on you: respect YouTube's terms and copyright, especially if you republish transcripts.
Questions or a video that won't work? Open an issue on the actor page and I'll take a look.
來源:DEV Community · dev.to