Skip to content

feat: Pinterest Fashion Decode 자동 수집 + Vision AI 파싱 파이프라인 #214

@cocoyoon

Description

@cocoyoon

Summary

Pinterest/Instagram에 존재하는 "fashion decode" 포스트(셀럽 착장 사진 + 브랜드/상품명/가격 오버레이 이미지)를 자동 수집하고, Vision AI로 파싱하여 기존 seed 파이프라인(seed_posts → seed_spots → seed_solutions)에 자동 투입하는 워크플로우를 구축한다.

Instagram 파이프라인은 동료가 별도 구축 중 — 이 이슈는 Pinterest 전용.

fashion decode 예시

예시: 셀럽 착장 사진 옆에 아이템 정보(브랜드, 상품명, 가격)가 annotate된 이미지


Architecture

[수집] Pinterest 보드/검색 크롤링 (Hyperbrowser)
  → 이미지 다운로드 → Cloudflare R2 저장 (decoded-pinterest 버킷)
  → warehouse.pinterest_pins INSERT (parse_status='pending')

[파싱] ARQ 배치 잡 (10분 간격)
  → R2에서 이미지 다운로드
  → Gemini Flash Vision AI → 구조화 데이터 추출
  → warehouse.seed_posts / seed_spots / seed_solutions 자동 생성

[검수] Admin 대시보드에서 seed_posts 검수 → 퍼블리싱

Phase 1: 인프라 — R2 버킷 + DB 스키마

R2 버킷

  • 버킷명: decoded-pinterest
  • wrangler r2 bucket create decoded-pinterest 또는 Cloudflare 대시보드
  • Public access 설정 (R2.dev subdomain 또는 custom domain)

Supabase 마이그레이션

-- 스크래핑 대상 (보드, 검색어, 유저)
CREATE TABLE warehouse.pinterest_sources (
    id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
    source_type text NOT NULL CHECK (source_type IN ('board', 'search', 'user')),
    source_identifier text NOT NULL,
    label text,
    is_active boolean DEFAULT true NOT NULL,
    last_scraped_at timestamptz,
    metadata jsonb,
    created_at timestamptz DEFAULT now() NOT NULL,
    updated_at timestamptz DEFAULT now() NOT NULL
);

-- 수집된 개별 핀
CREATE TABLE warehouse.pinterest_pins (
    id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
    source_id uuid NOT NULL REFERENCES warehouse.pinterest_sources(id) ON DELETE CASCADE,
    pin_id text NOT NULL UNIQUE,
    pin_url text NOT NULL,
    image_url text NOT NULL,
    r2_key text,
    r2_url text,
    image_hash text,
    title text,
    description text,
    pinner_name text,
    parse_status text DEFAULT 'pending' NOT NULL
        CHECK (parse_status IN ('pending', 'parsing', 'parsed', 'failed', 'skipped')),
    parse_result jsonb,
    seed_post_id uuid REFERENCES warehouse.seed_posts(id) ON DELETE SET NULL,
    metadata jsonb,
    created_at timestamptz DEFAULT now() NOT NULL,
    updated_at timestamptz DEFAULT now() NOT NULL
);

CREATE INDEX idx_pinterest_pins_source ON warehouse.pinterest_pins(source_id);
CREATE INDEX idx_pinterest_pins_parse_status ON warehouse.pinterest_pins(parse_status);

Phase 2: ai-server R2 클라이언트

의존성

  • boto3 추가 (pyproject.toml)

새 파일: src/managers/storage/r2_client.py

  • boto3 S3 호환 클라이언트 (endpoint_url=https://{account_id}.r2.cloudflarestorage.com)
  • upload(key, data, content_type) → public_url
  • download(key) → bytes

환경변수 (_environment.py)

  • PINTEREST_R2_ACCOUNT_ID
  • PINTEREST_R2_ACCESS_KEY_ID
  • PINTEREST_R2_SECRET_ACCESS_KEY
  • PINTEREST_R2_BUCKET_NAME (default: decoded-pinterest)
  • PINTEREST_R2_PUBLIC_URL

DI 컨테이너 (_container.py)

  • PinterestContainer(DeclarativeContainer)Application에 등록

Phase 3: Pinterest 스크래퍼

새 모듈: src/services/pinterest/

파일 역할
models.py Pydantic 모델 (PinterestSource, RawPin, ParsedItem, ParsedDecodeResult)
repository.py Supabase CRUD (pinterest_sources, pinterest_pins)
scraper.py Hyperbrowser 기반 Pinterest 크롤러
vision_parser.py Gemini Flash Vision 파싱
seed_writer.py seed 파이프라인 연결
jobs.py ARQ 잡 정의

스크래퍼 로직 (scraper.py)

  1. pinterest_sources에서 활성 소스 조회
  2. Hyperbrowser로 보드/검색 페이지 접근 → 핀 목록 추출
  3. pin_id로 dedup (UNIQUE 제약)
  4. 이미지 다운로드 → 기존 ImageCompressor 압축 → R2 업로드
  5. pinterest_pins INSERT (parse_status='pending')

Pinterest는 안티봇이 강력 — 로그인 세션, JS 렌더링, 핑거프린팅 조합. 일반 Playwright로는 금방 차단됨. Hyperbrowser 같은 managed browser API가 필요.


Phase 4: Vision AI 파싱

파서 (vision_parser.py)

  • 기존 GeminiClient 재사용 (gemini-3-flash-preview)
  • 구조화된 출력 스키마:
class ParsedItem(BaseModel):
    brand: str
    product_name: str | None
    price_amount: float | None
    price_currency: str | None        # USD, KRW, EUR, ...
    subcategory: str                   # top, bottom, shoes, bag, accessory, ...
    position_x_pct: int               # 0-100 (seed_spots.position_left 매핑)
    position_y_pct: int               # 0-100 (seed_spots.position_top 매핑)

class ParsedDecodeResult(BaseModel):
    celebrity_name: str | None
    group_name: str | None
    occasion: str | None
    items: list[ParsedItem]

Seed 파이프라인 연결 (seed_writer.py)

  • ParsedDecodeResult → 기존 테이블 매핑:
    • warehouse.seed_postsimage_url=r2_url, media_source={"platform":"pinterest","pin_id":...}
    • warehouse.seed_spotsposition_left, position_top, subcategory_code (아이템별)
    • warehouse.seed_solutionsbrand, product_name, price_amount, price_currency
    • warehouse.seed_assetarchived_url=r2_url, image_hash
  • pinterest_pins.seed_post_id 업데이트, parse_status='parsed'

Phase 5: 스케줄링 + 관리 API

ARQ 잡 (jobs.py)

  • pinterest_scrape_job: 활성 소스 크롤링 → 핀 수집 → R2 저장
  • pinterest_parse_job: pending 핀 배치(10개) → Vision AI → seed 생성

APScheduler (bootstrap.py)

  • 스크래핑: 매 1시간
  • 파싱: 매 10분

FastAPI 엔드포인트 (src/api/pinterest.py)

  • POST /pinterest/sources — 소스 추가
  • GET /pinterest/sources — 소스 목록
  • GET /pinterest/pins?status= — 핀 목록 (status 필터)
  • POST /pinterest/scrape — 수동 스크래핑 트리거
  • GET /pinterest/stats — 수집/파싱 통계

파일 변경 요약

수정

파일 변경
packages/ai-server/pyproject.toml boto3, hyperbrowser 의존성 추가
packages/ai-server/src/config/_environment.py Pinterest R2 환경변수
packages/ai-server/src/config/_container.py PinterestContainer 추가
packages/ai-server/src/bootstrap.py 스케줄러 태스크 등록, API 라우터
packages/ai-server/src/app.py pinterest 라우터 mount
packages/web/lib/supabase/warehouse-types.ts 재생성

신규

파일 역할
supabase/migrations/..._create_pinterest_tables.sql DB 스키마
packages/ai-server/src/managers/storage/__init__.py 패키지
packages/ai-server/src/managers/storage/r2_client.py R2 클라이언트
packages/ai-server/src/services/pinterest/__init__.py 패키지
packages/ai-server/src/services/pinterest/models.py 데이터 모델
packages/ai-server/src/services/pinterest/repository.py DB 레포지토리
packages/ai-server/src/services/pinterest/scraper.py 스크래퍼
packages/ai-server/src/services/pinterest/vision_parser.py Vision 파서
packages/ai-server/src/services/pinterest/seed_writer.py Seed 연결
packages/ai-server/src/services/pinterest/jobs.py ARQ 잡
packages/ai-server/src/api/pinterest.py REST API

검증

  • R2 버킷 생성 + boto3 업로드/다운로드 테스트
  • Pinterest 보드 1개에서 핀 5개 수집 → DB 저장 확인
  • fashion decode 이미지 → Vision AI 파싱 정확도 검증 (브랜드/상품명/가격)
  • E2E: 핀 수집 → R2 → 파싱 → seed_posts/spots/solutions 생성 확인
  • seed_posts에서 pinterest 소스 데이터가 기존 Admin 검수 UI에 노출되는지 확인

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

Labels

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