유튜브 데이터 추출, 왜 API만으로는 부족한가
미디어 분석팀이나 크리에이터 경제 연구자라면 이런 상황을 겪었을 것입니다—수천 개의 채널을 모니터링해야 하는데, YouTube Data API v3의 일일 할당량은 오전 중에 이미 바닥납니다. 댓글 스레드 대량 수집, 초기 트렌드 탐지, 광고 모니터링 같은 작업은 API만으로는 사실상 불가능에 가깝습니다.
이 가이드에서는 유튜브 공개 데이터를 프록시로 스크래핑하는 방법을 단계별로 설명합니다. YouTube Data API v3가 충분한 경우와 스크래핑이 필요한 경우를 비교하고, InnerTube JSON 엔드포인트 활용법, 주거 프록시 회전 전략, 그리고 윤리적·법적 경계까지 모두 다룹니다.
⚠️ 중요 고지: 이 글은 공개 접근 가능한 데이터에 대한 합법적 수집만을 다룹니다. 유튜브의 서비스 약관(Terms of Service)을 존중하고, 미국 CFAA, EU GDPR 등 관할 법률을 준수하세요. 로그인이 필요한 데이터, 비공개 동영상, 크리에이터의 지식재산권을 침해하는 수집은 절대 권장하지 않습니다.
YouTube Data API v3 vs 스크래핑: 언제 무엇을 쓸까
YouTube Data API v3는 공식적이고 안정적인 경로지만, 비용 모델이 까다롭습니다. 각 요청은 '할당 단위(quota unit)'를 소비하며, 기본 할당량은 하루 10,000 단위입니다.
| 항목 | YouTube Data API v3 | InnerTube 스크래핑 |
|---|---|---|
| 일일 한도 | 10,000 할당 단위 (증가 요청 가능) | IP당 속도 제한 (프록시로 회피 가능) |
| 검색 비용 | 100 단위/요청 | 사실상 무제한 (속도 제한 내) |
| 댓글 전체 스레드 | 불가 (최상위 댓글만) | 가능 (continuation 토큰 활용) |
| 자막 텍스트 | 미지원 | 가능 (youtube-transcript-api) |
| 광고 모니터링 | 미지원 | playerResponse에서 부분적 가능 |
| 안정성 | 높음 (공식 스펙) | 중간 (엔드포인트 변경 가능) |
| 인증 | API 키 + OAuth | 불필요 (공개 데이터) |
API가 충분한 경우: 소규모 채널 모니터링(일 100개 이하), 업로드 알림 자동화, 기본 메타데이터 수집.
스크래핑이 필요한 경우: 대규모 댓글 스레드 분석, 초기 트렌드 감지(검색 1회당 100 단위 소모), 자막 기반 콘텐츠 분석, 경쟁 광고 모니터링.
로그인 없이 접근 가능한 공개 데이터
유튜브는 로그인하지 않아도 브라우저에 표시되는 모든 데이터가 공개 데이터입니다. 여기에는 다음이 포함됩니다:
- 동영상 메타데이터: 제목, 설명, 조회수, 좋아요 수, 업로드 날짜, 카테고리
- 채널 페이지: 구독자 수, 총 조회수, 최신 동영상 목록
- 댓글 스레드: 최상위 댓글 + 답글 전체 (continuation 토큰으로 페이지네이션)
- 자동 생성/수동 자막: 타임스탬프 포함 텍스트
- 관련 동영상 목록: 추천 알고리즘의 공개 출력
반면 로그인이 필요한 데이터—구독 피드, 개인 재생목록, 나이 제한 콘텐츠—는 이 가이드의 범위를 벗어납니다. 이런 데이터를 스크래핑하는 것은 서비스 약관 위반이며 법적 위험이 큽니다.
InnerTube API: 유튜브의 내부 데이터 엔드포인트
유튜브 웹 프론트엔드는 InnerTube라는 내부 API를 사용해 데이터를 가져옵니다. 브라우저가 유튜브 페이지를 로드할 때 실제로 호출하는 것이 바로 이 엔드포인트들입니다.
핵심 엔드포인트
/youtubei/v1/next— 동영상의 다음 정보(관련 영상, 댓글)를 가져옵니다. 댓글 continuation 토큰의 출발점입니다./youtubei/v1/browse— 채널 페이지, 재생목록 탐색에 사용합니다./youtubei/v1/player— playerResponse를 반환합니다. 스트리밍 URL, 광고 슬롯 메타데이터, 포맷 정보를 포함합니다./youtubei/v1/search— 검색 결과를 JSON으로 반환합니다.
Continuation 토큰으로 댓글 페이지네이션하기
InnerTube는 댓글을 한 번에 모두 주지 않습니다. 대신 첫 요청에 continuation token을 포함해 반환하고, 이후 요청에서 이 토큰을 보내 다음 페이지를 가져옵니다. 이 패턴은 관련 동영상 목록에도 동일하게 적용됩니다.
동작 흐름:
/youtubei/v1/next에 videoId 전송 → 첫 번째 댓글 묶음 + continuation 토큰 수신- 수신한 continuation 토큰을
/youtubei/v1/next에 다시 전송 → 다음 댓글 묶음 + 새 continuation 토큰 - continuation 토큰이 더 이상 반환되지 않으면 종료
필수 헤더 구성
InnerTube 요청이 정상 작동하려면 브라우저를 모방하는 헤더가 필요합니다:
Content-Type: application/jsonUser-Agent: 최신 Chrome 문자열X-YouTube-Client-Name: 1(웹 클라이언트)X-YouTube-Client-Version: 최신 버전 (예:2.20240101)
왜 주거 프록시가 필요한가
구글은 데이터센터 IP 대역을 매우 잘 식별합니다. AWS, GCP, Azure, Hetzner 등의 IP는 구글의 내부 데이터베이스에 데이터센터로 분류되어 있으며, 이 대역에서 오는 대량 요청은 즉시 속도 제한이나 CAPTCHA 챌린지의 대상이 됩니다.
주거 프록시(residential proxy)는 실제 ISP가 할당한 IP를 사용하므로, 구글의 관점에서는 일반 가정용 사용자의 요청과 구별하기 어렵습니다. 이것이 대규모 유튜브 데이터 추출에 주거 프록시가 필수적인 이유입니다.
IP 회전 전략
- 요청별 회전(per-request rotation): 각 요청마다 새 IP를 할당받습니다. 대규모 댓글 수집에 적합합니다.
- 스티키 세션(sticky session): 일정 시간(보통 10~30분) 동안 같은 IP를 유지합니다. 채널 페이지 순회처럼 세션 일관성이 필요한 작업에 적합합니다.
ProxyHat에서는 사용자 이름에 플래그를 추가해 이 두 전략을 제어합니다:
- 요청별 회전:
user-country-US:pass - 스티키 세션:
user-session-abc123:pass
실전: Python으로 유튜브 공개 데이터 스크래핑하기
예제 1: 자막 추출 + 주거 프록시 회전
youtube-transcript-api 라이브러리는 자막 수집에 특화되어 있으며, 프록시 설정을 지원합니다.
from youtube_transcript_api import YouTubeTranscriptApi
# ProxyHat 주거 프록시 설정
proxy_url = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
proxies = {
"http": proxy_url,
"https": proxy_url,
}
video_id = "dQw4w9WgXcQ"
try:
transcript = YouTubeTranscriptApi.get_transcript(
video_id,
languages=["ko", "en"],
proxies=proxies
)
for entry in transcript:
print(f"[{entry['start']:.1f}s] {entry['text']}")
except Exception as e:
print(f"자막 추출 실패: {e}")
예제 2: InnerTube로 동영상 메타데이터 + 댓글 수집
이 예제는 /youtubei/v1/next 엔드포인트를 사용해 동영상 정보와 첫 댓글 페이지를 가져옵니다.
import requests
import json
proxy_url = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy_url, "https": proxy_url}
INNER_TUBE_URL = "https://www.youtube.com/youtubei/v1/next"
headers = {
"Content-Type": "application/json",
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
),
"X-YouTube-Client-Name": "1",
"X-YouTube-Client-Version": "2.20240601",
}
def fetch_video_comments(video_id: str, max_pages: int = 5):
"""동영상의 댓글을 continuation 토큰으로 페이지네이션하며 수집."""
payload = {
"videoId": video_id,
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20240601",
}
}
}
all_comments = []
continuation_token = None
for page in range(max_pages):
if continuation_token:
payload = {
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20240601",
}
},
"continuation": continuation_token
}
resp = requests.post(
INNER_TUBE_URL,
json=payload,
headers=headers,
proxies=proxies,
timeout=30
)
resp.raise_for_status()
data = resp.json()
# 첫 요청에서 continuation 토큰 찾기
if not continuation_token:
# 댓글 섹션의 continuation 토큰 추출
eng_panels = data.get("engagementPanels", [])
for panel in eng_panels:
content = panel.get("engagementPanelSectionListRenderer", {}) \
.get("content", {})
comments = content.get("continuationItemRenderer", {})
if comments:
continuation_token = comments \
.get("continuationEndpoint", {}) \
.get("continuationCommand", {}) \
.get("token")
break
else:
# 이후 페이지에서 댓글 텍스트 추출
contents = data.get("onResponseReceivedEndpoints", [])
for endpoint in contents:
actions = endpoint.get("appendContinuationItemsAction", {}) \
.get("continuationItems", [])
for item in actions:
comment_renderer = item.get("commentRenderer") \
or item.get("commentThreadRenderer", {}) \
.get("comment", {}) \
.get("commentRenderer", {})
if comment_renderer:
text = comment_renderer \
.get("content", {}) \
.get("runs", [{}])
comment_text = "".join(
t.get("text", "") for t in text
)
author = comment_renderer \
.get("authorText", {}).get("simpleText", "")
all_comments.append({
"author": author,
"text": comment_text
})
# 다음 continuation 토큰 찾기
new_token = None
for item in actions:
cont = item.get("continuationItemRenderer", {})
if cont:
new_token = cont \
.get("continuationEndpoint", {}) \
.get("continuationCommand", {}) \
.get("token")
break
continuation_token = new_token
if not continuation_token:
break
# 속도 제한 회피를 위한 대기
import time
time.sleep(2)
return all_comments
comments = fetch_video_comments("dQw4w9WgXcQ", max_pages=3)
for c in comments[:10]:
print(f"{c['author']}: {c['text'][:80]}")
예제 3: 여러 국가의 조회수 비교 (지역 타겟팅)
ProxyHat의 지역 타겟팅을 사용하면, 특정 국가에서 볼 때의 유튜브 페이지를 시뮬레이션할 수 있습니다. 이는 국가별 조회수·추천 영상 차이를 분석하는 데 유용합니다.
import requests
def get_view_count_from_country(video_id: str, country: str) -> dict:
"""특정 국가의 IP로 동영상 메타데이터를 가져옵니다."""
proxy_url = f"http://user-country-{country}:PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy_url, "https": proxy_url}
url = f"https://www.youtube.com/watch?v={video_id}"
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
)
}
resp = requests.get(url, headers=headers, proxies=proxies, timeout=30)
# HTML에서 ytInitialPlayerResponse 추출
import re
match = re.search(
r'var ytInitialPlayerResponse\s*=\s*({.+?});',
resp.text
)
if match:
data = json.loads(match.group(1))
video_details = data.get("videoDetails", {})
return {
"country": country,
"title": video_details.get("title"),
"view_count": video_details.get("viewCount"),
"length_seconds": video_details.get("lengthSeconds"),
}
return {"country": country, "error": "데이터 추출 실패"}
# 미국, 독일, 일본에서의 메타데이터 비교
for cc in ["US", "DE", "JP"]:
result = get_view_count_from_country("dQw4w9WgXcQ", cc)
print(json.dumps(result, ensure_ascii=False, indent=2))
import time; time.sleep(3)
예제 4: Node.js로 동영상 메타데이터 수집
const axios = require('axios');
const proxyUrl = 'http://user-country-US:PASSWORD@gate.proxyhat.com:8080';
async function fetchVideoMetadata(videoId) {
const payload = {
videoId,
context: {
client: {
clientName: 'WEB',
clientVersion: '2.20240601',
},
},
};
const headers = {
'Content-Type': 'application/json',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) ' +
'Chrome/125.0.0.0 Safari/537.36',
'X-YouTube-Client-Name': '1',
'X-YouTube-Client-Version': '2.20240601',
};
const resp = await axios.post(
'https://www.youtube.com/youtubei/v1/next',
payload,
{
headers,
proxy: {
host: 'gate.proxyhat.com',
port: 8080,
auth: {
username: 'user-country-US',
password: 'PASSWORD',
},
},
timeout: 30000,
}
);
const contents = resp.data?.contents
?.twoColumnWatchNextResults?.results?.results?.contents || [];
for (const content of contents) {
if (content.videoSecondaryInfoRenderer) {
const owner = content.videoSecondaryInfoRenderer
?.owner?.videoOwnerRenderer?.title?.runs?.[0]?.text;
console.log(`채널: ${owner}`);
}
}
}
fetchVideoMetadata('dQw4w9WgXcQ').catch(console.error);
속도 제한, 핑거프린팅, 그리고 실패 처리
속도 제한 패턴 이해
구글의 속도 제한은 단순한 '분당 N회' 모델이 아닙니다. 여러 신호를 조합해 봇 행위를 감지합니다:
- IP당 요청 빈도: 단일 IP에서 과도한 요청이 감지되면 일시적 차단(보통 1~12시간)
- 요청 패턴: 규칙적인 간격의 요청은 자동화로 감지됨. 지터(jitter) 추가 필수
- 브라우저 핑거프린트: TLS 지문, HTTP/2 설정, 헤더 순서 등이 브라우저와 불일치하면 의심
- 계정 신호: 로그인하지 않은 요청은 더 엄격한 제한 적용
모범 사례
- 요청 간 2~5초의 랜덤 대기를 추가하세요.
time.sleep(random.uniform(2, 5)) - 주거 프록시 회전을 사용해 단일 IP 부담을 분산하세요
- HTTP/2와 최신 TLS를 사용하는 HTTP 클라이언트를 선택하세요 (
httpx권장) - 429 응답 수신 시 지수 백오프로 재시도하세요
- 한 국가에서 과도한 요청을 보내지 말고 여러 국가 IP로 분산하세요
import random, time, requests
def request_with_backoff(url, max_retries=3, **kwargs):
"""지수 백오프와 랜덤 지터를 적용한 요청 함수."""
for attempt in range(max_retries):
try:
resp = requests.get(url, timeout=30, **kwargs)
resp.raise_for_status()
return resp
except requests.exceptions.HTTPError as e:
if e.response is not None and e.response.status_code == 429:
wait = (2 ** attempt) + random.uniform(1, 5)
print(f"속도 제한 감지. {wait:.1f}초 대기 후 재시도...")
time.sleep(wait)
else:
raise
raise Exception(f"{max_retries}회 재시도 후 실패")
윤리적 스크래핑과 공식 API 사용 시기
스크래핑은 강력한 도구지만, 모든 상황에서 정당한 것은 아닙니다. 다음 원칙을 지키세요:
지켜야 할 원칙
- 공개 데이터만 수집: 로그인 없이 볼 수 있는 정보만 대상으로 하세요
- 크리에이터 지식재산권 존중: 자막 텍스트, 동영상 내용을 원본 그대로 재배포하지 마세요. 분석 결과(통계, 트렌드)만 공개하세요
- robots.txt 확인: 유튜브의 robots.txt는 특정 경로를 허용/차단합니다. 이를 존중하세요
- 수집 목적의 합법성: 연구, 분석, 공공 이익 목적이어야 합니다. 스팸, 조작, 경쟁자 공격 목적은 금지
- 데이터 최소화: 필요한 데이터만 수집하고, 보유 기간을 최소화하세요 (GDPR 원칙)
공식 API를 써야 하는 경우
- 소규모 프로젝트(일 100개 이하 동영상 추적) → API 할당량으로 충분
- 안정성이 최우선인 프로덕션 시스템 → InnerTube 엔드포인트는 언제든 변경될 수 있음
- 사용자 인증이 필요한 작업 → OAuth를 통한 공식 API만 합법적 경로
- 상업적 서비스에서 유튜브 데이터를 제품의 핵심 기능으로 사용 → YouTube API 서비스 약관 준수 필수
핵심 구분: '연구·분석을 위해 공개 데이터를 수집하는 것'과 '수집한 콘텐츠를 그대로 재판매·재배포하는 것'은 전혀 다른 문제입니다. 전자는 합법적 공간이 있지만, 후자는 크리에이터의 권리를 침해합니다.
Key Takeaways
- YouTube Data API v3는 소규모 작업에 적합하지만, 할당량 비용(검색 100단위/회) 때문에 대규모 분석에는 현실적 한계가 있습니다
- InnerTube 엔드포인트(
/youtubei/v1/next,/youtubei/v1/player등)를 사용하면 공개 데이터를 JSON으로 직접 수집할 수 있습니다 - 주거 프록시는 데이터센터 IP 차단을 우회하는 핵심 도구입니다. 요청별 회전과 스티키 세션을 작업 특성에 맞게 선택하세요
- continuation 토큰으로 댓글·관련 영상의 페이지네이션을 처리하세요. 토큰이 반환되지 않으면 마지막 페이지입니다
- 랜덤 지터와 지수 백오프를 항상 적용하세요. 규칙적인 요청 패턴은 봇 탐지의 핵심 신호입니다
- 윤리적 경계를 명확히 하세요: 공개 데이터만, 재배포 금지, 크리에이터 권리 존중
대규모 유튜브 데이터 추출을 시작할 준비가 되었다면, ProxyHat 요금제를 확인하고 주거 프록시로 첫 스크래핑을 실행해 보세요. 전 세계 190개 이상 국가의 주거 IP를 즉시 사용할 수 있습니다.






