o2o-negosium-original/lps/docs/architecture.md
민헌 61b9e6ae82 docs(lps): 오픈마켓 폴백 크롤 반영 — 아키텍처·README
파이프라인 ⑤-1 폴백 단계(네이버 미커버 몰만 크롤), 구성요소 표에 오픈마켓 폴백,
폴더 구조에 esm/st11 추가.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:54:00 +09:00

5.5 KiB

아키텍처 — 어떻게 동작하는가

← README로

1. 구성요소 (한눈에)

구성요소 파일 역할
API 서버 web_main.py 검색 요청을 받아 큐에 적재만 함(빠르게 응답). 상태·이력 조회 제공
큐(대기줄) crud/job_crud.py + job 테이블 할 일을 순서대로 안전하게 보관 (PostgreSQL 사용)
워커(일꾼) worker_main.py, worker/ 큐에서 하나씩 꺼내 실제 검색·판정·저장 수행
소스 어댑터 services/search/ 네이버·쿠팡에서 상품 수집 (소스별 방식 캡슐화)
오픈마켓 폴백 services/search/{esm,st11}/ G마켓·옥션·11번가 크롤 — 네이버가 그 몰을 커버 못 했을 때만 (BrowserSearchAdapter 공유)
파이프라인 services/pipeline/ 수집 결과를 필터·이상치 제거·최저가 정렬
AI services/ai/ "같은 상품" 판정 + 검색어 생성 (OpenAI)

API와 워커를 분리한 이유: 요청 접수는 즉시(가벼움), 실제 검색은 무거움(브라우저·AI). 분리하면 요청이 밀리지 않고, 워커만 따로 늘릴 수 있습니다.

2. 처리 파이프라인 (워커가 하는 일)

한 건의 검색 작업은 아래 단계를 거칩니다. 각 단계의 통과 건수는 stages로 기록됩니다(관측).

① 네거티브 캐시 확인
   최근 "없음"으로 확인된 상품이면 → 재검색 생략(비용 절약)

② 소스 검색 (재정제 루프, 최대 3라운드)
   라운드1: 원본 검색어  →  라운드2: AI 정밀 검색어  →  라운드3: 광역 검색어
   각 라운드에서 네이버 ∥ 쿠팡 동시 검색 후 병합

③ 필터
   - mall 필터 (필요 시 특정 쇼핑몰만/제외)
   - 가격 밴드 (요청에 현재가가 있으면 ±범위 밖 제거)

④ 이상치 제거 (IQR)
   비정상적으로 싸거나 비싼 항목 제거 (오매칭·묶음 등)

⑤ AI 같은 상품 판정
   후보 중 "찾는 상품과 동일한 것"만 선별 (액세서리·다른 규격 제외)
   → 매칭 있으면: ⑤-1 오픈마켓 폴백 → 최저가순 정렬 → 상위 N개 반환 (found)
   → 매칭 0건 + 소스 정상: 다음 라운드로
   → 매칭 0건 + 소스 차단: 작업 실패 처리(뒤에서 재시도)

⑤-1 오픈마켓 폴백 (매칭 성공 시)
   네이버가 커버 못 한 몰(G마켓·옥션·11번가)만 실사이트 크롤 → 같은 상품 판정 → 병합
   ("네이버로 그 몰 값 확보 성공 → 그 값, 실패(몰 없음) → 크롤". 크롤 실패는 격리)

⑥ 결과 저장
   최저가 확정 + 최저가 이력 스냅샷 기록(몰별 by_mall 포함)

모든 라운드에서 못 찾으면 → not_found(정상 종료) + 네거티브 캐시에 기록.

3. 재시도 로직 — "왜 실패했나"에 따라 다르게

실패는 성격이 다르므로 두 종류로 분리해서 처리합니다. (섞으면 무한 재시도·오작동)

실패 종류 예시 대응
기술적 실패 네트워크·쿠팡 차단·API 한도·AI 오류 잠시 후 재시도(지수 백오프), 여러 번 실패하면 DEAD(사람이 확인)
검색어 문제 "맥심 커피"가 너무 광범위 → 0건 검색어를 바꿔 재시도(정밀→광역), 다 실패하면 not_found
진짜 없는 상품 실제로 안 파는 상품 재시도 무의미 → not_found로 정상 종료 (에러 아님)

무한 재시도 방지: 기술적 재시도(횟수 상한)·검색어 재시도(라운드 상한) 둘 다 유한합니다. "못 찾음"은 실패가 아니라 정상적인 답으로 처리해 쌓이지 않습니다.

4. 쿠팡 봇 차단 대응

  • 쿠팡은 Akamai 봇 차단(JS 챌린지)이 있어, 일반 HTTP로는 못 뚫습니다 → 실제 Chrome 브라우저(Patchright)로 통과합니다.
  • 프록시 IP 회전(DECODO): 같은 IP로 계속 두드리면 차단되므로, 일정 시간마다 다른 IP로 바꿉니다. (매 요청마다 바꾸면 오히려 의심받아 일정 시간 유지 후 회전)
  • 감지 시 즉시 IP 전환: 차단이 감지되면 새 IP로 바꿔 재시도하고, 감지 이력(몇 번째 요청에서 걸렸는지)을 기록해 패턴을 분석합니다.
  • 대역폭 절약: 이미지·폰트 등 불필요한 리소스는 받지 않아 프록시 비용을 줄입니다.

5. 최저가 이력 (그래프)

  • 트리거 기반: 자동 배치로 전 상품을 주기 조회하지 않고, 실제 조회된 상품만 그 시점에 기록 → 트래픽·비용 절약.
  • 검색할 때마다 네이버 최저가 / 쿠팡 최저가 / 최종 최저가를 스냅샷으로 남깁니다.
  • 그래프: X축 = 조회 시각(불규칙), Y축 = 가격, 3개 선. (한쪽 소스에 그 상품이 없던 시점은 선이 비어있음 — 정상)

6. 설계 원칙 (참고)

  • PostgreSQL을 큐로 제대로 사용: 별도 메시지 브로커(Redis 등) 없이, 원자적 작업 할당 + 자동 복구로 유실·중복 없이 처리.
  • 소스 어댑터 패턴: 네이버·쿠팡의 수집 방식 차이를 어댑터 안에 가두고, 코어는 정규화된 결과만 다룸 → 새 쇼핑몰 추가가 쉬움.
  • AI로 매칭: 상품명 형식이 제각각이라 규칙으로 고정 파싱하지 않고, AI가 "같은 상품인지" 판단.

더 깊은 내부 구현은 각 파일 상단 주석에 정리되어 있습니다.