shop.json 이 2026-07-31 종료(404 SE05)되고 NCP API HUB 에도 승계되지 않아
가격을 얻을 공식 경로가 사라졌다 → 쿠팡과 같은 스택(patchright+실제 Chrome)으로 크롤 전환.
경로: msearch.shopping.naver.com (PC 는 405/418 로 막힘). 7/9 스파이크 때 모바일은
로그인 리다이렉트였는데 그 사이 열렸다.
통과 조건 3개 — 하나라도 빠지면 WTM 캡차(실측):
- **한국 IP**: 해외 residential 은 즉시 하드차단(2.6KB) → kr.decodo.com 게이트웨이
([DecodoConfig].kr_host, DecodoProxy(host=...) 로 주입. 쿠팡은 기존 월드와이드 유지)
- **ko-KR 로케일/시간대**: KR IP + en-US 조합을 봇으로 본다
(BrowserSearchAdapter.context_options 훅 추가)
- **리소스 차단 금지**: route 를 걸면 즉시 캡차. image/media/font 만 막아도 동일 →
'무엇을 막느냐'가 아니라 요청 가로채기 자체가 탐지 신호. 대신 검색당 ~3MB(~$0.009)
파서는 '정확한 상품의 최저가'를 기준으로 취사선택한다:
- 광고/슈퍼적립/브랜드블록 카드 제외(멤버십·쿠폰 조건부 가격)
- 쿠폰할인가를 price 로 쓰지 않음(조건부라 실구매가보다 싸게 잡힘)
- 가격비교('최저 N원') 카드는 유지하고 mall_name="네이버"(옛 lprice 와 같은 의미)
- **배송비 확보** — 옛 오픈API 는 필드 자체가 없어 전 소스 None 이었다
- 가격 함정 3종 회귀 테스트: 단위가격(548원)·가격노드 안의 배송비(3,900원)·정상가/할인율
source 는 "naver" 유지 — price_history.naver_lowest·MALL_BY_SOURCE·프론트 그래프 계약이
구현(API→크롤) 교체와 무관하게 살아야 한다.
테스트 12건 추가(축약 픽스처 + 합성 함정) · 전체 182 passed.
119 lines
6.8 KiB
Plaintext
119 lines
6.8 KiB
Plaintext
# 복사해서 사용: cp config.local.toml.example config.local.toml
|
||
# 실제 config.local.toml 은 시크릿 포함이라 커밋하지 않는다(.gitignore: *.toml).
|
||
#
|
||
# ── 설정 소스는 TOML 하나다(2026-07-13 협의 — env/.env 이중 관리 제거) ──
|
||
# 호스트 실행: APP_ENV=local(기본) → 이 파일
|
||
# Docker : APP_ENV=dev|prod → config.dev.toml / config.prod.toml 을 컨테이너에 마운트
|
||
# (각 example 참고, 실행은 ../run_docker.sh 대화형 권장)
|
||
# env 는 APP_ENV·PROCESS_COUNT/WORKER_CONCURRENCY(실행 스크립트 대화형 입력)·LPS_LIVE(테스트)만 남는다.
|
||
[WebServerConfig]
|
||
server_name = "LpsServer"
|
||
port = 9600
|
||
process_count = 1
|
||
is_ssl = false
|
||
is_test = true
|
||
# CORS 허용 오리진(프론트). 비우면 [] (CORS 미적용). 5173=vite dev.
|
||
cors_origins = ["http://localhost:5173", "http://127.0.0.1:5173"]
|
||
# API guard 키 — 비우면 개방 모드(개발). prod 는 채운다(복수 등록 = 무중단 키 교체).
|
||
# 생성 예: openssl rand -hex 32. 호출자(negodata)도 같은 키를 설정해야 한다.
|
||
api_keys = []
|
||
|
||
[LogConfig]
|
||
print_console = true
|
||
log_level = "debug"
|
||
|
||
# DB Read/Write 분리. 관리형 DB(RDS/Aurora/Azure)는 host 에 엔드포인트, sslmode="require".
|
||
# LPS 도메인 로직/테이블이 생기기 전까지는 접속하지 않으므로(엔진 lazy) placeholder 여도 부팅된다.
|
||
[MainDBConfig]
|
||
db_type = "postgresql"
|
||
name = "lps_db"
|
||
write_host = "127.0.0.1"
|
||
write_port = 5432
|
||
write_id = "<DB_USER>"
|
||
write_pw = "<DB_PASSWORD>"
|
||
read_host = "127.0.0.1"
|
||
read_port = 5432
|
||
read_id = "<DB_USER>"
|
||
read_pw = "<DB_PASSWORD>"
|
||
show_log = false
|
||
pool_size = 10 # connection_budget>0 이면 무시(자동 산정). budget=0 일 때만 이 값 사용.
|
||
max_overflow = 20 # 〃
|
||
# 커넥션 예산(자동 산정). process_count 에 맞춰 pool_size/max_overflow 를 자동 계산:
|
||
# (pool+overflow) × 2엔진 × process_count ≤ connection_budget.
|
||
# 'lps API 가 쓸 총 커넥션 상한' — 공유 PG(max_connections)·동거 서비스(worker 등)를 고려한 값.
|
||
# 예) 전용 PG(max_connections=100)면 90 근처, 공유 PG면 40 권장. 0 이면 자동 끔(위 pool 값 사용).
|
||
connection_budget = 40
|
||
sslmode = "" # 로컬: "" / 관리형 DB: "require"|"verify-ca"|"verify-full"
|
||
|
||
# 워커 런타임 (worker_main.py). 동시성만 실행 시 WORKER_CONCURRENCY env 로 임시 override 가능.
|
||
[WorkerConfig]
|
||
concurrency = 1 # 상품 동시 검색 수(워커별 브라우저 세트, Chrome 최대 4×N). 로컬 권장 2~3
|
||
fallbacks = [] # 오픈마켓 폴백(기본 OFF). 예: ["gmarket", "auction", "st11"] — 켜기 전 라이브 스모크
|
||
# Chrome 프로필 베이스. 영속 경로면 재시작에도 쿠키 유지(재웜업 회피).
|
||
# ⚠️ 컨테이너로 띄우면 반드시 "/profiles" — compose 가 lps-profiles 볼륨을 그 경로에 마운트한다.
|
||
# ".profiles" 로 두면 /app/.profiles(컨테이너 레이어)에 쌓여 재생성 때마다 쿠키가 전부 날아가고,
|
||
# 볼륨은 붙어만 있고 아무 일도 하지 않는다(2026-07-28 로컬·배포서버 양쪽에서 실측).
|
||
# 호스트 직접 실행(run_local_worker.sh)일 때만 ".profiles" 를 쓴다.
|
||
profile_dir = ".profiles"
|
||
job_deadline_sec = 300 # 잡 1건 처리 상한(크롤 행 방어). 0=무제한(테스트용)
|
||
shutdown_grace_sec = 60 # graceful 종료 유예 — docker stop_grace_period 를 이보다 길게
|
||
chrome_channel = "chrome" # 로컬: 실제 Chrome
|
||
chrome_executable = "" # 컨테이너: "/opt/google/chrome/chrome" (설정 시 channel 무시)
|
||
heartbeat_file = "/tmp/lps_worker_heartbeat" # Docker HEALTHCHECK 가 신선도 확인
|
||
|
||
# 임계 알림 (AlertManager — 룰 의미는 docs/operations.md 표)
|
||
[AlertConfig]
|
||
webhook = "" # Slack 호환 웹훅 URL. 비우면 로그로만 알림
|
||
cooldown_min = 30 # 같은 룰 재발송 억제(분). 해소 알림은 즉시
|
||
dead_1h = 20 # 최근 1h DEAD 잡 수
|
||
blocks_1h = 80 # 최근 1h 봇 감지 수
|
||
queue_lag_sec = 300 # 가장 오래된 PENDING 대기 초
|
||
pool_pct = 90 # DB 커넥션 풀 포화율(%)
|
||
source_fail_30m = 5 # 소스별 30분 내 시도 N회 이상 & 성공 0건
|
||
deadline_1h = 5 # 최근 1h 잡 데드라인 강제종료 수
|
||
cost_1h_usd = 1.0 # 최근 1h 검색원가 합($)
|
||
ports_low_pct = 30 # 가용 프록시 포트 비율(%)
|
||
block_sessions_6h = 1 # 최근 6h '예산 회전에도 차단된' IP 세션 수
|
||
|
||
# ── 시크릿(API 키 등)도 이 파일에서 통합 관리 (미커밋). ──
|
||
|
||
# 네이버 쇼핑 오픈API (https://developers.naver.com/apps). 여러 개면 429/403 로테이션 자동 포함.
|
||
# ⚠️ 2026-07-31 네이버가 쇼핑·책·전문자료 검색을 종료(유예·대체 없음) → shop.json 은 404 SE05.
|
||
# 이 섹션은 사문화 상태로 남겨둔다(어댑터 제거 결정 전까지).
|
||
[NaverConfig]
|
||
[[NaverConfig.keys]]
|
||
id = "<NAVER_CLIENT_ID>"
|
||
secret = "<NAVER_CLIENT_SECRET>"
|
||
# 추가 키는 아래처럼 블록을 더 넣으면 됨:
|
||
# [[NaverConfig.keys]]
|
||
# id = "..."
|
||
# secret = "..."
|
||
|
||
# NCP NAVER API HUB (https://www.ncloud.com → Application Services > NAVER API HUB).
|
||
# 검색(블로그·뉴스·지식iN·이미지·지역·웹문서·백과·카페·오타변환·성인판별)·검색어 트렌드·쇼핑 인사이트.
|
||
# 쇼핑 '검색'(상품/가격)은 허브에도 없다 — 최저가 소스로는 쓸 수 없음.
|
||
# 키는 NCP 콘솔 > NAVER API HUB > Application > API 관리 > [인증 정보] 에서 발급.
|
||
[NaverApiHubConfig]
|
||
base_url = "https://naverapihub.apigw.ntruss.com"
|
||
client_id = "<NCP_CLIENT_ID>"
|
||
client_secret = "<NCP_CLIENT_SECRET>"
|
||
timeout_sec = 10.0
|
||
|
||
# AI 유사도 판정/검색어 생성 (OpenAI)
|
||
[OpenAIConfig]
|
||
api_key = "<OPENAI_API_KEY>"
|
||
model = "gpt-4o-mini"
|
||
|
||
# DECODO residential 프록시 (쿠팡 전용, 포트기반 sticky). 값 다 채우면 활성(비면 프록시 미사용).
|
||
[DecodoConfig]
|
||
host = "" # 예: gate.decodo.com
|
||
kr_host = "kr.decodo.com" # 한국 타깃 게이트웨이(네이버용). 비우면 host 사용
|
||
username = "" # 대시보드 USERNAME (예: sppd6a3ze3)
|
||
password = "" # 대시보드 PASSWORD
|
||
port_start = 0 # 예: 10001
|
||
port_end = 0 # 예: 10010 — 포트를 늘리면(계약 변경) 이 범위만 넓히면 됨(코드 무변경)
|
||
session_minutes = 10 # 대시보드 Sticky 지속시간(분)과 일치
|
||
cost_per_gb = 0.0 # DECODO 요금($/GB) — 검색 원가의 대역폭 비용 산정용(플랜에 맞게, 예 3.0)
|
||
ip_request_budget = 3 # IP당 요청 예산 — 도달 시 차단 전 선제 회전(0=비활성). 튜닝은 docs/database.md
|
||
port_cooldown_sec = 0 # 차단 감지 포트 격리 초. 0=자동 max(sticky, 30분)
|