- lps-admin 컨테이너화: Vite 정적 빌드 → nginx, /v1·/healthz·/readyz 를 lps-api:9600 으로 동일출처 프록시(빌드 타임 API URL 주입 불필요) - lps-api/worker: APP_ENV=local 고정 + DB_HOST override(도메인 backend·negodata·agent 와 동일 패턴), config.<env>.toml 마운트 방식 폐기 - server_configs 에 DB 접속 env override(_apply_db_env_override) 복원 - config.dev/prod.toml.example 제거 — 환경 구분 없이 config.local.toml 하나 (prod 서버도 그 서버의 config.local.toml + docker compose up -d) - run_docker.sh 환경 선택 제거·lps-admin 포함, README·operations 문서 갱신 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
149 lines
9.2 KiB
Markdown
149 lines
9.2 KiB
Markdown
# LPS — 인터넷 최저가 검색 솔루션
|
|
|
|
> 상품 정보를 넣으면 **네이버·쿠팡을 뒤져 "같은 상품"의 최저가를 찾아** 돌려주고, 그 가격을 **시간에 따라 기록**해 그래프로 볼 수 있는 시스템입니다.
|
|
|
|
---
|
|
|
|
## 🧭 이게 뭔가요? (비개발자용 3줄 요약)
|
|
|
|
1. "맥심 커피 (1박스, 160개입)" 같은 상품 정보를 보내면,
|
|
2. 시스템이 **네이버·쿠팡을 실제로 검색**하고, **AI가 "진짜 같은 상품"만 골라** 최저가를 알려줍니다. (빨대·커버 같은 **엉뚱한 액세서리는 걸러냅니다**)
|
|
3. 같은 상품을 **여러 번 조회하면 가격 변화가 쌓여서**, 네이버/쿠팡/최종 최저가를 **그래프**로 볼 수 있습니다.
|
|
|
|
**왜 유용한가?** 사람이 일일이 검색·비교하지 않아도, 필요한 상품만(조회할 때만) 자동으로 최저가를 찾고 가격 추이를 남깁니다.
|
|
|
|
---
|
|
|
|
## ⚙️ 어떻게 동작하나요? (워크플로우)
|
|
|
|
```
|
|
[1] 검색 요청 [2] 대기줄(큐) [3] 일꾼(워커)가 처리
|
|
상품 정보 전송 ─────▶ 순서대로 쌓임 ─────▶ 네이버 + 쿠팡 동시 검색
|
|
(POST /search) (즉시 접수번호 반환) │
|
|
▼
|
|
[4] 걸러내기 + AI 판정
|
|
가격 이상치 제거 → "같은 상품"만 선별
|
|
│
|
|
▼
|
|
[4-1] 오픈마켓 폴백 (옵션 · 기본 꺼짐)
|
|
네이버가 못 덮은 몰(G마켓·옥션·11번가)만 크롤
|
|
│
|
|
▼
|
|
[5] 최저가 확정 + 기록
|
|
네이버/쿠팡/최종 + 몰별 최저가 저장 → 이력 적재
|
|
│
|
|
▼
|
|
[6] 완료(결과 + 검색 원가 조회 가능)
|
|
```
|
|
|
|
**핵심 포인트**
|
|
- **즉시 응답 + 나중 처리**: 요청하면 바로 "접수번호(job_id)"를 주고, 실제 검색은 뒤에서 진행됩니다. (검색은 몇 초~수십 초 걸림)
|
|
- **못 찾으면 검색어를 바꿔 재시도**: "맥심 커피"로 안 나오면 "맥심 모카골드 커피믹스"처럼 **AI가 검색어를 다듬어** 다시 시도하고, 그래도 없으면 "없음"으로 정리합니다. (무한 재시도 안 함)
|
|
- **차단 대응**: IP당 요청 예산(기본 3회)에 닿으면 **차단당하기 전에 IP를 선제 교체**하고(평판 보존 — 그 IP는 로테이션 복귀 시 재사용), 그래도 감지되면 그 포트를 쿨다운 격리 후 다른 IP로 재시도합니다. 시작 시 챌린지를 미리 풀어(웜업) 실 작업을 빠르게 합니다.
|
|
- **원가 투명**: 검색 1건이 쓴 AI 비용·프록시 대역폭·시간을 함께 기록합니다.
|
|
|
|
---
|
|
|
|
## ✨ 주요 기능
|
|
|
|
| 기능 | 설명 |
|
|
|------|------|
|
|
| 멀티 소스 검색 | 네이버 쇼핑 API + 쿠팡(Akamai 우회) 동시 검색·병합 |
|
|
| 오픈마켓 폴백 크롤 | 네이버가 못 덮은 몰만 G마켓·옥션(Cloudflare Turnstile 우회)·11번가 크롤 → 몰별 가격. **기본 비활성**(`[WorkerConfig].fallbacks`, [배경](docs/decision-openmarket-crawler.md)) |
|
|
| AI 같은 상품 판정 | "진짜 그 상품"만 선별 (액세서리·다른 규격 제외) |
|
|
| 검색어 자동 정제 | 0건이면 정밀/광역 검색어로 재시도 |
|
|
| 최저가 이력 그래프 | 조회 시점마다 네이버/쿠팡/최종 + 몰별(by_mall) 최저가를 시계열로 기록 |
|
|
| 검색 원가 계측 | 검색 1건의 AI 토큰·비용 + DECODO 대역폭(실측 CDP) + 시간을 집계 |
|
|
| 다중 상품 병렬 | 워커별 브라우저 세트로 여러 상품 동시 검색(`WORKER_CONCURRENCY`) |
|
|
| 안정적 큐 처리 | 작업 유실 없이 순서대로, 실패 시 자동 재시도 |
|
|
| 프록시 IP 선제 회전 | 요청 예산(기본 3회) 도달 시 **차단 전 선제 교체** + 불탄 포트 쿨다운 + 봇 감지·전송오류 즉시 순환 + 시작 웜업(DECODO). 예산 튜닝용 `ip_session` 관측 로그 |
|
|
| 임계 알림 | 큐·차단·DB풀·소스별 장기실패·비용 등 10룰 — 쿨다운(스팸 방지)·해소 알림, Slack 웹훅([룰 표](docs/operations.md)) |
|
|
| API guard | `[WebServerConfig].api_keys` 설정 시 `/v1` 전체 X-API-Key 검증(개발은 빈값=개방 모드) |
|
|
|
|
---
|
|
|
|
## 🚀 빠른 시작
|
|
|
|
```bash
|
|
cd lps
|
|
|
|
# 1) 설정 파일 준비 (DB·API 키 등)
|
|
cp config/config.local.toml.example config/config.local.toml # 값 채우기
|
|
|
|
# 2) API 서버 실행 (요청 접수)
|
|
./run_local_server.sh # → http://localhost:9600/docs
|
|
|
|
# 3) 워커 실행 (실제 검색 수행) — 별도 터미널
|
|
./run_local_worker.sh # 대화형: 동시성·프로필 선택 (또는 python worker_main.py)
|
|
|
|
# (선택) 부하 테스트 GUI — Locust 웹 UI(:8089)
|
|
./run_loadtest_gui.sh # 브라우저에서 users/spawn 조절하며 RPS/지연 관측
|
|
```
|
|
|
|
간단 테스트:
|
|
```bash
|
|
curl -X POST localhost:9600/v1/lps/search -H 'Content-Type: application/json' \
|
|
-d '{"data":[{"product_code":"T1","product_name":"맥심 커피","specification":"1박스, 160개입"}]}'
|
|
```
|
|
|
|
**서버 실행 — Docker**
|
|
```bash
|
|
# 리포 루트에서 전체 스택과 함께 (권장)
|
|
docker compose up -d # negosium 스택 + lps-api·lps-worker·lps-admin 모두 기동
|
|
|
|
# lps 서브셋만 (대화형: 설정 검증·guard 키 안전장치 + admin 포함)
|
|
./run_docker.sh
|
|
```
|
|
> 환경 구분이 없습니다(도메인 backend·negodata·agent 와 동일) — 항상 `config.local.toml`.
|
|
> **prod 서버**도 그 서버의 `config.local.toml` 에 prod 값(시크릿·guard 키·스케일)을 채우고 그냥 `docker compose up -d`.
|
|
> DB 는 컨테이너에서 `host.docker.internal`(호스트 DB)로 접속하고, 관리형 DB 면 `LPS_DB_HOST=` 로 override 를 끄고 toml 의 호스트를 씁니다.
|
|
> 운영에서 API 를 외부에 열지 않으려면 `export LPS_API_BIND=127.0.0.1`(리버스프록시 뒤).
|
|
|
|
> 자세한 실행/설정은 [운영 가이드](docs/operations.md) 참고.
|
|
|
|
---
|
|
|
|
## 📚 문서
|
|
|
|
| 문서 | 대상 | 내용 |
|
|
|------|------|------|
|
|
| **[아키텍처](docs/architecture.md)** | 개발자/기획자 | 구성요소·파이프라인·안티봇(Akamai/Turnstile)·비용계측·동시성 |
|
|
| **[데이터베이스](docs/database.md)** | 개발자/기획자 | 테이블 5종 구조와 코드값(+by_mall·ip_session) |
|
|
| **[API 사용법](docs/api.md)** | 연동 개발자 | 엔드포인트·요청/응답·metrics 예시 |
|
|
| **[운영 가이드](docs/operations.md)** | 운영자/개발자 | 실행·병렬·관측(readyz/ops/알림)·**Docker 배포**·문제 해결 |
|
|
| **[크롤러 논의](docs/decision-openmarket-crawler.md)** | 팀 | 오픈마켓 크롤러 유지 여부(ROI) 의사결정 메모 |
|
|
|
|
---
|
|
|
|
## 📁 폴더 구조
|
|
|
|
```
|
|
lps/
|
|
├── web_main.py # API 서버 진입점 (요청 접수)
|
|
├── worker_main.py # 워커 진입점 (검색+웜업+유휴정리+ops모니터)
|
|
├── Dockerfile # API 이미지(lean) · Dockerfile.worker # 워커(Chromium+Xvfb)
|
|
├── run_local_server.sh # 로컬 API 실행 (대화형)
|
|
├── run_local_worker.sh # 로컬 워커 실행 (대화형: 동시성·프로필)
|
|
├── run_docker.sh # lps 서브셋 Docker 실행 (대화형: 설정 검증·guard 안전장치 + admin 포함)
|
|
├── run_loadtest_gui.sh # 부하 테스트 Locust 웹 UI(:8089) 실행 (대화형)
|
|
├── config/ # 설정(config.local.toml — 포트/DB/API키, 미커밋; 배포는 env 주입)
|
|
├── common/ # 공통(enums, DB 세션, 모델, 로거, alerts=임계 알림 관리자)
|
|
│ └── database/model/models.py # DB 테이블 정의
|
|
├── loadtest.py # 부하 테스트 (N개 상품 → 처리량·지연·비용 집계)
|
|
├── crud/ # DB 접근 (job_crud, price_history, negative_cache, bot_detection, ip_session)
|
|
├── services/
|
|
│ ├── search/ # 소스 어댑터 (coupang, naver, esm=G마켓·옥션, st11=11번가)
|
|
│ │ ├── browser_base.py # patchright 공통(수명·프록시회전·차단감지·CDP 바이트계측)
|
|
│ │ ├── proxy.py # DECODO(IP 회전·포트 쿨다운·프리플라이트)
|
|
│ │ └── card_parser.py # 오픈마켓 공용 카드 파서
|
|
│ ├── pipeline/ # 필터·이상치·최저가 정렬(+몰별 분해)
|
|
│ ├── ai/ # AI 유사도 판정·검색어 생성 (OpenAI)
|
|
│ └── metrics.py # 검색 원가 계측(AI/DECODO 비용·시간)
|
|
├── worker/ # 워커 루프·핸들러(폴백·데드라인)·알림(NOTIFY)
|
|
├── router/v1/lps/ # API 라우터
|
|
└── tests/ # 테스트
|
|
```
|
|
|
|
## 포트
|
|
backend 9300 / negodata 9400 / agent 9500 과 겹치지 않게 **LPS는 9600**.
|