Youtube 최종 결정 반영(2026-07-23) · 코드 반영 전

Social Fetcher URL Structure Audit — 2026-07-19
관련 코드: social-fetcher.router.ts(classifyYoutube) · fetchers/youtube.fetcher.ts
관련 데이터: samples/youtube.json · samples/unknown.json
소스 채택 권장도 low
YouTube은 밈코인 모멘텀이 실시간으로 형성되는 채널이 아니다(제작~업로드 지연이 밈코인 분/시간 단위 변동성과 안 맞음). 현재 구현(무료 oEmbed)은 title/author_name/author_url/thumbnail_url만 반환하는데 트레이더 렌즈에서 전부 context 이하(actionable 0건) — 매매 판단을 바꾸는 필드가 하나도 없다. 유료 Apify 티어를 붙여야 처음으로 actionable 필드(numberOfSubscribers, channelJoinedDate, isChannelVerified)가 생기지만 이는 '모멘텀 발굴용'이 아니라 '이미 발견한 언급의 사칭/러그 판별용' 좁은 용도다.
트레이더 관점: 밈코인 트레이더는 유튜브를 보고 진입 타이밍을 잡지 않는다 — X/Telegram이 초 단위 하이프의 무대이고 유튜브는 그보다 몇 시간~며칠 늦게 따라오는 후행 지표다. 이 소스가 쓸모 있는 유일한 순간은 '유튜버가 이 토큰을 홍보하는데, 이 채널 진짜냐 방금 만든 가짜 계정이냐'를 확인할 때뿐이다 — 그때도 무료 oEmbed로는 판단 불가하고 유료 Apify의 구독자수+개설일+인증배지 조합이 있어야 한다. 즉 1차 신호 소스가 아니라 러그/사칭 검증용 2차 보조 소스로만 조건부 채택할 가치가 있다.
✅ 최종 결정 (수기 audit 반영)
원칙: sourceKey는 unique & immutable & linkable — content에도 그 키가 있어야 creator로 분기 없이 연결. 같은 채널이 handle/UCxxx/옛핸들로 분기되면 안 됨.
조인 키
객체비고
Creator (channel)channelId (UCxxx)video.snippet.channelId가 영상 응답에 직접 포함 → channelId를 canonical 조인/dedup 키로. handle/customUrl/user-name은 alias(router sourceKey)이고 channels.list?forHandle/forUsername으로 channelId 해석. ⚠ 현재 fetcher는 oEmbed라 channelId 미제공(handle만) → Data API v3 전환 후 확보, 그전엔 handle 폴백.
Content (video)videoId (콘텐츠 자체) + channelId (creator 링크)videoId=dedup 키. creator 링크는 snippet.channelId(영상 응답에 직접).
sourceType 통합 맵
최종 타입포함 패턴
youtube_channel/channel/UC.., /@handle(+tab), /user/{name}(+/videos), /c/{name} — canonical 키=channelId로 통일(분기 방지)
youtube_video/watch?v, youtu.be, /shorts, /live, /embed, /v + host 확장(music.youtube.com, youtube-nocookie.com)
youtube_playlist/playlist?list — key=playlistId, fetch 보류(컬렉션, 밈신호 낮음). 리터럴 'playlist' 키 버그 교정
이 문서에서 쓰는 pill 범례
관측 source-data에서 실제 발견됨 웹리서치/실측 검색·curl·실제 호출로 확인 정상/유지 차단·버그·갭·drop 신규/분리 타입 fixed / mutable confidence: high medium low
목차   1a. 개념 글로서리 · 1b. URL 패턴 · 2. sourceType/sourceKey 판정 · 3. Fetch 테스트 + 획득 방법 · 4. 필드 통합 (변동성·파싱·판정) · 다음 액션

1a. 플랫폼 개념 글로서리

이 플랫폼을 이해하는 데 필요한 도메인 개념. URL/필드 설명에서 참조.

개념설명
video (watch URL)youtube.com/watch?v={videoId} — 표준 단일 영상 URL. videoId는 쿼리 파라미터 v로 전달됨. 관측
videoId11자 base64url-like 문자열(A-Za-z0-9_-). 대소문자 구분(case-sensitive) — 예: rkJAgXQYWAI, 5MaWkuCz-G8. 다른 케이스는 다른 영상을 가리키므로 정규화 시 소문자 변환 금지. 관측
youtu.be 단축 URLyoutu.be/{videoId} — watch URL의 축약형, 리다이렉트로 동일 영상 도달. si= 등 공유 트래킹 쿼리 파라미터가 흔히 붙음(핵심값 아님, 무시 가능). 관측
si 쿼리 파라미터공유 링크에 붙는 트래킹 식별자(예: ?si=XhdPNKpzampT0k-6). 영상 식별과 무관하므로 videoId 추출 시 제거 대상. 관측
channel handle (@handle)youtube.com/@{handle} — 2022년 이후 채널의 사람이 읽을 수 있는 고유 식별자(핸들). @ 접두사 필수, 대소문자 무관하게 매칭되나 표기는 원문 유지(예: @dexerto, @FourMeasure). 트레일링 슬래시(/@dexerto/)는 동일 채널을 가리킴. 관측
legacy /user/{username} URLyoutube.com/user/{username} — 구버전 커스텀 사용자명 기반 채널 URL. 신규 발급 중단, 기존 채널만 유지. 관측 표본에서 fetch 시 not_found 다수(oEmbed 등 API가 handle/channelId 미지원 형태로 처리 못함 가능성). 관측
channelId (/channel/{UC...})youtube.com/channel/{channelId} — 불변의 내부 채널 고유 ID, UC로 시작하는 24자 문자열. handle과 달리 채널명 변경에도 불변. 관측
channel sub-path (/videos, /featured 등)채널 URL 뒤에 붙는 탭 경로. /videos는 해당 채널의 업로드 영상 목록 탭. 채널 자체 식별에는 불필요하며 스킴상 {handle 또는 user}/{탭} 형태로 나타남. 관측
video vs shortsYouTube는 일반 영상(watch?v=)과 짧은 세로형 영상인 Shorts(youtube.com/shorts/{videoId})를 구분 제공. 관측 표본에는 shorts URL이 없었으나 실제 존재하는 URL 스킴이므로 파서 설계 시 고려 필요. 웹리서치/실측
playlistyoutube.com/playlist?list={playlistId} 또는 watch?v={videoId}&list={playlistId} — 영상 모음. playlistId는 videoId와 별개 네임스페이스(보통 PL/UU/OL 등 접두사). 웹리서치/실측
channel vs handlechannel은 내부적으로 channelId(UC...)로 식별되는 불변 엔티티이고, handle(@name)은 그 채널에 부여된 변경 가능한 사람이 읽는 별칭. 하나의 channelId는 시점에 따라 다른 handle을 가질 수 있음. 웹리서치/실측
authorName / authorUrl (oEmbed)YouTube oEmbed API가 반환하는 필드로 영상 업로더의 채널명(authorName)과 채널 URL(authorUrl, 보통 @handle 형태). 관측 표본에서 fixed(고정값) 성격으로 분류됨 — 영상 자체와 함께 불변. 관측
thumbnailUrl (i.ytimg.com)i.ytimg.com/vi/{videoId}/hqdefault.jpg 형태의 영상 썸네일 CDN URL. videoId로부터 결정적으로 파생 가능. 관측
subscriber count채널을 구독한 사용자 수. 채널 단위 지표이며 영상 단위 지표(view/like)와 별개 — YouTube Data API의 channels.statistics에서 조회. 웹리서치/실측
view count영상이 재생된 횟수. 영상 단위 지표, videos.statistics에서 조회 가능. 웹리서치/실측
like count영상에 좋아요를 누른 수. dislike count는 2021년 이후 공개 API에서 비공개 처리됨. 웹리서치/실측
www 접두사 유무youtube.com과 www.youtube.com은 동일 서비스를 가리키며 관측 표본 대부분 www 포함이나 도메인 정규화 시 www 유무는 무시 가능. 관측

1b. URL 패턴

https://www.youtube.com/watch?v=rkJAgXQYWAI
표준 시청 URL — videoId가 유일 식별자, 쿼리 파라미터(si, list 등)는 부가정보로 무시 가능
https://youtu.be/audhiM3Ur1I?si=XhdPNKpzampT0k-6
단축 URL — path 전체가 videoId, si= 트래킹 쿼리는 버림(관측 샘플 중 개행+텍스트 오염 사례 존재, 파싱 시 첫 세그먼트만 취해야 함)
https://www.youtube.com/@dexerto/videos
핸들 프로필 — @ 포함 handle이 식별자, 대소문자는 원문 보존되나 router는 key를 소문자화; 뒤에 /videos 같은 탭 세그먼트가 붙을 수 있음(무시 가능)
https://www.youtube.com/channel/UCbrPqq29C9Q_TQP7OFFRzcw
채널 ID URL — UC로 시작하는 채널 고유 ID가 식별자(대소문자 보존 필수, router가 이미 보존)
https://www.youtube.com/user/DaveThomasFoundation
레거시 사용자명 URL — 사람이 읽는 이름이 식별자(대소문자 무의미, router가 소문자화하여 key로 사용)
URL 패턴의미ID 후보관측출처/비고
youtube.com/watch?v={videoId}표준 동영상 시청 URL (감사 template 'youtube.com/{value}'에 흡수, count 201 중 다수)videoId (query v=)관측 201건youtube.json structures[0] count=201, 표본 3건 전부 watch?v= 형태
youtube.com/user/{name}레거시 사용자명 채널 (템플릿 'youtube.com/{value}/{value}'에 channel/{id}와 합쳐서 집계)username (소문자화됨, 예: davethomasfoundation)관측 159건youtube.json structures[1], 표본 2/3이 /user/{name}
youtube.com/channel/UC{22 chars}채널 고유 ID (대소문자 보존 필요, router가 이미 보존)channelId (UC로 시작하는 24자 ID)관측 159건youtube.json structures[1] 표본 3/3이 /channel/UCxxxx
youtube.com/@{handle}공식 핸들 프로필 (2022년 이후 YouTube 표준)handle (소문자화)관측 104건youtube.json structures[2] count=104
youtu.be/{videoId}단축 URL, 쿼리(si=, 개행 등 오염 포함 가능)videoId관측 45건youtube.json structures[3] count=45; 표본 3번째는 si=...뒤에 개행+텍스트 오염 사례
youtube.com/@{handle}/{tab}핸들 프로필의 서브탭(videos 등)handle (tab은 videos/shorts/streams/about 등 무시 가능)관측 3건youtube.json structures[4] count=3, tab=videos
youtube.com/user/{name}/videos레거시 사용자명 채널의 videos 탭username관측 2건youtube.json structures[5] count=2
youtube.com/shorts/{videoId}Shorts 재생 URL — 미관측이나 router classifyYoutube가 이미 처리 대상으로 분기시킴(watch/shorts/live/embed/v 동일 취급)videoId관측 0건unobserved in sample; 코드 로직(classifyYoutube)상 지원 확인됨
youtube.com/live/{videoId}라이브 스트림 재생 URL — 미관측, 코드상 지원videoId관측 0건unobserved in sample; router 지원
youtube.com/embed/{videoId}임베드 플레이어 URL — 미관측, 코드상 지원videoId관측 0건unobserved in sample; router 지원
youtube.com/v/{videoId}구형 Flash 임베드 URL — 미관측, 코드상 지원(레거시)videoId관측 0건unobserved in sample; router 지원
youtube.com/c/{name}커스텀 URL(레거시, 2022년경 @handle로 대체 진행 중) — 미관측이나 router가 명시적으로 처리customName (소문자)관측 0건unobserved in sample; router 명시 지원. 웹지식: 신규 채널은 발급 중단, 기존 채널은 유지
youtube.com/playlist?list={playlistId}재생목록 URL — 완전 미관측 + router 미지원(sourceType='youtube' 단일분류 로직에 playlist 케이스 없음). 현재 코드에서 실제로 어떻게 떨어지는지 별도 확인 필요playlistId (PL/UU/FL/RD 등 prefix)관측 0건unobserved in both samples; 웹지식만 근거. classifyYoutube 요약에 playlist 언급 없음 — unknown 또는 오분류 가능성, 코드 재확인 권장

2. sourceType / sourceKey 판정 👤 판단 반영

2.1 sourceType 적합도 👤 판단 반영

패턴현재 type판정/제안sourceKeyconf근거사용자 결정
youtube.com/watch?v={videoId}
youtube (병합)youtube_videoyt-video-watchhighYoutubeFetcher는 routed.sourceUrl 원본을 그대로 youtube.com/oembed 에 넘긴다. YouTube 공식 oEmbed 는 video URL에서만 title/author/thumbnail 을 안정적으로 반환하고, 채널류 URL은 fetcher 주석에도 '401/403 가능 → blocked' 로 명시돼 있다. 즉 video와 channel은 fetch 성공률·회수 데이터가 근본적으로 다른데 지금은 통계·모니터링에서 구분이 안 된다. trader-value 관점에서도 video=콘텐츠/모멘텀(펌핑 영상 존재 여부), channel=팀 정체성·도달(rug-risk·팔로워 규모류)로 판단축이 다르다.✅ youtube_video 확정(videoId=콘텐츠 키). 기존 제안 유지.
youtube.com/user/{name}
youtube (channel/{id} 와 합쳐서 집계)youtube_channelyt-channel-userhigh채널류(user/channel/c/@handle) 전부 oEmbed가 사실상 실패하는 동일 카테고리이므로 이들끼리는 병합이 맞다. video 그룹과는 split. observed=159로 video(246) 대비 채널류 합계(159+159+104+3+2=427)가 오히려 더 크므로, 현재 단일 'youtube' 타입 아래에서는 fetch 실패(error)가 다수를 차지할 가능성이 높다는 게 중요한 커버리지 시사점.✅ youtube_channel로 통합. canonical 키=channelId(user-name은 alias→forUsername resolve). 기존 제안 유지.
youtube.com/channel/UC{22 chars}
youtube (user/{name} 와 합쳐서 집계, key만 원형 보존)youtube_channelyt-channel-idhigh채널 정준 ID. handle/username과 표현만 다를 뿐 트레이더 관점에서 동일 카테고리(팀 정체성·도달)이며, oEmbed 미지원이라는 fetch 특성도 동일. video 그룹과는 분리(merge into channel bucket, split from video).✅ youtube_channel. /channel/은 UCxxx=channelId 직접. 기존 제안 유지.
youtube.com/@{handle}
youtubeyoutube_channelyt-handlehigh2022년 이후 표준 핸들. channel/user/c 와 동일 카테고리(채널 정체성). observed=104로 무시 못할 볼륨.✅ youtube_channel. handle=alias, canonical=channelId(forHandle resolve). 분기 방지 통일.
youtu.be/{videoId}
youtubeyoutube_videoyt-video-shortmedium단축 URL, video 그룹에 속함(watch?v= 와 동일 fetch 경로). 다만 observed 표본에 si= 뒤 개행/텍스트 오염 사례가 있어 — 이건 sourceType 분류 이슈가 아니라 상류 스크레이핑 데이터 정제 이슈. 오염된 URL은 router.normalize()에서 파싱 실패로 unknown 처리될지 확인 필요(정규화 로직 직접 추적은 안 함, 표본 근거로만 플래그).✅ youtube_video. 기존 제안 유지.
youtube.com/@{handle}/{tab}
youtube (tab 무시, key=handle)youtube_channelyt-handle-tabhighrouter가 이미 segments[0]만 보고 tab을 버리므로 @handle과 동일 key로 정규화됨. 채널 그룹에 합류.✅ youtube_channel(tab 무시). canonical=channelId. 기존 제안 유지.
youtube.com/user/{name}/videos
youtube (tab 무시, key=username)youtube_channelyt-user-videos-tabhighrouter가 이미 segments[1]만 보고 /videos tab을 무시. user/{name}과 동일 취급 — 채널 그룹.✅ youtube_channel. canonical=channelId. 기존 제안 유지.
youtube.com/shorts/{videoId}
youtube (코드상 지원, 미관측)youtube_videoyt-shortsmediumrouter가 이미 videoId를 원형 보존해 뽑아내고 있음. video 그룹에 속함. 미관측이라 실사용 빈도는 확인 안 됨.✅ youtube_video. 기존 제안 유지.
youtube.com/live/{videoId}
youtube (코드상 지원, 미관측)youtube_videoyt-livemedium라이브 스트림도 video 그룹. 다만 라이브는 oEmbed 응답이 진행 중 상태에 따라 title이 변하는(스트림 제목 변경 가능) mutable 가능성 — video인데도 valueNature가 watch류와 다를 수 있어 field-necessity 판정관에 별도 플래그 권장.✅ youtube_video. 기존 제안 유지.
youtube.com/embed/{videoId}
youtube (코드상 지원, 미관측)youtube_videoyt-embedmediumvideo 그룹. 실제 트윗/사이트 임베드에서 흔한 패턴이라 미관측이어도 향후 유입 가능성 있음.✅ youtube_video. 기존 제안 유지.
youtube.com/v/{videoId}
youtube (코드상 지원, 미관측)youtube_videoyt-v-legacylow구형 Flash 임베드, 사실상 사멸한 패턴. video 그룹에 합류시키되 실사용 가능성 낮음 — 유지보수 우선순위 낮음.✅ youtube_video. 기존 제안 유지.
youtube.com/c/{name}
youtube (코드상 지원, 미관측)youtube_channelyt-c-legacymedium레거시 커스텀 URL, 채널 그룹. 신규 채널 발급은 중단됐지만 기존 채널은 계속 노출되므로 완전 죽은 패턴은 아님.✅ youtube_channel. customName=alias, canonical=channelId. 기존 제안 유지.
youtube.com/playlist?list={playlistId}
youtube (실질적으로 오분류 — 코드 확인 결과 sourceKey='playlist' 리터럴 문자열로 떨어짐, 실제 playlistId 유실)youtube_playlist (또는 최소한 key 추출 버그 수정)yt-playlisthighrouter 코드 직접 확인: classifyYoutube에서 segments[0]==='playlist' 는 shorts/live/embed/v/watch/@/channel/c/user 어느 분기에도 안 걸리고 최종 catch-all { sourceType: 'youtube', sourceKey: this.lc(first) || null } 로 떨어져 sourceKey가 실제 playlistId(list= 쿼리파라미터)가 아니라 리터럴 'playlist' 문자열이 된다. 이는 순수 라우팅 버그. 다만 trader-value 관점에서 playlist는 개별 영상/채널 정체성보다 우선순위 낮은 컨텐츠 묶음이라 fetcher 신규 구현까지는 낮은 ROI — 최소한 오분류(key 유실)만은 수정 필요.✅ youtube_playlist 타입만 정의(key=playlistId), fetch 보류(컬렉션·밈신호 낮음). 리터럴 키 버그 교정.
music.youtube.com/watch?v={videoId} (host 커버리지 갭)
unknown (host 매칭 목록에 없음 — youtube.com/m.youtube.com/youtu.be만 포함)youtube_video (host 목록에 추가)yt-host-musicmediumrouter의 host 분기(social-fetcher.router.ts classify)는 youtube.com·m.youtube.com·youtu.be만 처리. music.youtube.com은 다른 host라 전부 'unknown' 또는 apex-domain 기반 'website' 로 새 버킷. 밈코인 테마곡/음원 홍보 URL이 존재할 수 있어 완전 무가치는 아니나, 실측 표본에 미관측이라 우선순위는 낮음.✅ youtube_video로 흡수(host 확장 music.youtube.com). 기존 제안 유지.
youtube-nocookie.com/embed/{videoId} (host 커버리지 갭)
unknown/website (host 미포함)youtube_video (host 목록에 추가, 원한다면)yt-host-nocookielow프라이버시 강화 임베드용 별도 도메인. 웹사이트 임베드에서 흔히 쓰이나 밈코인 소셜 URL 필드에 직접 등장할 확률은 낮음(주로 iframe src). 실측 미관측 + 신호가치 낮아 우선순위 최하.✅ youtube_video로 흡수(host 확장 youtube-nocookie.com). 기존 제안 유지.

2.2 id 추출 적합도 👤 판단 반영

패턴추출 idresolve?판정conf근거사용자 결정
youtube.com/watch?v={videoId}videoId (대소문자 보존)okhighlive-test 직접 확인: oEmbed(youtube.com/oembed?url=...watch?v=rkJAgXQYWAI)가 200으로 title/authorName/authorUrl/thumbnailUrl 반환. videoId는 YouTube 전역 불변 식별자이며 sourceKey와 정확히 일치 — 진짜 고유 식별자.✅ videoId=콘텐츠 키 확정.
youtube.com/user/{name}username (소문자화)wronghighlive-test 직접 확인: oEmbed(/user/DaveThomasFoundation)은 404 not_found — 무료 경로로는 아예 이 sourceKey를 소비 못 함. 더 근본적으로 username은 채널의 '진짜' 식별자가 아니라 레거시 별칭(alias)이고, 실제 고유키는 channelId(UC...)임(id-probe: curl -I가 200/no-redirect만 반환, canonical id는 노출 안 됨). 같은 채널이 /channel/UCxxx 형태로도 등장하면 서로 다른 sourceKey가 나와 dedup 실패 — Social-field loss filter 연구의 sameHandle 판별 로직이 이 alias 불일치로 같은 계정을 다른 계정으로 오판할 위험.✅ router키=alias(소문자). canonical=channelId(forHandle/forUsername resolve)로 통일 — 분기 방지.
youtube.com/channel/UC{22 chars}channelId (대소문자 보존)okhighid-probe 확인: /channel/UCbrPqq29C9Q_TQP7OFFRzcw는 curl -I에서 200/no-redirect — 이미 canonical 형태. 별도 Apify 라이브 테스트에서도 @dexerto가 실제로 resolve되는 진짜 채널 식별자가 channelId(UCWWCHrEqzME9N6JTuL6YUaw)임을 재확인 — channelId 자체는 진짜 고유·불변 식별자. 다만 현재 무료 oEmbed 경로는 이 형태를 404 not_found로 소비 못함(별도 fetcher 이슈, id-fit 문제 아님).✅ UCxxx=channelId 직접, canonical 키.
youtube.com/@{handle}handle (소문자화)wronghighlive-test 직접 확인: oEmbed(/@dexerto) 404 not_found — 무료 경로 소비 불가. 결정적으로 유료 Apify 테스트가 sourceKey=dexerto(handle)와 실제 채널 식별자(channelId=UCWWCHrEqzME9N6JTuL6YUaw)가 다르다는 'id-fit gap'을 직접 확인함. handle은 채널 소유자가 변경 가능한 mutable alias라 시간에 따라 같은 channelId가 다른 handle로 바뀔 수 있음 — 장기 dedup/재식별에 부적합. sameHandle 기반 사칭탐지 로직이 이 alias 위에서 동작 중이면 handle 변경 시 오탐/누락 위험.✅ router키=alias(소문자). canonical=channelId(forHandle/forUsername resolve)로 통일 — 분기 방지.
youtu.be/{videoId}videoIdokhighlive-test 이중 확인: (1) oEmbed(youtu.be/audhiM3Ur1I)가 200으로 정상 필드 반환, (2) id-probe(curl -I)에서 youtu.be가 303으로 watch?v=audhiM3Ur1I(동일 id)로 HTTP 리다이렉트 — sourceKey로 추출한 videoId가 canonical id와 정확히 일치함을 별도 채널로 재확인. 진짜 고유 식별자 확정.✅ videoId=콘텐츠 키 확정.
youtube.com/@{handle}/{tab}handle (tab 제거)wronghighlive-test 직접 확인: oEmbed(/@techbullion/videos) 404 not_found — bare @handle과 동일한 실패. 근본 원인도 동일(handle=mutable alias, 진짜 식별자는 channelId). #4의 id-fit gap이 그대로 적용됨.✅ router키=alias(소문자). canonical=channelId(forHandle/forUsername resolve)로 통일 — 분기 방지.
youtube.com/user/{name}/videosusernamewronghighlive-test 직접 확인: oEmbed(/user/kabosumama/videos) 404 not_found. #2(user/{name})와 동일한 근본 이슈(username alias, 진짜 식별자는 channelId) — tab suffix 유무는 무관.✅ router키=alias(소문자). canonical=channelId(forHandle/forUsername resolve)로 통일 — 분기 방지.
youtube.com/shorts/{videoId}videoIdokmedium샘플 미관측(observed=0)이라 직접 live-test 대상은 아니었으나, 코드상 classifyYoutube가 watch?v/youtu.be와 동일 경로(videoId 추출, 대소문자 보존)로 처리하도록 확인됨. watch?v·youtu.be 둘 다 live-test에서 oEmbed 성공이 실증됐으므로 동일 로직인 shorts도 유사 확률 높음 — 다만 shorts 자체는 직접 fetch 테스트하지 않아 확신도는 medium.✅ videoId=콘텐츠 키 확정.
youtube.com/live/{videoId}videoIdokmediumshorts와 동일 근거: 미관측(observed=0)이나 router가 watch?v과 동일 videoId 추출 경로로 분기. oEmbed가 videoId 기반으로 동작함이 이미 실증됐으므로 analogically 안전할 가능성 높지만 직접 테스트는 없어 medium.✅ videoId=콘텐츠 키 확정.
youtube.com/embed/{videoId}videoIdokmediumshorts/live와 동일 근거(코드 경로 동일, 직접 테스트는 없음) — medium confidence.✅ videoId=콘텐츠 키 확정.
youtube.com/v/{videoId}videoIdokmedium레거시 Flash embed 형태. 코드상 videoId 추출 경로는 동일하나 관측 0건 + 직접 테스트 없음 — medium confidence.✅ videoId=콘텐츠 키 확정.
youtube.com/c/{name}customName (소문자화)wrongmedium직접 테스트 대상은 아니었으나(observed=0), /user/{name}·/@{handle} 두 유사 alias 패턴이 이번 세션에서 모두 '진짜 식별자 아님(channelId 별도 존재)'으로 확인됨. /c/{name}도 동일하게 사용자가 설정하는 레거시 커스텀 URL이라 같은 alias 구조 — 동일 id-fit gap 예상, 다만 직접 검증은 안 해 medium.✅ router키=alias(소문자). canonical=channelId(forHandle/forUsername resolve)로 통일 — 분기 방지.
youtube.com/playlist?list={playlistId}playlistId (미확인)wronglow관측 0건 + classifyYoutube 로직 요약에 playlist 케이스 자체가 언급되지 않음 — 현재 router가 이 URL을 어떤 sourceType/sourceKey로 분류하는지조차 불확실(unknown 처리 또는 다른 youtube 케이스로 오분류 가능). id-probe/live-test 모두 미수행. sourceKey가 진짜 고유 식별자인지 판정할 근거 자체가 없어 '확인 안 됨' 상태로 wrong 처리(안전 쪽) — 코드 재확인이 판정 전제조건.✅ playlistId 키(youtube_playlist, fetch 보류).
커버리지 갭 — playlist URL(youtube.com/playlist?list=)이 최종 catch-all 분기로 떨어져 sourceKey가 실제 playlistId가 아닌 리터럴 'playlist' 문자열로 오염됨 — 코드 확인된 버그
커버리지 갭 — music.youtube.com, youtube-nocookie.com, gaming.youtube.com(레거시) 등 host가 classify() 의 host 매칭 목록(youtube.com/m.youtube.com/youtu.be)에 없어 전부 미분류(website/unknown)로 샘. 관측 표본엔 없으나 잠재 갭
커버리지 갭 — channel류(user/channel/c/@handle, 합계 observed 427)와 video류(watch/youtu.be, 합계 observed 246)가 동일 sourceType 'youtube' 로 묶여 있는데, YoutubeFetcher는 oEmbed만 호출하고 oEmbed는 video URL 전용이라 채널류는 다수 error/blocked 로 떨어질 가능성이 높음(fetcher 자체 주석이 이를 인정). 두 그룹의 fetch 성공률·데이터 품질을 구분 통계로 볼 수 없는 게 실질적 커버리지 갭

3. Fetch 테스트 + 획득 방법

3.1 획득 사다리 (공짜 → 유료)

방법비용설명트레이드오프conf
확정 ✅ (2026-07-21)사용자 선택 완료 — youtube_channel→official:youtube-data-api-v3 ($0 (10,000 quota units/day 무료 티어, channels.list 1 unit/call → 최대 1만 채널 조회/일)) · youtube_video→official:youtube-data-api-v3 ($0 (10,000 quota units/day 무료 티어, videos.list 1 unit/call)) adopted$0 (10,000 quota units/day 무료 티어, channels.list 1 unit/call → 최대 1만 채널 조회/일) / $0 (10,000 quota units/day 무료 티어, videos.list 1 unit/call)Phase A 후보 사다리에서 사람이 선택한 확정 source. §4 통합 필드 뷰는 이 source의 실제 스키마 기준.신규 채택 source는 현행 코드 미구현분이 §4에서 parseStatus=dropped(구현 필요)로 표시됨. 실제 코드 전환·스키마 검증은 반영 단계에서.high
1YouTube oEmbed (www.youtube.com/oembed?format=json&url=...) adopted (current implementation)free, no key, unauthenticated GETReturns title, author_name (channel display name), author_url, thumbnail_url, html embed. No numeric counters at all — no subscriber count, no view count, no channel/video creation date. Works for both video URLs and (undocumented, less reliable) channel-ish inputs but is really designed for videos.No statistics of any kind, so it cannot satisfy 'subscriber count', 'creation date', or 'view count' requirements. Endpoint is undocumented/unofficial for anything beyond basic oEmbed spec — no published SLA, no rate-limit doc, can change or throttle without notice. Fine as a free first-pass metadata source, dead end for the numeric fields this task actually needs.high — response shape confirmed by oEmbed spec + multiple independent third-party writeups (oembed.com, Google's own IFrame docs ecosystem); matches what the fetcher in this repo already observes (title/authorName/authorUrl only).
2YouTube Data API v3 (official, Google Cloud) gated — free tier but requires Google Cloud project + API key + quota budgeting$0 for the standard 10,000 quota units/day per GCP project (as of the Dec 2025 pricing simplification, YouTube Data API usage itself carries no request pricing — quota is the only constraint). channels.list and videos.list both cost 1 quota unit per call regardless of which 'part' fields are requested (snippet+statistics together still = 1 unit), so 10,000 units/day ≈ 10,000 channel or video lookups/day, comfortably covering a 'low-volume on-demand' backend.channels.list(part=snippet,statistics) returns subscriberCount, publishedAt (channel creation date), title. videos.list(part=snippet,statistics) returns title, channelTitle/channelId (author), publishedAt, viewCount. This is the only free source in the ladder that actually has the counters/dates required.Requires a Google Cloud project + API key (5-minute self-serve setup, no credit card, no manual review for read-only key usage under the default quota — review/audit only kicks in if you request a quota increase beyond 10k/day or use OAuth scopes, neither of which applies here). subscriberCount can be hidden by channel owners (returns as hidden/approximate in that case) — a real, documented data gap, not an integration bug. search.list is separately capped at 100 calls/day and costs 100 units/call, but this task doesn't need search — only channels.list/videos.list by known ID, both cheap.high on quota-cost figures — cross-checked against Google's own quota-cost documentation page directly (not just secondary blog posts). Medium-high on the 'no request pricing since Dec 2025' claim — that specific detail came from secondary SEO/blog sources (getphyllo, blotato, socialcrawl) rather than a Google changelog page fetched directly; treat as directionally right but verify against the live Google Cloud console pricing page before relying on it for a cost model, since these vendor-adjacent blogs have an incentive to overstate 'free-ness' to sell their own tooling around the API.
3Apify YouTube actors (e.g. streamers/youtube-scraper, apidojo/youtube-scraper) rejected/blocked for this use case — unauthorized-scraper tier, only relevant if official API becomes insufficientPay-per-result, priced per 1,000 items and varies a lot by actor: apidojo/youtube-scraper ≈ $0.50/1,000 videos; streamers/youtube-scraper ≈ $2.40/1,000 videos — on top of Apify platform subscription/compute-unit costs (free plan gives ~$5/month credit, cheapest paid plan $29/month). No per-channel pricing was found separately from per-video pricing in what I could verify.These scrape YouTube's web UI rather than calling the official API, so they can return fields the API also provides (subscriber count, view count, title, publish date) plus some the API doesn't cleanly expose (e.g. monetization status). Field completeness is inconsistent between actors — streamers/youtube-scraper's own listing claims subscriberCount + channelJoinedDate for channels; apidojo/youtube-scraper's listing does not clearly document subscriberCount/join-date in its schema even though it's a similarly-named product from a different publisher.medium — pricing figures came directly from each actor's own Apify store page (first-party for that vendor, but Apify actor marketplace listings are self-reported by third-party publishers, not Apify itself, so treat exact field lists as unverified marketing copy until tested against a live run). Given YouTube Data API v3 already covers 100% of the stated requirements (subscriber count, channel creation date, title, video title/author/publish date/view count) at zero request cost within a low-volume on-demand quota, there is no functional reason to pay for a scraper here — this tier is dominated by tier 2 and should only be revisited if daily volume were to exceed the ~10,000 units/day free quota, which is not the stated scenario (low-volume, on-demand).

3.2 공식 API 상세

확신도 high for quota mechanics (verified against Google's own quota-cost page); medium for the broader 'no cost at all' pricing narrative (secondary-source only) — subscriberCount can come back hidden/omitted for channels whose owner has disabled public subscriber count display — this is a real data gap in the official API itself, not a fetcher bug, and needs a null-handling path (e.g. fall back to null/unknown rather than treating it as fetch failure). Also: the widely-repeated 'API usage is now fully free / December 2025 pricing change' claim is sourced from secondary SEO blogs (getphyllo, blotato, socialcrawl) in this research pass, not a primary Google announcement I fetched directly — recommend a direct check of Google's official YouTube API terms/pricing page before finalizing any cost writeup, since these are exactly the kind of vendor-adjacent sources that tend to editorialize 'free' claims to drive their own SEO/affiliate traffic.
단계내용conf
Create/select a Google Cloud projectconsole.cloud.google.com → New Project. No billing account required for this quota tier.high
Enable 'YouTube Data API v3'APIs & Services → Library → search 'YouTube Data API v3' → Enable.high
Create an API keyAPIs & Services → Credentials → Create Credentials → API Key. Optionally restrict the key to the YouTube Data API and to your server's IP for security. No OAuth/user-consent flow needed since channel/video public statistics are read with a plain API key, not user-authorized data.high
Call channels.list and videos.listchannels.list(part=snippet,statistics,id={channelId}) → title, publishedAt (creation date), subscriberCount. videos.list(part=snippet,statistics,id={videoId}) → title, channelTitle/channelId, publishedAt, viewCount. Each call = 1 quota unit regardless of how many parts/fields requested in that single call.high
Budget quotaDefault 10,000 units/day per project resets at midnight Pacific Time. At 1 unit/call for channels.list+videos.list, that's up to 10,000 lookups/day — request a quota increase only if this low-volume on-demand use ever grows past that (requires a compliance/use-case audit through Google's quota extension form).high

3.3 라이브 테스트 실측

대상방법과금결과비고
https://www.youtube.com/watch?v=rkJAgXQYWAI (template youtube.com/{value}, sourceKey=rkJAgXQYWAI)GET youtube.com/oembed?format=json&url=...무료success (200)Matches existing implementation exactly: title/authorName/authorUrl/thumbnailUrl all present. oEmbed additionally returns type/height/width/html/provider_name/provider_url/version which the fetcher currently discards.
https://youtu.be/audhiM3Ur1I (template youtu.be/{value}, sourceKey=audhiM3Ur1I)GET youtube.com/oembed?format=json&url=...무료success (200)youtu.be short-link resolves in oEmbed exactly like the watch?v= form; same field set returned. Confirms videoId key is directly usable without needing to expand the short link first for the free path.
https://www.youtube.com/channel/UCbrPqq29C9Q_TQP7OFFRzcw (template youtube.com/{value}/{value}, sourceKey=UCbrPqq29C9Q_TQP7OFFRzcw)GET youtube.com/oembed?format=json&url=...무료not_found (404, body 'Not Found')Confirms current implementation's status:'not_found' for channel-id URLs — oEmbed genuinely does not support channel-shaped inputs, this is not a fetcher bug.
https://www.youtube.com/user/DaveThomasFoundation (template youtube.com/{value}/{value}, sourceKey=davethomasfoundation)GET youtube.com/oembed?format=json&url=...무료not_found (404, body 'Not Found')Same as channel/{id}: legacy /user/{name} form unsupported by oEmbed.
https://www.youtube.com/@dexerto (template youtube.com/@{handle}, sourceKey=dexerto)GET youtube.com/oembed?format=json&url=...무료not_found (404, body 'Not Found')@handle form also unsupported by oEmbed (confirms not_found for the whole 104-URL @{handle} bucket).
https://www.youtube.com/@techbullion/videos (template youtube.com/@{handle}/{value}, sourceKey=techbullion)GET youtube.com/oembed?format=json&url=...무료not_found (404, body 'Not Found')/@handle/videos tab suffix also 404s — same as bare @handle.
https://www.youtube.com/user/kabosumama/videos (template youtube.com/{value}/{value}/videos, sourceKey=kabosumama)GET youtube.com/oembed?format=json&url=...무료not_found (404, body 'Not Found')Legacy /user/{name}/videos tab also 404s.
https://youtu.be/audhiM3Ur1Icurl -I (HEAD, follow redirect chain)무료303 See Other -> location: https://www.youtube.com/watch?v=audhiM3Ur1I&feature=youtu.beid-probe: youtu.be short link HTTP-redirects to watch?v=<same id>. Canonical videoId == sourceKey extracted by router; no id-fit mismatch for this template.
https://www.youtube.com/@dexertocurl -sIL (HEAD, follow redirects)무료200 OK, no Location header (no HTTP redirect)id-probe: @handle URLs resolve directly at HTTP layer — YouTube does NOT redirect /@handle to a /channel/{UC...} canonical URL via HTTP. The true channelId (UCWWCHrEqzME9N6JTuL6YUaw for dexerto) is only obtainable by parsing the rendered page or calling an API/scraper (confirmed via the Apify test below), not via curl -I alone.
https://www.youtube.com/channel/UCbrPqq29C9Q_TQP7OFFRzcwcurl -sIL (HEAD, follow redirects)무료200 OK, no Location headerid-probe: /channel/{id} already is the canonical form and correctly serves 200 without redirecting further.
https://www.youtube.com/user/DaveThomasFoundationcurl -sIL (HEAD, follow redirects)무료200 OK, no Location headerid-probe: legacy /user/{name} form also resolves 200 with no HTTP redirect to a canonical /channel/{UC...} id — same limitation as @handle; canonical channelId not recoverable via curl -I.
channels.list / videos.list (part=snippet,statistics)YouTube Data API v3무료not called — 키 없음(미검증)grep for YOUTUBE_API_KEY / GOOGLE_API_KEY / YT_API in .env.dev and .env.prod found no matching variable (only HELIUS_*, ALCHEMY_*, BINANCE_API_KEY, EXTERNAL_GEMINI_API_KEY, TWITTER_API_KEY, APIFY_TOKEN, URLSCAN_API_KEY are present). Per instructions, no live call attempted; subscriberCount/publishedAt/viewCount fields for this route remain estimates from acquisition research only, not verified in this session.
https://www.youtube.com/@dexerto (startUrls, maxResults=1, maxResultsShorts=0, maxResultStreams=0)Apify actor streamers/youtube-scraper (run-sync-get-dataset-items)success — 1 dataset item returned, run cost $0.004 (actor-run usageTotalUsd)Confirms acquisition's rank-3 pick works and actually returns the numeric fields oEmbed/legacy sources lack: numberOfSubscribers=880000, channelJoinedDate='Jan 5, 2021', channelTotalVideos=4425, channelTotalViews present, channelId=UCWWCHrEqzME9N6JTuL6YUaw (this IS the canonical id that @dexerto silently resolves to — confirms the id-fit gap noted above). Also returns per-video fields (title, viewCount, likes, commentsCount, duration, date, hashtags, isMonetized, isAgeRestricted) bundled in the same item since the actor is video-centric, not a dedicated channel-only endpoint.

https://www.youtube.com/watch?v=rkJAgXQYWAI (template youtube.com/{value}, sourceKey=rkJAgXQYWAI) 실측 응답 원문:

{"title":"This Is My Room — Marisol 60 | Dave Thomas Foundation for Adoption","author_name":"DaveThomasFoundation","author_url":"https://www.youtube.com/@DaveThomasFoundation","type":"video","height":113,"width":200,"version":"1.0","provider_name":"YouTube","provider_url":"https://www.youtube.com/","thumbnail_height":360,"thumbnail_width":480,"thumbnail_url":"https://i.ytimg.com/vi/rkJAgXQYWAI/hqdefault.jpg","html":"<iframe ... src=\"https://www.youtube.com/embed/rkJAgXQYWAI?feature=oembed\" ...></iframe>"}

https://youtu.be/audhiM3Ur1I (template youtu.be/{value}, sourceKey=audhiM3Ur1I) 실측 응답 원문:

{"title":"ONE PIECE HEROINES | Official Trailer","author_name":"ONE PIECE Official - ENG","author_url":"https://www.youtube.com/@OnePieceOfficialENG","thumbnail_url":"https://i.ytimg.com/vi/audhiM3Ur1I/hqdefault.jpg", ...}

https://www.youtube.com/@dexerto (startUrls, maxResults=1, maxResultsShorts=0, maxResultStreams=0) 실측 응답 원문:

{"title":"Danny Gonzalez Breaks Down His YouTube Watch History","channelName":"Dexerto","channelUrl":"https://www.youtube.com/channel/UCWWCHrEqzME9N6JTuL6YUaw","channelId":"UCWWCHrEqzME9N6JTuL6YUaw","numberOfSubscribers":880000,"channelTotalVideos":4425,"channelJoinedDate":"Jan 5, 2021","viewCount":39730,"date":"2026-07-16T16:00:14.000Z","isChannelVerified":true, ...}
oEmbed 결과는 기존 구현·샘플 fetched 데이터와 100% 일치 — 6개 템플릿 중 video형(watch?v=, youtu.be/{id}) 2개만 ok, 나머지 channel/user/@handle 계열 4개 템플릿은 전부 404 not_found로 라이브 재현됨. 구현 버그 아님, oEmbed 스펙상 video 전용 한계.
YouTube Data API v3 키가 .env.dev/.env.prod 어디에도 없음(YOUTUBE_API_KEY, GOOGLE_API_KEY, YT_API 패턴으로 grep, 매치 없음). acquisition 문서가 제시한 subscriberCount/publishedAt/viewCount 무료 경로는 이번 세션에서 미검증 상태로 남음 — 키 발급 전까지 '추정'으로만 취급해야 함.
id-probe 결과 youtu.be/{id}만 HTTP 303으로 watch?v={id} canonical로 리다이렉트됨(무료 확인). @handle, /channel/{id}, /user/{name}은 curl -I에서 전부 200을 반환하고 Location 헤더가 없음 — 즉 @handle→UC... 실제 channelId 매핑은 HTTP 리다이렉트로는 절대 얻을 수 없고, 페이지 렌더링/API/스크래퍼로만 확인 가능함(Apify 테스트로 dexerto의 실제 channelId=UCWWCHrEqzME9N6JTuL6YUaw 확인됨).
Apify streamers/youtube-scraper 1회 호출($0.004)로 acquisition의 rank-3 주장(구독자수+channelJoinedDate 제공)이 실측 확인됨. 단, 이 actor는 video-centric이라 채널 단독 조회가 아니라 최소 1개 video item에 채널 필드가 동봉되는 구조 — 순수 채널 통계만 필요해도 최소 1 video의 크레딧이 소모됨.

4. 객체별 필드 통합 (변동성 · 파싱상태 · 트레이딩 유용성) 👤 판단 반영

Creator/Content 객체별로 필드를 한 표에 통합. 값 변동성 = 값이 시간에 따라 변하나(immutable 역사적 사실 / mutable 드리프트 / derivable 계산값) → look-ahead 안전성이 여기서 도출(immutable=safe · mutable=conditional/as-of · derivable=unsafe). 파싱 상태 = 현재 파이프라인이 실제로 저장하나.

4.1 · Creator (채널) — youtube_channel

source: official:youtube-data-api-v3 (channels.list, part=snippet,statistics,contentDetails) · subscriberCount/videoCount/viewCount는 조회 시점 스냅샷(mutable) — as-of 시점 고정 필요, look-ahead 시 진입 이후 값 섞임 주의. publishedAt(채널 개설일)만 immutable이라 진입시점 무관하게 안전하게 재사용 가능. 현재 코드는 v3 API 자체를 호출하지 않음(oEmbed는 채널 URL에서 전부 404) — 아래 필드 전부 신규 구현 필요.
필드설명값 변동성파싱 상태(현재)최종 결정트레이딩 유용성
channelId채널 고유 불변 ID (UC로 시작 24자). channels.list id 또는 snippet.channelId. 예: UCWWCHrEqzME9N6JTuL6YUawimmutable⚠️ actor제공·미파싱🔑 keyactionable — @handle/username은 mutable alias라 dedup/사칭탐지의 진짜 join key는 channelId뿐(현재 라우터는 handle을 key로 써서 alias 불일치 시 동일계정 오분리 위험)
snippet.title채널 표시명. 예: 'Dexerto'. 소유자가 언제든 변경 가능mutable⚠️ actor제공·미파싱keepcontext — 브랜드/사칭 텍스트 매칭(sameHandle 판별)의 identity 키, 값 자체가 매매 트리거는 아님
snippet.customUrl채널 @handle(정규화된 커스텀 URL). 예: '@dexerto'. 소유자가 변경 가능한 mutable aliasmutable⚠️ actor제공·미파싱removecontext — URL 원본과 대사(reconciliation)해 카피캣/핸들변경 추적용, 단독으론 noise에 가까움
snippet.publishedAt채널 개설일(ISO8601). 예: '2021-01-05T00:00:00Z'. 한번 정해지면 불변immutable⚠️ actor제공·미파싱keepactionable — 계정 연령은 러그/사칭 판별 핵심 tell(신생 계정=고위험), look-ahead 안전(즉시 확정값)
snippet.description채널 소개/바이오 텍스트. 소유자가 수시 편집 가능mutable⚠️ actor제공·미파싱keepcontext — 팀 진정성 확인용 보조 텍스트, 단독 매매 트리거는 아님
snippet.thumbnails.default.url채널 프로필 이미지 CDN URL. 채널 아이콘 변경 시 값 변경mutable⚠️ actor제공·미파싱removenoise — 이미지 자체는 파이프라인에서 분석 안 함, channelId로 언제든 재구성 가능한 파생 성격
snippet.country채널이 설정한 국가 코드(선택 입력이라 비어있는 채널 다수)mutable⚠️ actor제공·미파싱removenoise — 러그/모멘텀/팀진정성/타이밍 어느 축과도 무관
statistics.subscriberCount구독자 수. 채널 소유자가 공개를 끄면 hiddenSubscriberCount=true와 함께 값이 생략됨(정상 데이터 갭, API 결함 아님)mutable⚠️ actor제공·미파싱keepactionable — 도달 규모(viability) 판단축, 단 절대 도달만 예측하고 진입상대 수익은 별개(project_af_viability_vs_entry_relative와 동일 구조)
statistics.hiddenSubscriberCount구독자 수 비공개 여부 boolean 플래그mutable⚠️ actor제공·미파싱skipnoise — subscriberCount 결측 원인 설명용 메타플래그일 뿐 그 자체로 매매 신호 아님
statistics.videoCount채널 누적 업로드 영상 수mutable⚠️ actor제공·미파싱keepcontext — 채널 활성도/신생 여부 보조 지표, publishedAt만큼 강한 tell은 아님
statistics.viewCount채널 전체 영상 누적 조회수 합mutable⚠️ actor제공·미파싱keepcontext — 채널 규모감(브랜드 파워) 정도, 개별 홍보 영상의 즉시 확산력과는 별개
verified badge (checkmark)YouTube 공식 인증 배지 — v3 API의 snippet/statistics/contentDetails part 어디에도 공개 필드로 노출되지 않음(구독자수 임계치 기반 자동 부여이나 API 응답에 boolean으로 없음)mutable❌ 없음noise — 애초에 구독자수 임계치로 자동 부여되는 배지라 별도 구매 불가하지만, API로 확보 자체가 불가해 활용 불가
channelAgeDays (파생)publishedAt 기준 경과일수 — 우리가 raw publishedAt 저장 후 계산해야 하는 파생값derivable❌ 없음skipactionable이나 look-ahead unsafe — 조회 시점마다 값이 달라지므로 raw publishedAt만 저장하고 진입 as-of 시점 기준으로 재계산 필수

4.2 · Content (영상) — youtube_video

source: official:youtube-data-api-v3 (videos.list, part=snippet,statistics,contentDetails) · viewCount/likeCount/commentCount는 조회 시점 스냅샷(mutable, 영상 공개 이후 계속 누적) — 진입시점 as-of 값과 이후 값 혼동 주의(look-ahead). publishedAt(업로드일)·videoId는 immutable이라 즉시 확정. 현재 코드는 무료 oEmbed로 title/author_name/author_url/thumbnail_url만 받고 통계(view/like/comment)·publishedAt·channelId는 전혀 파싱 안 함 — v3 신규 구현 필요.
필드설명값 변동성파싱 상태(현재)최종 결정트레이딩 유용성
videoId영상 고유 불변 ID(11자). 라우터가 URL에서 이미 원형 보존해 sourceKey로 추출 중(watch?v=, youtu.be 둘 다 live-test로 canonical 일치 확인됨)immutable✅ 캡처🔑 keyactionable — 콘텐츠 재사용/카피캣 탐지의 진짜 dedup 키(project_content_reuse_direction과 동일 축)
snippet.publishedAt영상 업로드 일시(ISO8601). 한번 게시되면 불변immutable⚠️ actor제공·미파싱keepactionable — 콘텐츠 신선도(신규 vs 재탕) 판별, look-ahead 안전. 현재 oEmbed는 이 필드를 아예 안 줌
snippet.channelId업로드한 채널의 불변 ID — Creator 객체의 channelId와 동일 값, join keyimmutable⚠️ actor제공·미파싱🔑 keyactionable — 사칭/동일계정 재사용 판별의 join key. 현재 oEmbed의 author_url(@handle)은 mutable alias라 대체 불가
snippet.channelTitle업로드한 채널의 표시명(스냅샷). oEmbed의 author_name과 개념적으로 동일하나 소스가 다름mutable⚠️ actor제공·미파싱keepcontext — 현재 oEmbed author_name으로 유사값이 이미 잡히므로 우선순위는 낮음, v3 전환 시 재확인 필요
snippet.title영상 제목. 게시 후에도 소유자가 편집 가능(드뭄)mutable✅ 캡처keepactionable/context — 현재 oEmbed로 이미 확보 중, 브랜드사칭·키워드 매칭용 콘텐츠 텍스트
snippet.description영상 설명란 전문 — 프로모션 링크, CA(컨트랙트 주소), 디스코드/텔레그램 초대링크가 흔히 포함됨mutable⚠️ actor제공·미파싱keepactionable — 컨트랙트 주소/외부링크 직접 노출 가능성 있어 현재 oEmbed 커버리지 공백 중 가장 아까운 필드
snippet.tags영상에 붙은 검색 태그 배열mutable⚠️ actor제공·미파싱skipnoise — SEO용 키워드라 트레이딩 판단축과 직접 연결 약함
snippet.categoryIdYouTube 카테고리 ID(예: 25=News&Politics). 게시 후 변경 가능하나 드뭄mutable⚠️ actor제공·미파싱removenoise — 카테고리 분류 자체가 밈코인 알파 신호와 무관
statistics.viewCount영상 조회수. 실시간 누적 증가mutable⚠️ actor제공·미파싱keepactionable — 확산 속도/모멘텀의 핵심 지표. raw count만 저장하고 velocity는 시점별 재수집으로 파생해야 look-ahead 안전
statistics.likeCount좋아요 수. 채널 소유자가 공개를 끄면 필드 자체가 결측(정상 데이터 갭)mutable⚠️ actor제공·미파싱keepactionable — 참여율(진짜 시청 vs 조회수 어뷰징) 판별 보조, 결측을 fetch 실패와 구분 처리 필요
statistics.commentCount댓글 수. 댓글 기능 꺼진 영상은 결측mutable⚠️ actor제공·미파싱keepcontext — 커뮤니티 반응 열기 정도, 텍스트 내용(러그 경고 등)은 별도 크롤링 없이는 못 봄
statistics.favoriteCount즐겨찾기 수 — YouTube가 2016년경 기능 자체를 제거해 API는 항상 0 반환(deprecated 필드, 값은 여전히 응답에 포함)immutable⚠️ actor제공·미파싱removenoise — 값이 항상 0으로 고정돼 정보량 자체가 없음
statistics.dislikeCount싫어요 수 — 2021년 이후 공개 API 응답에서 완전히 제거됨(어떤 파라미터로도 조회 불가)mutable❌ 없음noise — 애초에 어떤 source도 안 주는 필드, 활용 불가
contentDetails.duration영상 길이(ISO8601 duration, 예: 'PT4M13S'). 게시 후 불변immutable⚠️ actor제공·미파싱skipnoise — 콘텐츠 길이 자체는 러그리스크·모멘텀·팀진정성·타이밍 어느 축과도 직접 연결 안 됨
engagementRate (파생: likeCount/viewCount)우리가 raw like/view로 계산하는 파생값derivable❌ 없음skipactionable이나 look-ahead unsafe — raw count 저장 후 as-of 시점 기준 재계산 필수, 캐시된 파생값 그대로 쓰면 미래 데이터 유입 위험
ageHours (파생: now - publishedAt)조회 시점 기준 경과 시간derivable❌ 없음skipcontext, look-ahead unsafe — 신선도 판단에 쓰이나 raw publishedAt만 저장하고 매 시점 재계산 필요

4-부록. actor raw 응답 스키마 (ground truth)

위 통합 표의 원천 — actor가 실제 반환하는 원본 필드 전체.

dataType: "youtube.oembed (video only — free, current impl)"

필드예시값설명
titleThis Is My Room — Marisol 60 | Dave Thomas Foundation for Adoptionvideo title
author_nameDaveThomasFoundationchannel display name (mapped to authorName)
author_urlhttps://www.youtube.com/@DaveThomasFoundationcanonical @handle URL of the channel (mapped to authorUrl)
thumbnail_urlhttps://i.ytimg.com/vi/rkJAgXQYWAI/hqdefault.jpgvideo thumbnail (mapped to thumbnailUrl)
typevideoalways 'video' — unused by current fetcher, but useful as a sanity check
provider_name / provider_urlYouTubealways 'YouTube' / youtube.com — constant, no value
html<iframe ... src="https://www.youtube.com/embed/rkJAgXQYWAI?feature=oembed" ...>embed iframe markup — unused, contains embed URL with videoId
height / width / thumbnail_height / thumbnail_width / version113 / 200 / 360 / 480 / 1.0layout metadata, constant per request — no analytical value

dataType: "youtube.apify.streamers-youtube-scraper (paid, video+channel bundle)"

필드예시값설명
channelIdUCWWCHrEqzME9N6JTuL6YUawcanonical UC... channel id — resolves the @handle/legacy-user id-fit gap that oEmbed/redirect cannot
channelName / channelUsername / channelUrlDexerto / dexerto / https://www.youtube.com/channel/UCWWCHrEqzME9N6JTuL6YUawdisplay name / @handle / canonical channel URL
numberOfSubscribers880000subscriber count — the field free sources cannot provide at all
channelJoinedDateJan 5, 2021channel creation date, human-formatted string (not ISO)
channelTotalVideos / channelTotalViews4425 / (present, large int)lifetime channel counters
channelDescription / channelAvatarUrl / channelBannerUrl / channelLocation-channel profile metadata
isChannelVerifiedtrueboolean verified badge flag
title / url / idDanny Gonzalez Breaks Down His YouTube Watch Historyscraped video's title / URL / videoId (item is video-scoped, channel fields are attached per-video)
viewCount / likes / commentsCount / duration / date39730 / - / - / - / 2026-07-16T16:00:14.000Zper-video stats and publish date (ISO)
isMonetized / isAgeRestricted / isMembersOnly / hashtagsfalse / false / false / []per-video flags/metadata not exposed by oEmbed or Data API snippet/statistics parts

다음 액션