파이프라인 ⑤-1 폴백 단계(네이버 미커버 몰만 크롤), 구성요소 표에 오픈마켓 폴백, 폴더 구조에 esm/st11 추가. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.5 KiB
5.5 KiB
아키텍처 — 어떻게 동작하는가
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가 "같은 상품인지" 판단.
더 깊은 내부 구현은 각 파일 상단 주석에 정리되어 있습니다.