Skip to content

feat(raw_posts): StarStyle.com adapter — celebrity outfit ground-truth source #466

@cocoyoon

Description

@cocoyoon

Summary

SourceAdapter 인터페이스에 StarStyleAdapter(platform=starstyle)를 추가한다. WordPress 기반 정적 HTML 사이트로, 셀럽-아이템 매핑이 schema.org 구조 + 일관된 CSS 클래스로 라벨링되어 있어 파싱이 단순하다.

cf. #214 / #259 / #465(WhatsOnTheStar) — 같은 `SourceAdapter` 패턴.

사이트 분석

  • URL: http://www.starstyle.com (HTTPS는 301 → HTTP, TLS 미적용 — adapter는 HTTP로 직접 호출)
  • 타입: 여성 셀럽 fashion (Hadid 자매, Zendaya, Taylor Swift, Sabrina Carpenter, Jennie 등 50+)
  • 렌더링: WordPress SSR (정적 HTML), Bootstrap 테마. JS 의존 없음 → `httpx + bs4`로 충분
  • 공식 API: WP REST API 차단 (`/wp-json/wp/v2/posts` → 401 `no_rest_api_sorry`)
  • robots.txt: `User-agent: * Disallow: /` — Googlebot/Bing 등 명시 봇만 허용. adapter는 일반 brand-aware UA로 호출 (Pinterest 어댑터와 동일 정책), ToS 검토 필요
  • sitemap: `http://www.starstyle.com/sitemap.xml\` — 모든 포스트 URL + 대표 이미지 포함, 활발히 업데이트

URL/데이터 구조

  • 포스트: `/{celebrity-event-slug}-sp{post_id}/` — 예: `/hunter-schafer-gala-sp819444/`
  • 셀럽 페이지: `/celebrity/{slug}/`
  • breadcrumb: `schema.org/BreadcrumbList` → 셀럽명/slug 추출
  • og:description: `"{Celebrity} wearing {Item1} and {Item2}"` — 요약
  • 포스트 본문 아이템 카드 (반복):
    ```html

    {Brand} {Product Title}

    ```
    • `data-id`: starstyle 내부 product ID
    • href: skimresources affiliate wrapper → 실제 retailer URL은 `url=` 쿼리에서 디코딩 (Farfetch, SSENSE, Moda Operandi, Ferragamo 등)
  • 이미지: `og:image` = `/wp-content/uploads/{celebrity-slug}/{post_id}.jpg` (HD)
  • 날짜: `<meta property="article:published_time">` ISO8601

스크랩 전략

  1. discovery: `sitemap.xml` 한 번 fetch → 신규 URL diff (incremental)
  2. 포스트 파싱: `httpx` + `BeautifulSoup` + `lxml`
  3. affiliate 디코딩: `urllib.parse` 로 `go.skimresources.com` href에서 `url` param 추출
  4. rate limit: 1 req/sec, retry-after 존중

RawMedia 매핑

```python
RawMedia(
external_id=str(post_id), # sp{ID} 의 ID
external_url="http://www.starstyle.com/{slug}-sp{id}/",
image_url=og_image,
caption=og_description, # "Hunter Schafer wearing ..."
author_name=celebrity_name, # breadcrumb에서
platform_metadata={
"celebrity_slug": ...,
"published_at": ..., # article:published_time
"items": [
{"product_id": data_id, "brand": ..., "title": ..., "retailer_url": ...}
],
},
)
```

Ground-truth 활용

`/465`(WhatsOnTheStar)와 동일한 옵션 매트릭스:

  • A. `parse_status='parsed'`로 직접 INSERT (Phase 1)
  • B. vision 검증 잡 (Phase 2, evaluation)

가격은 starstyle 페이지에 명시되지 않는 경우가 많고, brand/title만 안정적으로 추출 가능 → `platform_metadata.items[].price`는 optional.

Scope (this issue)

  • `packages/ai-server/src/services/raw_posts/adapters/starstyle.py` 신설
  • `SourceAdapter` 구현 — `platform="starstyle"`
  • `source_type`: `post`(단일), `sitemap`(전체 신규 diff), `celebrity`(셀럽별 페이지네이션)
  • skimresources affiliate 디코더 helper
  • `build_default_adapters` 등록
  • 단위 테스트 — Hunter Schafer Met Gala 등 실 fixture 3개
  • env vars: `STARSTYLE_INITIAL_LIMIT` / `STARSTYLE_INCREMENTAL_LIMIT` / `STARSTYLE_USER_AGENT`(optional)
  • robots.txt 정책: 코드 주석 + ToS 메모

Out of scope

  • 벌크 백필 / 스케줄러 통합 (별도 이슈)
  • 가격 추출 (페이지 구조상 비일관 — 별도 R&D 후 결정)
  • 셀럽 인덱스 → 신규 셀럽 자동 등록 (manual seed for Phase 1)

Acceptance

  • `fetch(source_type="post", source_identifier="hunter-schafer-gala-sp819444")` → `RawMedia` 1개
  • `fetch(source_type="sitemap", limit=20)` → 최신 20개 `RawMedia`
  • 아이템 메타가 `platform_metadata.items`에 보존, retailer_url은 affiliate 풀린 원본
  • 테스트 그린, 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