Skip to content

feat(raw_posts): WhatsOnTheStar adapter — celebrity outfit ground-truth source #465

@cocoyoon

Description

@cocoyoon

Summary

SourceAdapter 인터페이스에 WhatsOnTheStarAdapter(platform=whatsonthestar)를 추가한다. Pinterest/Instagram과 달리 이 소스는 이미 셀럽-아이템(브랜드/상품명/가격)이 구조화 라벨링된 데이터라서, vision auto-tag를 우회하거나 ground-truth로 활용할 수 있다.

cf. #214 PinterestAdapter / #259 InstagramAdapter — 같은 패턴으로 등록.

사이트 분석

  • URL: https://whatsonthestar.com
  • 타입: 셀럽 outfit 식별 + 쇼핑 링크 (Lil Yachty, Lil Baby 등 힙합/연예인 중심)
  • 렌더링: Nuxt.js SSR — 모든 페이지에 `window.NUXT=(function(...){...})` 형태로 데이터 임베드
  • 공식 API: 프론트엔드용 공개 엔드포인트는 노출되지 않음 (`api.whatsonthestar.com`/`/api/*` 모두 404). iOS 앱(id=1534214433) 전용 API는 별도 분석 필요
  • robots.txt: `Disallow: //go/` 만 — affiliate redirect 경로만 차단, 콘텐츠 크롤 허용
  • sitemap: `https://cdn.whatsonthestar.com/sitemaps/sitemap.xml\` (lastmod 2020 — 거의 갱신 안 됨, 페이지 인벤토리 부정확)

데이터 구조 (포스트 단위)

각 outfit 포스트에 다음 필드:

  • celebrity: 이름 + 프로필 slug
  • date worn: 착용 날짜
  • outfit total: 전체 합계
  • items[]: `{brand, product_title, price, sale_price, retailer_url(affiliate)}`
  • photo URL

스크랩 전략 (제안)

  1. 1순위: `window.NUXT` 페이로드 파싱 — Nuxt가 SSR로 직렬화해주는 JSON에서 outfit/items 추출. 헤드리스 없이 가능 (regex/AST로 추출 후 `eval(function(...))` 평가). `api-server`의 기존 `httpx` + `pyminify` 또는 `mini-racer` 한정 평가
  2. fallback: BeautifulSoup으로 outfit 카드 / item 리스트 DOM 파싱
  3. discovery:
    • 셀럽 인덱스(`/celebrities/`) → 셀럽 별 페이지(`/celebrities/{slug}`) → outfit 페이지
    • 또는 `/recent` / `/popular` 류 글로벌 피드(확인 필요)
  4. rate limit: 1 req/sec 보수적, exponential backoff

RawMedia 매핑

```python
RawMedia(
external_id=outfit_id, # 사이트 내부 ID
external_url="https://whatsonthestar.com/outfits/{slug}",
image_url=outfit_photo_url,
caption=f"{celebrity} on {date_worn}",
author_name=celebrity_name,
platform_metadata={
"celebrity_slug": ...,
"date_worn": ...,
"outfit_total": ...,
"items": [
{"brand": ..., "title": ..., "price": ..., "sale_price": ..., "retailer_url": ...}
],
},
)
```

Ground-truth 활용 (#348 processor 연계)

기존 vision pipeline은 사진에서 아이템을 detect하지만, 이 소스는 이미 라벨이 있다. 옵션:

  • A. 우회: `parse_status='parsed'`로 직접 INSERT, `parse_result`에 라벨 그대로 저장
  • B. 검증용: vision 돌리고 사이트 라벨과 비교 → recall/precision 측정 데이터로 활용
  • C. 하이브리드: bbox는 vision, brand/title은 site 라벨

Phase 1은 A 권장. B는 별도 evaluation 잡으로 분리.

Scope (this issue)

  • `packages/ai-server/src/services/raw_posts/adapters/whatsonthestar.py` 신설
  • `SourceAdapter` Protocol 구현 — `platform="whatsonthestar"`
  • `source_type`: `outfit`(단일 포스트), `celebrity`(셀럽 페이지 페이지네이션)
  • Nuxt 페이로드 추출기 (재사용 가능하게 helper 분리)
  • `build_default_adapters` 등록 (`adapters/init.py`)
  • 단위 테스트 (`tests/unit/services/raw_posts/test_whatsonthestar_adapter.py`) — fixture HTML, ≥3 outfit 케이스
  • env vars: `WHATSONTHESTAR_INITIAL_LIMIT` / `WHATSONTHESTAR_INCREMENTAL_LIMIT`
  • 어댑터 docstring + README 1-pager

Out of scope

Acceptance

  • 셀럽 페이지 1개로 `fetch()` → `List[RawMedia]` 반환 (≥10 items 페이지에서)
  • 아이템 메타(brand/title/price)가 `platform_metadata.items`에 보존
  • 테스트 그린, lint 통과

Metadata

Metadata

Assignees

No one assigned

    Labels

    aiAI/자동화backend백엔드/APIdata데이터/DBenhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions