작업장 / 노하우
유튜브 자막 요청이 429로 막힐 때 — 막힌 것은 우리 집이 아니라 「번역해서 주세요」 줄이었다
yt-dlp 로 유튜브 자동 자막을 받다가 HTTP 429 로 막혔다. 주소 차단이 아니었다. 자막 트랙이 여러 개인 영상에서 en 코드가 「다른 언어 자막을 영어로 번역」하는 주소로 풀려 번역 요청이 나갔고, 유튜브는 그 번역 요청만 막고 있었다. 급한 날은 브라우저의 「스크립트 표시」 패널을 읽어 넘겼고, 그 뒤 사람 자막과 원래 언어 자동 자막(-orig)을 두 번에 나눠 요청하게 고쳤다.
유튜브 자막 요청이 429로 막힐 때 — 막힌 것은 우리 집이 아니라 「번역해서 주세요」 줄이었다
2026-11-11 · 자동화
유튜브 영상을 요약해 기록하는 자동화가 있다. yt-dlp로 자막을 받고, AI(Claude)가 그 자막을 읽어 정리한다. 어느 날 쇼츠 한 편에서 자막이 받아지지 않았다. 오류는 HTTP Error 429: Too Many Requests였다. 그날 요청한 영상은 그 한 편뿐이었다.
결론부터 적는다. 유튜브가 우리 인터넷 주소를 막은 것이 아니었다. 우리 요청이 모르는 사이에 「번역해서 주세요」 줄에 서 있었고, 유튜브는 그 줄만 막고 있었다. 「원래 말 그대로 주세요」 줄은 열려 있었다.
1. 증상 — 목록은 주는데 본문은 안 준다
쓰던 명령은 이랬다.
yt-dlp --skip-download --write-auto-sub --write-sub --sub-lang "en,ko" \
--sub-format "vtt/srt/best" --write-info-json -o video "$URL"
돌아온 답은 이것이다.
WARNING: Unable to download video subtitles for 'en': HTTP Error 429: Too Many Requests
이상한 점이 하나 있었다. yt-dlp --list-subs는 자동 자막 목록을 정상으로 보여 줬다. 막힌 것은 자막 본문을 내려받는 단계뿐이었다.
게다가 스크립트는 자막 파일이 없으면 「자막 없음」이라고 보고하게 되어 있었다. 자막은 있었다. 받지 못했을 뿐이다. 도구가 「없음」과 「막힘」을 같은 말로 보고한 탓에 원인을 찾는 데 시간이 더 걸렸다.
2. 헛다리 — 「주소가 막혔다」고 믿고 한 일
429를 보면 먼저 떠오르는 생각은 「너무 많이 요청해서 주소가 막혔다」이다. 그 믿음으로 두 가지를 해 봤다.
재생 클라이언트 바꾸기. --extractor-args "youtube:player_client=…" 값을 8가지로 바꿔 다시 요청했다.
| 클라이언트 | 결과 |
|---|---|
| tv, ios, android, web_embedded, tv_simply | HTTP Error 429 |
| web, web_safari, mweb | There are no subtitles for the requested languages |
브라우저 안에서 자막 주소 직접 부르기. 영상 페이지가 가진 자막 주소 뒤에 &fmt=json3을 붙여 불렀다. HTTP 200이 왔다. 그런데 본문이 0바이트였다. 성공처럼 보이는 빈 종이였다.
나중에 보니 8번의 시도는 전부 같은 요청이었다. 옷(클라이언트)만 갈아입혔고, 자막 언어 코드는 en 그대로였다. 같은 줄에 8번 다시 선 셈이다. 실패가 쌓일수록 「막혔다」는 믿음만 단단해졌다.
3. 급한 불 — 사람이 보는 화면을 읽었다
요약 글은 그날 올려야 했다. 그래서 자막 창구를 포기하고, 평소 쓰는 브라우저를 자동 조작해 사람이 하는 순서를 그대로 밟았다.
- 영상 페이지(
/watch?v=…)를 연다. - 설명란을 펼친다.
- 「스크립트 표시」 버튼을 누른다.
- 옆에 뜬 스크립트 패널의 글자를 읽는다.
브라우저 자동화 도구의 페이지 안 실행으로 옮기면 이 정도다.
// 설명란 펼치기
document.querySelector('ytd-watch-metadata #expand')?.click();
// 「스크립트 표시」 누르기
document.querySelector(
'ytd-video-description-transcript-section-renderer button'
)?.click();
// 패널이 뜬 뒤 — 화면 구조에 따라 둘 중 하나에 줄이 들어 있다
const rows = [
...document.querySelectorAll('transcript-segment-view-model'),
...document.querySelectorAll('ytd-transcript-segment-renderer'),
].map((el) => el.textContent.replace(/\s+/g, ' ').trim());
자막 29줄이 나왔고, 그것으로 요약 글을 발행했다.
이 길은 우회로다. 화면 구조가 바뀌면 깨진다. 그래도 계정 쿠키를 내려받기 도구에 넘기는 방법보다 낫다. 쿠키를 넘기면 계정이 정지될 위험이 있다고 yt-dlp 문서가 적고 있다.
4. 진짜 원인 — 줄을 잘못 서 있었다
급한 일을 끝낸 뒤, 원인 조사를 다른 AI에게 맡겼다. 조건은 「공개 자료만 읽고, 유튜브에 실제 요청은 보내지 말 것」이었다. 막힌 상태에서 계속 두드리면 막힘이 길어질 수 있어서다.
조사 보고가 가리킨 곳은 yt-dlp 저장소의 이슈 #13831이었다. 요지는 이렇다.
- 자동 자막 주소에 「이 언어로 번역해서 달라」는 표시(
tlang)가 붙으면 번역 요청이 된다. - 유튜브는 이 번역 요청에 429를 돌려준다. 원래 언어 그대로인 자막은 받아진다.
yt-dlp는 원래 언어 자동 자막에en-orig처럼-orig가 붙은 코드를 따로 준다.
보고는 이 결론에 「추정」이라고 표시해 두었다. 그래서 고치기 전에 같은 영상으로 한 번만 확인했다.
yt-dlp --skip-download --write-auto-sub --sub-langs ".*-orig" \
--sub-format "vtt/srt/best" -o video "$URL"
429 없이 자막이 받아졌다. 같은 컴퓨터, 같은 인터넷, 같은 yt-dlp 버전(2026.03.17)이었다. 막힌 것은 주소가 아니라 요청의 종류였다.
의문이 하나 남았다. 이 영상은 원래 영어다. 영어(en)를 요청했는데 왜 번역 요청이 됐나. 자막 본문은 받지 않고 주소 목록만 뽑아 봤다.
yt-dlp -J --skip-download "$URL" # automatic_captions 안의 주소를 본다
이 영상에는 자동 생성 자막 트랙이 8개 있었다. 영어 하나와 프랑스어·독일어·힌디어·인도네시아어·이탈리아어·포르투갈어·스페인어다. 그래서 en이라는 코드 하나 아래에 주소가 8개 들어 있었다.
en 아래의 주소 | 개수 | 뜻 |
|---|---|---|
영어 트랙, tlang 없음 | 1 | 원래 자막 |
다른 언어 트랙 + tlang=en | 7 | 그 언어 자막을 영어로 번역 |
yt-dlp는 같은 형식의 주소가 여러 개면 맨 뒤의 것을 고른다. 맨 뒤는 「스페인어 트랙을 영어로 번역」이었다. 영어 영상의 영어 자막을 요청했는데, 실제로 나간 것은 번역 요청이었다. en-orig 아래에는 원래 자막 주소 하나뿐이라 이런 일이 없다.
자막 트랙이 하나뿐인 보통 영어 영상에서는 en이 원래 자막을 가리킨다. 같은 명령이 그동안 잘 돌았던 이유다. 한편 ko처럼 영상 원어가 아닌 코드는 언제나 번역 요청이다.
2절의 수수께끼도 같이 풀렸다. 본문 0바이트 응답은 429와 다른 실패다. 그 주소는 재생기가 붙이는 토큰(PO Token)이 있어야 본문을 준다는 보고가 같은 저장소 이슈 #13075에 있다.
5. 고친 방법 — 두 번에 나눠 요청한다
# 1차: 사람이 올린 자막만 (자동 자막 옵션 없음)
yt-dlp --skip-download --write-sub --sub-langs "en,ko" \
--sub-format "vtt/srt/best" --write-info-json -o video "$URL"
# 2차: 자막 파일이 하나도 없을 때만, 원래 언어 자동 자막
yt-dlp --skip-download --write-auto-sub --sub-langs "en-orig,ko-orig" \
--sub-format "vtt/srt/best" -o video "$URL"
- 왜 한 번에 안 하나.
--write-sub와--write-auto-sub를 같이 주고en을 요청하면, 사람이 올린 자막이 없는 영상에서en이 자동 자막 쪽으로 풀린다. 자막 트랙이 여러 개인 영상에서는 그것이 번역 요청이 된다. 다시 429다. 사람 자막과 자동 자막을 따로 부르고, 자동 자막은-orig코드로만 요청해야 번역 요청이 섞이지 않는다. - 언어 표기에 하이픈이 있을 때. 영상 언어가
zh-Hans나en-US처럼 적혀 있으면zh-Hans-orig와zh-orig를 둘 다 요청한다.yt-dlp는--sub-langs를 전체 일치로 고르기 때문에zh-orig만으로는zh-Hans-orig를 못 받는다. - 「없음」과 「막힘」을 나눈다. 자막 파일이 없는데 출력에
HTTP Error 429가 있으면 「자막 없음」이 아니라 「429로 막힘」이라고 보고하게 했다.
하이픈 문제는 내가 찾지 못했다. 고친 코드를 다른 AI에게 검수시켰더니 찾아냈다. 쓴 쪽과 검사하는 쪽을 나누는 이유가 여기 있다.
시험은 가짜 yt-dlp로 만들었다. 가짜는 「자동 자막을 -orig 없는 코드로 요청하면 429」를 돌려준다. 가장 나쁜 경우를 흉내 낸 것이다. 누군가 나중에 옛 방식으로 되돌리면 시험이 실패한다. 시험이 정말 잡는지는 코드를 일부러 네 가지로 망가뜨려 확인했다.
6. 배운 것
| 상황 | 할 일 |
|---|---|
| 429가 떴다 | 「주소가 막혔다」고 단정하기 전에 무엇을 요청했는지 본다. 목록은 되는데 본문만 안 되면 요청의 종류를 의심한다 |
| 여러 방법이 다 실패했다 | 그 방법들이 사실은 같은 요청을 반복하고 있지 않은지 본다 |
| 200인데 본문이 비었다 | 성공으로 세지 않는다. 길이를 확인한다 |
| 이름은 맞는데 결과가 이상하다 | 이름(en)이 아니라 실제로 나가는 주소를 뽑아 본다. 코드 하나가 주소 여러 개를 가리킬 수 있다 |
| 오늘 안에 결과가 필요하다 | 사람이 보는 화면을 읽는 우회로를 쓴다. 계정 쿠키는 넘기지 않는다 |
| 도구가 「없음」이라고 한다 | 「없음」과 「막힘」을 다른 이름으로 보고하게 고친다 |
| 조사 결과가 「추정」이다 | 실제 요청 한 번으로 확인한 뒤 고친다. 막힌 상태에서 여러 번 두드리지 않는다 |
확인하지 못한 것도 적어 둔다. 실제 유튜브로 확인한 영상은 영어 자동 자막 쇼츠 한 편이다. 그 영상에 자막 트랙이 왜 8개인지(자동 더빙 때문인지)는 확인하지 못했다. 429가 얼마 뒤에 풀리는지는 근거를 찾지 못했다. 유튜브와 yt-dlp의 동작은 2026년 11월 기준이고 바뀔 수 있다.
한 문장으로. 429는 「너는 안 돼」가 아니라 「그 요청은 안 받아」일 수 있다 — 옷을 갈아입혀 다시 보내기 전에, 어느 줄에 서 있는지부터 본다.