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

예시: 셀럽 착장 사진 옆에 아이템 정보(브랜드, 상품명, 가격)가 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)
pinterest_sources에서 활성 소스 조회
- Hyperbrowser로 보드/검색 페이지 접근 → 핀 목록 추출
pin_id로 dedup (UNIQUE 제약)
- 이미지 다운로드 → 기존
ImageCompressor 압축 → R2 업로드
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_posts ← image_url=r2_url, media_source={"platform":"pinterest","pin_id":...}
warehouse.seed_spots ← position_left, position_top, subcategory_code (아이템별)
warehouse.seed_solutions ← brand, product_name, price_amount, price_currency
warehouse.seed_asset ← archived_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)
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 |
검증
🤖 Generated with Claude Code
Summary
Pinterest/Instagram에 존재하는 "fashion decode" 포스트(셀럽 착장 사진 + 브랜드/상품명/가격 오버레이 이미지)를 자동 수집하고, Vision AI로 파싱하여 기존 seed 파이프라인(
seed_posts → seed_spots → seed_solutions)에 자동 투입하는 워크플로우를 구축한다.Architecture
Phase 1: 인프라 — R2 버킷 + DB 스키마
R2 버킷
decoded-pinterestwrangler r2 bucket create decoded-pinterest또는 Cloudflare 대시보드Supabase 마이그레이션
Phase 2: ai-server R2 클라이언트
의존성
boto3추가 (pyproject.toml)새 파일:
src/managers/storage/r2_client.pyendpoint_url=https://{account_id}.r2.cloudflarestorage.com)upload(key, data, content_type) → public_urldownload(key) → bytes환경변수 (
_environment.py)PINTEREST_R2_ACCOUNT_IDPINTEREST_R2_ACCESS_KEY_IDPINTEREST_R2_SECRET_ACCESS_KEYPINTEREST_R2_BUCKET_NAME(default:decoded-pinterest)PINTEREST_R2_PUBLIC_URLDI 컨테이너 (
_container.py)PinterestContainer(DeclarativeContainer)→Application에 등록Phase 3: Pinterest 스크래퍼
새 모듈:
src/services/pinterest/models.pyPinterestSource,RawPin,ParsedItem,ParsedDecodeResult)repository.pypinterest_sources,pinterest_pins)scraper.pyvision_parser.pyseed_writer.pyjobs.py스크래퍼 로직 (
scraper.py)pinterest_sources에서 활성 소스 조회pin_id로 dedup (UNIQUE 제약)ImageCompressor압축 → R2 업로드pinterest_pinsINSERT (parse_status='pending')Phase 4: Vision AI 파싱
파서 (
vision_parser.py)GeminiClient재사용 (gemini-3-flash-preview)Seed 파이프라인 연결 (
seed_writer.py)ParsedDecodeResult→ 기존 테이블 매핑:warehouse.seed_posts←image_url=r2_url,media_source={"platform":"pinterest","pin_id":...}warehouse.seed_spots←position_left,position_top,subcategory_code(아이템별)warehouse.seed_solutions←brand,product_name,price_amount,price_currencywarehouse.seed_asset←archived_url=r2_url,image_hashpinterest_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)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.tomlboto3,hyperbrowser의존성 추가packages/ai-server/src/config/_environment.pypackages/ai-server/src/config/_container.pyPinterestContainer추가packages/ai-server/src/bootstrap.pypackages/ai-server/src/app.pypackages/web/lib/supabase/warehouse-types.ts신규
supabase/migrations/..._create_pinterest_tables.sqlpackages/ai-server/src/managers/storage/__init__.pypackages/ai-server/src/managers/storage/r2_client.pypackages/ai-server/src/services/pinterest/__init__.pypackages/ai-server/src/services/pinterest/models.pypackages/ai-server/src/services/pinterest/repository.pypackages/ai-server/src/services/pinterest/scraper.pypackages/ai-server/src/services/pinterest/vision_parser.pypackages/ai-server/src/services/pinterest/seed_writer.pypackages/ai-server/src/services/pinterest/jobs.pypackages/ai-server/src/api/pinterest.py검증
🤖 Generated with Claude Code