비개발자/기획자/개발자 누구나 이해하도록 일목요연하게 정리. 길이 분산 위해 분할. - README.md: 3줄 요약 + 워크플로우 다이어그램 + 주요기능 + 빠른시작 + 문서 목차 - docs/architecture.md: 구성요소·처리 파이프라인·재시도/프록시/봇감지/이력 원리 - docs/database.md: 테이블 4종(job/price_history/search_negative/bot_detection) + 코드값 - docs/api.md: 엔드포인트 요청/응답 예시(curl/Postman), 상태·코드 요약 - docs/operations.md: 실행·로그·DB조회·테스트·문제해결·배포유의 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.0 KiB
5.0 KiB
아키텍처 — 어떻게 동작하는가
1. 구성요소 (한눈에)
| 구성요소 | 파일 | 역할 |
|---|---|---|
| API 서버 | web_main.py |
검색 요청을 받아 큐에 적재만 함(빠르게 응답). 상태·이력 조회 제공 |
| 큐(대기줄) | crud/job_crud.py + job 테이블 |
할 일을 순서대로 안전하게 보관 (PostgreSQL 사용) |
| 워커(일꾼) | worker_main.py, worker/ |
큐에서 하나씩 꺼내 실제 검색·판정·저장 수행 |
| 소스 어댑터 | services/search/ |
네이버·쿠팡에서 상품 수집 (소스별 방식 캡슐화) |
| 파이프라인 | services/pipeline/ |
수집 결과를 필터·이상치 제거·최저가 정렬 |
| AI | services/ai/ |
"같은 상품" 판정 + 검색어 생성 (OpenAI) |
API와 워커를 분리한 이유: 요청 접수는 즉시(가벼움), 실제 검색은 무거움(브라우저·AI). 분리하면 요청이 밀리지 않고, 워커만 따로 늘릴 수 있습니다.
2. 처리 파이프라인 (워커가 하는 일)
한 건의 검색 작업은 아래 단계를 거칩니다. 각 단계의 통과 건수는 stages로 기록됩니다(관측).
① 네거티브 캐시 확인
최근 "없음"으로 확인된 상품이면 → 재검색 생략(비용 절약)
② 소스 검색 (재정제 루프, 최대 3라운드)
라운드1: 원본 검색어 → 라운드2: AI 정밀 검색어 → 라운드3: 광역 검색어
각 라운드에서 네이버 ∥ 쿠팡 동시 검색 후 병합
③ 필터
- mall 필터 (필요 시 특정 쇼핑몰만/제외)
- 가격 밴드 (요청에 현재가가 있으면 ±범위 밖 제거)
④ 이상치 제거 (IQR)
비정상적으로 싸거나 비싼 항목 제거 (오매칭·묶음 등)
⑤ AI 같은 상품 판정
후보 중 "찾는 상품과 동일한 것"만 선별 (액세서리·다른 규격 제외)
→ 매칭 있으면: 최저가순 정렬 → 상위 N개 반환 (found)
→ 매칭 0건 + 소스 정상: 다음 라운드로
→ 매칭 0건 + 소스 차단: 작업 실패 처리(뒤에서 재시도)
⑥ 결과 저장
최저가 확정 + 최저가 이력 스냅샷 기록
모든 라운드에서 못 찾으면 → 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가 "같은 상품인지" 판단.
더 깊은 내부 구현은 각 파일 상단 주석에 정리되어 있습니다.