YouTube Transcript API를 이용한 Python으로 YouTube 대본 가져오기

YouTube Transcript API는 Python에서 YouTube 동영상의 대본을 간편하게 가져올 수 있는 강력한 라이브러리입니다. 이 글에서는 YouTube Transcript API를 설치하고 사용하는 방법을 설명하고, 일부 서버 환경에서 발생하는 이슈와 이에 대한 해결책도 다룹니다.


1. YouTube Transcript API 설치

먼저, youtube-transcript-api를 설치합니다. 아래 명령어를 실행하세요.

pip install youtube-transcript-api

2. 간단한 사용 예제

YouTube 동영상의 대본을 가져오기 위해, YouTube 동영상 ID를 사용합니다. 동영상 ID는 URL의 v= 뒤에 오는 값입니다. 예를 들어, URL이 https://www.youtube.com/watch?v=abcdefghijk라면 동영상 ID는 abcdefghijk입니다.

다음은 간단한 코드 예제입니다:

from youtube_transcript_api import YouTubeTranscriptApi
video_id = "abcdefghijk"  # 여기에 동영상 ID를 입력하세요
transcripts = YouTubeTranscriptApi.list_transcripts(video_id)

transcripts = [transcript.fetch() for transcript in transcripts][0]
transcripts = [(f'{item["start"]}s', item["text"]) for item in transcripts]

이 코드는 대본을 가져와 시작 시간과 함께 출력합니다.

3. Tor Proxy로 이슈 해결하기

youtube-transcript-api를 사용할 때 일부 서버 환경, 특히 AWS EC2 등에서 차단되는 경우가 있습니다. 이를 해결하기 위해 Tor Proxy를 사용할 수 있습니다. Tor는 IP를 변경하면서 프록시 역할을 수행하여 차단 문제를 우회할 수 있습니다. 또한, 특정 proxy 서비스에 가입할 필요가 없어서, 간단하게 적용 가능합니다.

Tor 설치 및 설정

EC2 또는 로컬 환경에 Tor를 설치하세요.

sudo apt update
sudo apt install tor -y

Tor를 실행하려면 아래 명령어를 입력합니다:

tor

Tor Proxy와 YouTube Transcript API 통합

tor의 기본 port는 9050입니다. proxy를 아래와 같이 설정합니다.

from youtube_transcript_api import YouTubeTranscriptApi
video_id = "abcdefghijk"  # 여기에 동영상 ID를 입력하세요
transcripts = YouTubeTranscriptApi.list_transcripts(video_id, proxies={
    "https": "socks5://127.0.0.1:9050",
    "http": "socks5://127.0.0.1:9050",
})

transcripts = [transcript.fetch() for transcript in transcripts][0]
transcripts = [(f'{item["start"]}s', item["text"]) for item in transcripts]
# 실행

주의 사항

  • Tor 네트워크는 대규모 API 호출에는 적합하지 않을 수 있습니다. Tor의 정책을 확인해보세요
  • 프록시 서버 중 일부는 이미 블럭되었을 수 있습니다. 필요하면 Tor Exit Node를 api호출시마다 변경하여 요청을 분산시킬 수 있습니다. 이 설정파일을 참고해주세요.

구현 코드

docker compose를 이용하여, Tor Proxy와 YouTube Transcript API를 통합한 코드를 여기에서 확인하실 수 있습니다.


4. 다국어 자막 처리

YouTube 동영상에는 여러 언어의 자막이 제공되는 경우가 많습니다. API를 통해 사용 가능한 언어를 확인하고, 특정 언어의 자막을 가져오거나 번역할 수 있습니다.

사용 가능한 자막 목록 확인

from youtube_transcript_api import YouTubeTranscriptApi

video_id = "abcdefghijk"
transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)

for transcript in transcript_list:
    print(f"언어: {transcript.language}, 코드: {transcript.language_code}")
    print(f"  자동 생성: {transcript.is_generated}")
    print(f"  번역 가능: {transcript.is_translatable}")

특정 언어 자막 가져오기

# 한국어 자막 가져오기
transcript = transcript_list.find_transcript(['ko'])
data = transcript.fetch()

# 한국어가 없으면 영어로 fallback
transcript = transcript_list.find_transcript(['ko', 'en'])
data = transcript.fetch()

자막 번역

번역을 지원하는 자막이라면 다른 언어로 번역할 수 있습니다.

transcript = transcript_list.find_transcript(['en'])
translated = transcript.translate('ko').fetch()  # 영어 → 한국어 번역

5. 자막 데이터 활용 예시

순수 텍스트 추출

def transcript_to_text(transcript_data):
    """자막 데이터를 순수 텍스트로 변환합니다."""
    return ' '.join(item['text'] for item in transcript_data)

text = transcript_to_text(data)
print(text)

SRT 자막 포맷으로 변환

def transcript_to_srt(transcript_data):
    """자막 데이터를 SRT 자막 포맷으로 변환합니다."""
    srt_lines = []
    for i, item in enumerate(transcript_data, 1):
        start = item['start']
        duration = item.get('duration', 0)
        end = start + duration

        start_time = format_srt_time(start)
        end_time = format_srt_time(end)

        srt_lines.append(f"{i}")
        srt_lines.append(f"{start_time} --> {end_time}")
        srt_lines.append(item['text'])
        srt_lines.append("")

    return '\n'.join(srt_lines)

def format_srt_time(seconds):
    """초 단위를 SRT 시간 형식으로 변환합니다."""
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)
    millis = int((seconds % 1) * 1000)
    return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"

6. 자주 발생하는 문제와 해결 방법

TranscriptsDisabled 에러

일부 동영상은 업로더가 자막을 비활성화한 경우가 있습니다. 이 경우 TranscriptsDisabled 예외가 발생하며, 업로더가 자막을 활성화하지 않는 한 해결 방법이 없습니다.

from youtube_transcript_api._errors import TranscriptsDisabled

try:
    transcripts = YouTubeTranscriptApi.list_transcripts(video_id)
except TranscriptsDisabled:
    print("이 동영상은 자막이 비활성화되어 있습니다.")

요청 속도 제한 (Rate Limiting)

짧은 시간에 너무 많은 요청을 보내면 YouTube에서 요청을 차단할 수 있습니다. 이를 방지하려면:

  • time.sleep()으로 요청 사이에 딜레이를 추가하세요
  • 재시도 시 지수 백오프(exponential backoff)를 구현하세요
  • 대규모 수집의 경우 위에서 설명한 Tor 프록시 로테이션을 활용하세요

연령 제한 동영상

연령 제한이 있는 동영상은 인증 쿠키가 필요할 수 있습니다. 유효한 세션 쿠키를 API에 전달하면 접근할 수 있지만, 쿠키 관리가 추가로 필요합니다.


7. 대량 자막 수집 아키텍처

여러 동영상의 자막을 대량으로 수집해야 하는 경우(예: 채널 전체 자막 수집, 연구 목적 데이터 수집), 안정적인 아키텍처가 필요합니다.

큐 기반 처리

import time
import random
from queue import Queue
from threading import Thread

class TranscriptWorker(Thread):
    def __init__(self, queue, results, proxy=None):
        Thread.__init__(self)
        self.queue = queue
        self.results = results
        self.proxy = proxy
        self.daemon = True

    def run(self):
        while True:
            video_id = self.queue.get()
            try:
                transcript = self._fetch_transcript(video_id)
                self.results[video_id] = transcript
            except Exception as e:
                self.results[video_id] = {"error": str(e)}
            finally:
                # 요청 간 랜덤 딜레이로 차단 방지
                time.sleep(random.uniform(1.0, 3.0))
                self.queue.task_done()

    def _fetch_transcript(self, video_id):
        proxies = None
        if self.proxy:
            proxies = {
                "https": self.proxy,
                "http": self.proxy,
            }

        transcript_list = YouTubeTranscriptApi.list_transcripts(
            video_id, proxies=proxies
        )
        transcript = list(transcript_list)[0]
        return transcript.fetch()

재시도 로직과 지수 백오프

import time
from youtube_transcript_api import YouTubeTranscriptApi

def fetch_with_retry(video_id, max_retries=3, base_delay=1.0):
    """지수 백오프를 적용한 재시도 로직"""
    for attempt in range(max_retries):
        try:
            transcripts = YouTubeTranscriptApi.list_transcripts(video_id)
            return [t.fetch() for t in transcripts][0]
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
            print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay:.1f}s")
            time.sleep(delay)

8. 자막 데이터를 활용한 실전 프로젝트

영상 요약 자동화

YouTube 자막을 가져와서 LLM(대규모 언어 모델)으로 요약하는 파이프라인을 구축할 수 있습니다.

from youtube_transcript_api import YouTubeTranscriptApi
import openai

def summarize_video(video_id):
    # 1. 자막 가져오기
    transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
    transcript = list(transcript_list)[0].fetch()

    # 2. 텍스트 추출
    full_text = ' '.join(item['text'] for item in transcript)

    # 3. LLM으로 요약 (예: OpenAI API)
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "다음 YouTube 영상 자막을 간결하게 요약해주세요."},
            {"role": "user", "content": full_text[:4000]}  # 토큰 제한 고려
        ]
    )

    return response.choices[0].message.content

키워드 타임스탬프 검색

특정 키워드가 등장하는 시점을 찾는 기능도 유용합니다.

def find_keyword_timestamps(transcript_data, keyword):
    """자막에서 키워드가 등장하는 시점을 모두 찾습니다."""
    results = []
    keyword_lower = keyword.lower()

    for item in transcript_data:
        if keyword_lower in item['text'].lower():
            minutes = int(item['start'] // 60)
            seconds = int(item['start'] % 60)
            results.append({
                'time': f"{minutes:02d}:{seconds:02d}",
                'start': item['start'],
                'text': item['text']
            })

    return results

# 사용 예시
timestamps = find_keyword_timestamps(transcript_data, "machine learning")
for ts in timestamps:
    print(f"[{ts['time']}] {ts['text']}")

9. Docker Compose를 이용한 Tor 프록시 구성

프로덕션 환경에서 Tor 프록시를 안정적으로 운영하려면 Docker Compose를 활용하는 것이 좋습니다.

docker-compose.yml 예시

version: '3.8'
services:
  tor:
    image: dperson/torproxy
    restart: always
    ports:
      - "9050:9050"    # SOCKS5 프록시
      - "9051:9051"    # 제어 포트
    environment:
      - TOR_NewCircuitPeriod=30    # 30초마다 새 회로 생성
      - TOR_MaxCircuitDirtiness=60 # 60초 후 회로 교체

  transcript-worker:
    build: .
    depends_on:
      - tor
    environment:
      - TOR_PROXY=socks5://tor:9050
    volumes:
      - ./data:/app/data

이 구성에서는 Tor 프록시가 별도 컨테이너로 실행되고, transcript-worker가 이를 통해 YouTube에 접속합니다. NewCircuitPeriodMaxCircuitDirtiness 설정으로 IP를 주기적으로 변경하여 차단을 방지합니다.