site logo

Marico's space

如何用 Python 批量获取 YouTube 字幕(无需 API key,适用于云服务器)

编程技术 2026-10-29 14:48:52 4

最近折腾 YouTube 字幕批量抓取,踩了几个很典型的坑:本地能跑,部署到云服务器就被风控;官方接口只能下载自己频道的字幕;爬完整个频道,还得单独处理没有字幕的视频。这篇把问题拆开说清楚,并给出一套能在云服务器上稳定运行的 Python 方案。

利益相关说明:本文使用的 Apify Actor 是我做的。它是按字幕条数计费的付费工具,下面也会明确说明哪些场景不适合用它。

如果你试过从代码里获取 YouTube 字幕,大概率遇到过下面几堵墙:

  1. 官方 YouTube Data API(开发者接口)解决不了这个问题。它的 captions.download 端点只允许下载自己拥有的视频字幕。换成别人的视频,直接返回 403。
  2. youtube-transcript-api 在笔记本上正常,扔到服务器上就挂了。YouTube 会重点检查云服务器 IP(互联网协议地址)段的请求,阿里云、腾讯云、Hetzner 和大多数 VPS(虚拟专用服务器)都可能触发机器人验证。于是本地跑得好好的代码,一到生产环境就开始抛 RequestBlocked 或 IpBlocked。这个库的 Issue(问题反馈)页面里,已经有开发者专门讨论过这种情况。
  3. YouTube 没有“下载整个频道字幕”的现成按钮。你得先拿到频道下的视频列表,再逐个抓字幕,还得处理那些没有字幕的视频。

下面这套方案可以从 Python 里绕开这三个问题,核心调用只需要二十行左右。

简单版:一次 HTTP(超文本传输协议)调用

YouTube Transcript Scraper 运行在 Apify 的基础设施上。它会先尝试数据中心代理,如果 YouTube 返回机器人验证,再自动切换到住宅 IP(网络服务商用户的公网地址)重试,因此不需要自己维护代理池。

你可以传入视频、播放列表或频道 URL,最终每个视频对应返回一行 JSON(JavaScript 对象表示法)数据。

需要一个免费的 Apify 账号,以及账号对应的 API(应用程序接口)令牌。获取路径是:Console → Settings → Integrations。拿到令牌后直接运行:

import requests TOKEN = "YOUR_APIFY_TOKEN"
url = (f"https://api.apify.com/v2/acts/groupoject~youtube-transcript-scraper" f"/run-sync-get-dataset-items?token={TOKEN}") rows = requests.post(url, json={ "urls": ["https://www.youtube.com/watch?v=iG9CE55wbtY"], "outputFormats": ["text"],
}, timeout=320).json() for row in rows: print(row["title"], "-", row["wordCount"], "words") print(row["text"][:300])

run-sync-get-dataset-items 会启动任务、等待执行完成,并在一次响应中返回所有数据行。Apify 对同步调用的限制大约是 5 分钟,所以它更适合抓取少量视频。如果要处理整个频道,使用下面的客户端库。

批量模式:整个频道或播放列表

先安装 apify-client:

pip install apify-client

然后运行:

from pathlib import Path
from apify_client import ApifyClient client = ApifyClient("YOUR_APIFY_TOKEN") run = client.actor("groupoject/youtube-transcript-scraper").call(run_input={ "urls": [ "@lexfridman", # a channel handle "https://www.youtube.com/playlist?list=PLxxxxxxxxxxxx", # a playlist ], "maxVideosPerSource": 50, # newest 50 per channel / playlist "channelContentTypes": ["videos"], # or add "shorts", "streams" "languages": ["en"], # preference order, e.g. ["es", "en"] "outputFormats": ["text", "segments", "srt"],
}) out = Path("transcripts")
out.mkdir(exist_ok=True) for item in client.dataset(run["defaultDatasetId"]).iterate_items(): (out / f"{item['videoId']}.txt").write_text(item["text"], encoding="utf-8") (out / f"{item['videoId']}.srt").write_text(item["srt"], encoding="utf-8") print(item["videoId"], item["languageCode"], "auto" if item["isAutoGenerated"] else "manual", item["title"])

有几个输入参数需要特别留意。下面这些就是 Actor 输入结构中的真实字段名:

  • urls 接受 watch?v= 链接、youtu.be 链接、Shorts(短视频)、/live/ 直播地址、11 位纯视频 ID、播放列表 URL、频道 URL 和 @handles,可以混合放在同一个列表里。
  • maxVideosPerSource 用于限制每个播放列表、每种频道内容类型最多抓取多少个视频,按最新优先,最多支持 5,000 个。单视频 URL 不受这个参数影响。
  • captionType 可选 any、manual 或 auto。默认的 any 会优先使用创作者上传的字幕,找不到时再回退到自动生成字幕。
  • fallbackToAnyLanguage 默认为 true。如果 languages 中指定的语言都不存在,就返回原始语言的字幕。改成 false 后,这类视频会被直接跳过。
  • includeVideoMetadata 默认为 true。开启后,每行数据还会包含标题、频道、发布日期、播放量、点赞数、时长、简介和标签等视频元数据。

哪些视频没有字幕?

没有字幕的视频不会写入数据集,也不会产生费用。这些视频会集中记录在本次任务的键值存储中:

store = client.key_value_store(run["defaultKeyValueStoreId"])
missing = (store.get_record("NO_TRANSCRIPT") or {}).get("value") or []
for m in missing: print(m["videoId"], m["reason"], m.get("availableLanguages"))

reason 可能是 NO_CAPTIONS、LANGUAGE_NOT_AVAILABLE 或 EMPTY_CAPTIONS。私有、已删除或受地区限制的视频则会进入 ERRORS 记录。

把字幕送进 LLM(大型语言模型)或 RAG(检索增强生成)流水线

如果只是做摘要,直接使用 text 字段就够了。如果需要带引用的内容检索,就用 segments。每个分段都包含以秒为单位的 start、duration 和 text,因此可以按时间切片,同时保留一个跳回视频具体位置的深度链接。

def chunks(item, seconds=90): buf, t0 = [], None for seg in item["transcript"]: t0 = seg["start"] if t0 is None else t0 buf.append(seg["text"]) if seg["start"] - t0 >= seconds: yield {"text": " ".join(buf), "source": f"{item['url']}&t={int(t0)}s", "title": item["title"]} buf, t0 = [], None if buf: yield {"text": " ".join(buf), "source": f"{item['url']}&t={int(t0)}s", "title": item["title"]}

每个分块都可以直接写入向量数据库,并把 source 作为元数据。这样,模型生成答案时就能链接到视频中的准确时间点。

不写代码:n8n、Make 和 Zapier

如果不想自己维护 Python 环境,这个 Actor 也支持 Apify 的集成服务。

在 n8n 中添加 Apify 节点,选择 Run an Actor and Get Dataset,把 Actor 设置为 groupoject~youtube-transcript-scraper,再粘贴上面的 JSON 输入即可。后面可以接 Google Sheets 或 Notion 节点。

在 Make 和 Zapier 中,Apify 应用也提供相同的“运行 Actor”和“获取数据集条目”步骤。你还可以通过 MCP(模型上下文协议)客户端调用它,包括 Claude、Cursor 等工具,地址为 https://mcp.apify.com?tools=groupoject/youtube-transcript-scraper。

监控频道的新字幕

开启 onlyNewVideos,再给监控任务设置一个 stateKey,名称可以随意,例如 ai-podcasts。

之后每次运行只会保存该 key 下此前没有处理过的视频,同一个视频不会被重复计费。在 Apify 中进入 Schedules → Create 设置每日定时任务,再接入 Slack、邮件或 Google Sheets,就能在新字幕生成后收到通知。

run = client.actor("groupoject/youtube-transcript-scraper").call(run_input={ "urls": ["https://www.youtube.com/@lexfridman"], "onlyNewVideos": True, "stateKey": "lex-podcast", "publishedAfter": "7 days", # optional: ignore older uploads on the first run
})

刚发布的视频有时要过几个小时才会生成自动字幕。这个 Actor 不会立刻放弃,而是会在 24 小时内持续复查这类视频。

费用是多少

免费套餐的价格是每 1,000 条字幕 2.50 美元,也就是每个视频 0.0025 美元,代理流量已经包含在内。Apify 付费套餐会自动获得更低的结果单价。

没有字幕的视频,以及执行失败、私有或已删除的视频,都不收费。一个包含 100 个视频、且每个视频都有字幕的播放列表,总费用为 0.25 美元。

你还可以设置单次任务的最大消费额度,一旦接近上限,Actor 会在超出之前停止运行。

先说清楚限制

  • 它只返回 YouTube 自己的字幕,不负责把音频转成文字。如果视频完全没有字幕,比如纯音乐片段或一部分 Shorts,就拿不到任何结果。这种情况需要改用 Whisper 或其他语音转文字 API。
  • 自动生成字幕的质量取决于 YouTube 的语音识别。人名和专业术语经常识别错误,可以通过 isAutoGenerated 判断拿到的是哪一种字幕。
  • 翻译功能 translateTo 属于尽力而为。YouTube 会严格限制自动翻译接口的调用频率。翻译失败时,仍然会返回原始语言字幕,并通过 translationError 说明失败原因。
  • 整个方案刻意不支持登录。有年龄限制、会员专享和私有视频只会记录在 ERRORS 中,不会被强行抓取。
  • 如果每天只需要自己笔记本上的几条视频,没必要用这个工具。youtube-transcript-api 或 yt-dlp --write-auto-subs 都是免费的,在家庭网络环境下也足够稳定。这个方案更适合批量任务、频道级采集或者服务器部署。
  • 尊重版权。字幕属于创作者作品。为研究目的做摘要是一回事,原样转载则是另一回事。

相关链接

  • YouTube Transcript Scraper Actor 页面

如果某个本应可处理的视频抓取失败,可以在 Actor 的 Issues 标签页提交问题,并附上视频 ID。我每天都会看。