如何處理伺服器擷取 YouTube 字幕時的「登入以確認你不是機械人」錯誤
Fixing "Sign in to confirm you're not a bot" for YouTube transcripts on a server
文章指出,伺服器端擷取 YouTube 字幕時,雲端及資料中心 IP 可能觸發「登入以確認你不是機械人」檢查,令請求遭拒。建議先更新 yt-dlp 並安裝 JavaScript 執行環境,再改用其他 YouTube 播放器用戶端;其他選項包括登入帳戶的 cookies、PO tokens、住宅代理及託管字幕 API。
Your transcript code works on your laptop. You deploy it to AWS, Vercel, Render, Railway or a VPS, and every video fails with one of these:
ERROR: [youtube] dQw4w9WgXcQ: Sign in to confirm you're not a bot. Use --cookies-from-browser or --cookies for the authentication.
youtube_transcript_api._errors.RequestBlocked / IpBlocked
playabilityStatus: {"status": "LOGIN_REQUIRED", "reason": "Sign in to confirm you're not a bot"}
Nothing is wrong with your code. YouTube decided the request came from a bot because of where it came from.
Why it only happens on servers
YouTube scores each request by its IP address. Home and mobile connections look like people. Cloud and datacenter ranges (AWS, Google Cloud, Azure, Hetzner, DigitalOcean, the functions behind Vercel) look like scrapers, so YouTube asks them to sign in before it serves the player data that captions come from. A few requests a day may get through. Past a few hundred, most won't. That's why it looks random at first and then fails on every video.
Is it the bot check, or is the video really private?
Both come back as LOGIN_REQUIRED, which is what makes this confusing. Check the reason text:
| Reason text | What it means | Retry? |
|---|---|---|
| Sign in to confirm you're not a bot | Your IP is flagged, the video is fine | Yes, from another IP or client |
| This video is private / Sign in to confirm your age / members-only | The video really needs an account | No |
| Video unavailable | Deleted or blocked in your region | No |
Checking that string before you retry saves you from burning proxy bandwidth on videos that will never work.
Fixes, from free to paid
1. Update yt-dlp and give it a JavaScript runtime
Recent yt-dlp versions need an external JavaScript runtime to talk to YouTube. Without one, extraction fails in ways that can look like the bot check. Install Node.js or Deno, then:
python -m pip install -U "yt-dlp[default]"
echo '--js-runtimes node' >> ~/.config/yt-dlp/config
yt-dlp --write-subs --write-auto-subs --sub-langs "en.*" --skip-download "https://youtu.be/dQw4w9WgXcQ"
If it was the missing runtime, this fixes it. If it was the IP, it won't.
2. Ask for the player data as a different client
YouTube serves several apps (web, mobile, TV) and doesn't apply the bot check the same way to all of them, so a video blocked for one client often loads for another. In yt-dlp that's --extractor-args "youtube:player_client=...". The yt-dlp wiki lists which clients currently work, and that list changes. For captions, the MIT-licensed yt-transcript-cli does this automatically (Android, then iOS, then web):
npx yt-transcript-cli "https://youtu.be/dQw4w9WgXcQ" --json
It exits with code 2 and BLOCKED when every client is refused, and code 3 when the video simply has no captions, so a script can tell the two apart. On a heavily flagged IP every client can be refused, and then you need a different IP.
3. Cookies from a logged-in account
yt-dlp --cookies cookies.txt --write-auto-subs --skip-download "URL"
This is what the error message suggests, and it works for a while. But cookies expire, every request is tied to a real Google account, and that account can get restricted for automated use. Use a throwaway account, never your main one.
4. Proof-of-origin tokens
yt-dlp supports PO tokens through provider plugins. They can clear the check without cookies, but it's one more moving part that YouTube changes regularly. The yt-dlp wiki's PO Token Guide covers the setup.
5. Residential proxies
Residential proxies make your requests look like home connections. youtube-transcript-api supports them directly:
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import GenericProxyConfig
ytt = YouTubeTranscriptApi(proxy_config=GenericProxyConfig(
https_url="http://USER:PASS@your-residential-proxy:PORT"))
transcript = ytt.fetch("dQw4w9WgXcQ", languages=["en"])
There's also a WebshareProxyConfig helper. This is the reliable fix and also the expensive one. One developer who hit the block at a few hundred requests a day reported going through about $200 of residential proxy in a month, and still had to babysit the retry logic.
6. A hosted transcript API
If transcripts are one step in your product rather than the product, it's usually cheaper to call a service that already handles client rotation, residential retries and the reason-string checks. Disclosure: I run two versions of one.
- YouTube Transcript Scraper on Apify: $2 per 1,000 transcripts, videos without captions aren't charged, and Apify's free monthly credit covers about 2,500.
- YouTube Transcript API on RapidAPI: one GET request returning JSON, plain text or SRT. 100 free requests a month.
curl "https://youtube-transcript-api23.p.rapidapi.com/transcript?url=https://youtu.be/dQw4w9WgXcQ&format=text" \
-H "x-rapidapi-key: YOUR_RAPIDAPI_KEY" -H "x-rapidapi-host: youtube-transcript-api23.p.rapidapi.com"
Other hosted options exist too. Whichever you pick, check that failed or caption-less videos aren't billed.
Which one to use
- Side project, under ~100 videos a day: fixes 1 and 2. Often enough on their own.
- Steady volume and you don't mind maintaining it: 2 plus residential proxies.
- Transcripts feed an app or an AI agent and you'd rather not maintain scraping: a hosted API.
None of these is permanent. YouTube changes its checks every few weeks, so whatever you pick, make your code tell "blocked, retry later" apart from "this video has no transcript". Then it fails loudly instead of quietly returning nothing.
來源:DEV Community · dev.to