지역(KR,JP,US) 인기 Shorts 주기 검색 → 작은구독자·고배율 떡상 채널 발굴 → RecommendedChannel 저장 → /recommend 페이지에서 등록/제외. 쿼터 가드·기존 검색 재사용. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
126 lines
6.9 KiB
Markdown
126 lines
6.9 KiB
Markdown
# 추천 채널 발굴 (떡상 Shorts 기반) 설계
|
||
|
||
- 날짜: 2026-06-25
|
||
- 상태: 설계 확정(구현 대기)
|
||
|
||
## 1. 목적
|
||
|
||
사이트가 **주기적으로(일 1회) 지역별 인기 Shorts 를 광범위 검색**해, "구독자는 적은데 조회수가
|
||
폭발한"(떡상) 채널을 자동 발굴하고, **상위 N개를 추천 채널로 모아** 사용자가 `/recommend` 페이지에서
|
||
접속해 보고, 마음에 들면 내 채널로 등록하거나 제외할 수 있게 한다.
|
||
|
||
기존 `/discover` 발굴은 이미 수집된 **영상** 중 떡상 후보를 찾는 것이고, 이 기능은 새로운 **채널**을
|
||
능동적으로 발굴해 추천하는 별도 기능이다.
|
||
|
||
## 2. 배경 / 재사용 대상
|
||
|
||
- `service/YoutubeSearchService.searchYoutubeVideos(YoutubeSearchCondition)`: 지역(regionCode) 단위로
|
||
YouTube search.list 검색 + 각 영상의 채널 구독자/메타 조회. 결과에 channelId·channelTitle·구독자·조회수 포함.
|
||
→ **광범위 Shorts 검색에 재사용**(type=video, videoDuration=short, order=viewCount, regionCode 별).
|
||
- `global/schedule/YoutubeQuotaGuard`: 일일 쿼터 예산 가드. → 발굴도 이 가드 안에서 동작.
|
||
- `global/schedule/ScheduledCollectionService`: 기존 일별 스케줄. → 발굴 작업을 여기에 추가(또는 병행).
|
||
- `domain/channel/Channel` + `ChannelService.saveChannelFromUrl`/채널 저장: "내 채널 등록" 시 재사용.
|
||
- 배율(조회수÷구독자) 개념: 기존 `viewsPerSubRatio` 와 동일 기준.
|
||
|
||
## 3. 데이터 흐름
|
||
|
||
```
|
||
[스케줄러 일1회 (cron 설정)] enabled & 쿼터 여유?
|
||
└─ regions(KR,JP,US) 각각:
|
||
YoutubeSearchService 로 인기 Shorts 검색(order=viewCount, short, pages 설정)
|
||
→ 영상별 채널 구독자 → 배율 계산
|
||
▼
|
||
채널 단위 집계: 채널별 '최고 배율 영상' 1건 선정
|
||
▼
|
||
필터: 구독자 ≤ maxSubscribers AND 배율 ≥ minRatio
|
||
AND 이미 등록된 채널(Channel) 아님
|
||
AND RecommendedChannel.status = EXCLUDED 아님
|
||
▼
|
||
RecommendedChannel upsert(channelId 기준): 메타(대표영상·배율·구독자·발견일) 저장/갱신
|
||
▼
|
||
[/recommend 페이지] status=NEW 를 배율 desc 정렬, 상위 N 표시
|
||
카드: 썸네일·채널명·구독자·대표 떡상영상(제목·조회수·배율)·샘플 링크
|
||
액션: [내 채널 등록] → Channel 생성 + status=REGISTERED
|
||
[제외] → status=EXCLUDED (다음 발굴에서 제외)
|
||
```
|
||
|
||
## 4. 컴포넌트
|
||
|
||
### 4.1 `RecommendedChannel` 엔티티 + `RecommendedChannelRepository` (신규, `domain/channel`)
|
||
- 필드: `id`, `channelId`(unique), `channelTitle`, `thumbnailUrl`, `subscriberCount`,
|
||
`status`(NEW|REGISTERED|EXCLUDED), 대표영상(`topVideoId`,`topVideoTitle`,`topVideoViewCount`,`ratio`),
|
||
`region`(발견 지역), `discoveredAt`, `updatedAt`(@CreationTimestamp/@UpdateTimestamp).
|
||
- Repo: `findByChannelId`, `findByStatusOrderByRatioDesc`, 존재여부 조회 등.
|
||
|
||
### 4.2 `ChannelDiscoveryService` (신규, `domain/channel` 또는 `service`)
|
||
- **검색 API 선택(구현 시 확정)**: 키워드 없는 "광범위 인기 Shorts"는 search.list(q 필요, 100units)보다
|
||
**videos.list `chart=mostPopular` + `regionCode`(1 unit, 키워드 불필요)** 가 적합하다. mostPopular 결과를
|
||
duration ≤ 60s(Shorts)로 필터링해 후보로 삼는다. 단 mostPopular 는 카테고리 제한이 있을 수 있어,
|
||
결과가 빈약하면 search.list(`order=viewCount`, `videoDuration=short`, 일반 q 또는 카테고리)로 보강한다.
|
||
→ `YoutubeSearchService` 에 region 기반 인기영상 조회 메서드를 추가하거나 신규 호출 경로를 둔다.
|
||
- `Map<String,Object> runDiscovery()`:
|
||
1. regions 별 인기 Shorts 조회(쿼터 가드로 지역마다 잔여 확인 후 진행).
|
||
2. 결과 영상 → 채널별 최고 배율 집계(`Map<channelId, Candidate>`).
|
||
3. 필터(구독자 상한·배율 하한·등록됨 제외·EXCLUDED 제외).
|
||
4. `RecommendedChannel` upsert(있으면 더 좋은 메트릭으로 갱신, status 가 NEW 일 때만).
|
||
5. 요약 반환(검색 지역수, 후보수, 신규 저장수, 소비 쿼터).
|
||
- 쿼터 부족 시 해당 지역 건너뛰고 로그(기존 패턴).
|
||
|
||
### 4.3 스케줄 연결
|
||
- `ScheduledCollectionService` 에 `@Scheduled(cron=${hlab.scheduler.channel-discovery.cron})` 추가 →
|
||
`channelDiscoveryService.runDiscovery()`. enabled 플래그로 on/off. 수동 실행용 메서드도 제공.
|
||
|
||
### 4.4 컨트롤러 (`RecommendedChannelController`, 신규)
|
||
- `GET /api/recommended-channels?limit=` → status=NEW, 배율 desc 상위 목록(ApiResponse).
|
||
- `POST /api/recommended-channels/{id}/register` → 내 채널 등록(Channel 생성) + status=REGISTERED.
|
||
- `POST /api/recommended-channels/{id}/exclude` → status=EXCLUDED.
|
||
- `POST /api/recommended-channels/run` → 수동 발굴 트리거(선택, 관리용).
|
||
|
||
### 4.5 웹 페이지 `/recommend` (신규 `recommend.html` + `WebController` 라우트)
|
||
- 사이드바에 "추천 채널" 메뉴 추가(`currentPage="recommend"`).
|
||
- 추천 채널 카드 그리드: 썸네일·채널명·구독자·대표 떡상영상(배율 배지)·YouTube 링크.
|
||
- 각 카드 [내 채널 등록]/[제외] 버튼 → 위 API 호출 후 목록 갱신.
|
||
- 빈 상태/로딩/사용법 안내(기존 페이지 톤 따름).
|
||
|
||
## 5. 설정 (`application.yml`, 기본값 有)
|
||
|
||
```yaml
|
||
hlab:
|
||
scheduler:
|
||
channel-discovery:
|
||
enabled: ${CHANNEL_DISCOVERY_ENABLED:true}
|
||
cron: ${CHANNEL_DISCOVERY_CRON:0 30 4 * * *} # 매일 04:30 (기존 수집 04:00 이후)
|
||
discovery:
|
||
regions: ${DISCOVERY_REGIONS:KR,JP,US}
|
||
max-subscribers: ${DISCOVERY_MAX_SUBS:100000} # 작은 채널 상한
|
||
min-ratio: ${DISCOVERY_MIN_RATIO:5.0} # 떡상 배율 하한
|
||
pages-per-region: ${DISCOVERY_PAGES:1} # 지역당 검색 페이지 수
|
||
top-n: ${DISCOVERY_TOP_N:30} # 추천 페이지 노출 상한
|
||
```
|
||
|
||
## 6. 쿼터/성능
|
||
|
||
- search.list = 지역·페이지당 약 100 units, channels.list(구독자) = 50채널당 1 unit.
|
||
KR,JP,US × 1페이지 ≈ 300 units + 수십 units → 일일 한도(기본 10000) 내 충분.
|
||
- `YoutubeQuotaGuard.remaining()` 확인 후 지역 단위로 진행/건너뜀.
|
||
|
||
## 7. 에러 처리
|
||
|
||
- 특정 지역 검색 실패 → 그 지역만 건너뛰고 나머지 진행(로그).
|
||
- 채널 등록 실패(이미 존재 등) → 사용자에게 메시지, 추천 항목은 REGISTERED 로 정리.
|
||
- 쿼터 소진 → 발굴 중단, 다음 주기 재시도.
|
||
|
||
## 8. 테스트 (`src/test`)
|
||
|
||
순수 로직 위주:
|
||
- 집계/랭킹: 영상 목록 → 채널별 최고 배율 선정, 구독자 상한·배율 하한 필터.
|
||
- dedup: 등록된 channelId·EXCLUDED 제외.
|
||
- 실제 YouTube/스케줄은 라이브/수동 검증.
|
||
|
||
## 9. 범위 제외 (YAGNI)
|
||
|
||
- 구독자 증가율 추적(스냅샷 누적 필요)
|
||
- 키워드 기반 검색(이번엔 지역 광범위)
|
||
- 자동 등록(수동 검토 유지)
|
||
- 추천 사유 다중 지표(이번엔 배율 단일 기준)
|