o2o-negosium-original/lps/services/search/util.py
민헌 e27b73c018 feat(lps): 쿠팡 검색 어댑터 — Akamai 우회(Patchright) + 정규화 파서
소스 어댑터 패턴으로 크롤링을 시작한다. 쿠팡은 Akamai Bot Manager 의
JS 행동 챌린지라 curl_cffi 단독 불가 → Patchright(스텔스 Playwright) +
실제 Chrome(channel=chrome)으로 챌린지를 통과하고 selectolax 로 파싱한다.

- contract: SearchAdapter(ABC)·NormalizedProduct·AdapterHealth·AdapterError
- coupang: adapter(브라우저 재사용, 챌린지 1회)·parser(순수)·selectors(외부화, webpack 해시 prefix 매칭)
- 공용: rate_limiter(2~8s 랜덤)·proxy(무프록시 off 인터페이스)·util.parse_price
- 가격 파싱: 단위가격 '(1개당 44,400원)'의 앞 '1' 오인 방지 — '원' 앞 숫자 앵커링
- deps: curl_cffi·selectolax·patchright(nodriver 는 py3.14 버그로 대체)
- tests: 파서 회귀(소형 fixture) — 라이브 2회 검색 + 파서 3/3 통과

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:59:48 +09:00

19 lines
542 B
Python

"""검색 어댑터 공용 순수 유틸(부작용 없음). 레퍼런스의 parse_price 를 정리 이식."""
import re
_NUM = re.compile(r"[\d,]+")
def parse_price(value) -> int | None:
"""'44,900원', 44900, '44.900' 등 다형 입력 → int. 실패 시 None."""
if value is None:
return None
if isinstance(value, (int, float)):
return int(value)
m = _NUM.search(str(value))
if not m:
return None
digits = m.group(0).replace(",", "")
return int(digits) if digits.isdigit() else None