YouTube 자막 스크래퍼가 빈 문자열을 반환하기 시작한 이유 (그리고 2026년에 이를 해결하는 방법)
요약
YouTube의 BotGuard 시스템 도입으로 인해 기존 자막 스크래퍼가 빈 응답을 반환하는 문제를 분석하고, PoToken을 활용한 해결 방법을 제시합니다. Watch page 파싱, PoToken 발행, 파라미터 추가를 통한 단계별 복구 프로세스를 설명합니다.
핵심 포인트
- YouTube는 PoToken(Proof-of-Origin Token) 요구로 스크래핑을 방어함
- 데이터센터 IP(AWS/GCP 등)는 차단되거나 제한될 가능성이 높음
- PoToken은 특정 비디오 ID에 바인딩되어 발행되어야 함
- 자막 요청 시 &pot= 및 &c=WEB 파라미터 포함이 필수적임
- bgutils-js를 활용해 BotGuard 챌린지를 효율적으로 해결 가능
만약 1년 전에 YouTube 자막을 추출하던 스크립트를 가지고 있다면, 그것이 조용히 고장 났을 가능성이 높습니다. 스크립트는 여전히 실행되고 에러도 발생하지 않지만, 단지 **빈 값(empty)**을 반환할 뿐입니다. 무엇이 변했는지, 그리고 어떻게 하면 다시 자막을 가져올 수 있는지 알려드리겠습니다.
증상
YouTube의 자막 엔드포인트(timedtext)를 호출하면 HTTP 200 응답을 받지만... 본문(body)은 비어 있습니다. 예외(exception)도, 403 에러도, 잡아낼 수 있는 그 어떤 것도 없습니다. 그냥 아무것도 없습니다. 그래서 당신의 파이프라인은 즐겁게 빈 자막을 기록하게 되고, RAG 인덱스가 빈칸으로 가득 찰 때까지 당신은 이를 알아차리지 못합니다.
이것이 바로 많은 인기 라이브러리들이 2025~2026년에 작동을 멈춘 이유이며, 심지어 여전히 "유지 관리(maintained)"되고 있는 라이브러리들조차 마찬가지입니다. 예전에는 작동하던 요청 형태(request shape)가 이제는 아무것도 반환하지 않습니다.
실제로 변한 것: PoToken
이제 YouTube는 자막 요청 시 BotGuard 시스템에 의해 생성되는 **Proof-of-Origin Token (PoToken)**을 요구합니다. 특정 비디오에 결합된 유효한 토큰이 없으면, timedtext는 빈 200 응답을 반환합니다. 또한 데이터센터 IP(AWS/GCP/Azure) 역시 차단되거나 심하게 제한(throttled)되는데, 이것이 서버 측 스크래퍼가 조용히 실패하는 두 번째 이유입니다.
따라서 현대적인 해결 방법은 세 단계로 이루어집니다:
- Watch page를 가져와서
ytInitialPlayerResponse를 파싱하여 자막 트랙(caption tracks)과visitorData를 추출합니다. - BotGuard 챌린지를 해결하여 비디오 ID에 결합된 **PoToken을 발행(Mint)**합니다.
&pot=<token>&c=WEB&fmt=json3파라미터를 포함하여 자막 트랙을 요청합니다. 그러면 실제 JSON을 받을 수 있습니다.
PoToken 부분이 모두가 막히는 지점입니다. BotGuard를 직접 역공학(reverse-engineer)할 필요는 없습니다. bgutils-js (실행 환경인 DOM을 제공하기 위해 jsdom과 함께 사용)가 챌린지를 처리해 줍니다. 구현 형태는 다음과 같습니다:
import { BG, buildURL, GOOG_API_KEY } from 'bgutils-js';
import { JSDOM } from 'jsdom';
...
저를 괴롭혔던 몇 가지 사항들입니다:
- 토큰을 일반적인 식별자가 아닌 비디오 ID에 바인딩 (
mintAsWebsafeString(videoId))해야 합니다. 세션 전용 토큰은 여전히timedtext에서 빈 값을 반환합니다. &pot=과 함께&c=WEB이 반드시 필요합니다. 이를 놓치면 다시 빈200응답을 받게 됩니다.- 무결성 토큰(Integrity token)은 TTL(~12시간)이 있으므로, 배치 작업을 수행할 때는 한 번 부트스트랩(Bootstrap)한 후 민터(Minter)를 재사용하십시오.
작은 CLI로 패키징했습니다
매번 이를 다시 유도하는 것에 지쳐서, 설정이 필요 없는 MIT 라이선스 패키지로 만들었습니다:
npx get-youtube-transcript https://www.youtube.com/watch?v=jNQXAC9IVRw
이 도구는 시청 페이지(watch-page) → PoToken → 자막(captions)으로 이어지는 모든 과정을 수행하며 자막을 (text/JSON/SRT 형식으로) 출력합니다. 소스: github.com/jamhimself/youtube-transcript-cli. 단일 비디오를 대상으로 하며 사용자의 IP에서 실행되므로, 스크립트나 노트북(Notebooks) 작업에 완벽합니다.
어려워지는 지점: 규모 확장 (Scale)
이 CLI는 수백 또는 수천 개의 비디오가 필요하기 전까지는 매우 잘 작동합니다. 하지만 그 단계에 도달하면 또 다른 벽에 부딪히게 됩니다. YouTube는 속도 제한(Rate-limits)을 걸고 데이터 센터 IP를 차단하기 때문에, 관리되지 않는 서버 작업은 금방 제한(Throttled)됩니다. 그 시점부터는 회전식 주거용 프록시(Rotating residential proxies) + 재시도(Retries) + 가동 시간 모니터링(Uptime monitoring)이 필요한데, 이는 "자막 파싱"과는 별개의 프로젝트입니다.
그 부분은 제가 Apify에서 호스팅 서비스로 운영하고 있습니다 — 자막 스크래퍼와 채널 전체를 RAG로 변환하는 채널 전체-to-RAG 버전이 있습니다. 후자는 채널 목록을 나열하고 모든 비디오의 자막을 청크(Chunked)화된 임베딩 준비 완료 텍스트로 반환합니다. CLI와 동일한 엔진을 사용하지만, 사용자가 직접 관리할 필요가 없도록 프록시/가동 시간 레이어가 추가된 형태입니다. ("이걸 대규모로 어떻게 하나요?"라는 질문이 필연적으로 뒤따를 것이기에 언급했습니다. 하지만 소량 작업에는 위에서 소개한 CLI만으로도 충분합니다.)
요약 (TL;DR)
- 2026년의 빈 자막 (Empty transcripts) = PoToken 누락 + 데이터센터 IP 차단.
- 해결 방법: 동영상 페이지 접속 → 자막 트랙 확인 → 동영상에 바인딩된 PoToken 생성 (
bgutils-js) →&pot=&c=WEB&fmt=json3파라미터를 사용하여 가져오기. - 일회성 사용 시:
npx get-youtube-transcript <url>사용. - 대규모 운영 시: 레지덴셜 프록시 (Residential proxies)와 재시도 (Retries) 로직이 추가로 필요함 — 실제 비용은 파싱이 아니라 이 부분에서 발생함.
만약 자막 파이프라인이 조용히 빈 값을 반환하고 있었다면, 이제 그 이유를 알게 되셨을 겁니다. 지금 바로 RAG 인덱스를 확인해 보세요. 🙃
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기