네이버 폴백용 오픈마켓 크롤러 3종. 4중 복제를 피해 BrowserSearchAdapter 베이스로 브라우저 수명·프록시 회전·차단감지+IP회전 재시도·감지기록을 공유하고, 사이트 차이는 훅(_search_url/_parse/ready_selector/block_markers)으로 분리. - browser_base: patchright 공통 machinery(쿠팡 machinery 일반화) - card_parser: 오픈마켓 공용 카드 파서(CardConfig 주입) + util(extract_price/clean_name/shipping) - esm: G마켓·옥션(eBay 백엔드 공유, site 파라미터 1개로 커버, '잠시만' 챌린지 대기) - st11: 11번가 PC(공식API 판매자계정 필요라 불가). 지연로딩 → scroll_steps, '.c-card-item' 토큰 - 학습 반영: 오픈마켓은 리소스차단 OFF(렌더/챌린지 깨짐), 스턱 챌린지='잠시만' 잔존 시 차단 간주→IP회전 - 파서 결정론 테스트 7종(실렌더 카드 3개씩 경량 fixture). 라이브 검증: 신선 IP로 3사 모두 파싱 성공 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
58 lines
2.2 KiB
Python
58 lines
2.2 KiB
Python
"""검색 어댑터 공용 순수 유틸(부작용 없음). 레퍼런스의 parse_price 를 정리 이식."""
|
|
|
|
import re
|
|
|
|
_NUM = re.compile(r"[\d,]+")
|
|
_WON = re.compile(r"([\d,]+)\s*원") # '원' 바로 앞 숫자(단위가격/퍼센트 오인 방지)
|
|
_NUM3 = re.compile(r"([\d,]{3,})") # 3자리+ 숫자 토큰(가격 후보)
|
|
_SHIP_FEE = re.compile(r"배송비\s*([\d,]+)\s*원")
|
|
_LABELS = ("상품명", "브랜드명", "판매가", "할인가") # 오픈마켓 카드의 a11y 라벨 프리픽스
|
|
|
|
|
|
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
|
|
|
|
|
|
def extract_price(text: str) -> int | None:
|
|
"""가격 노드 텍스트 → int. '원' 앵커 우선, 없으면 첫 3자리+ 숫자.
|
|
('1%할인가44,540원' → 44540, '44,540' → 44540). 실패 시 None."""
|
|
if not text:
|
|
return None
|
|
m = _WON.search(text)
|
|
if m:
|
|
return int(m.group(1).replace(",", ""))
|
|
m = _NUM3.search(text)
|
|
return int(m.group(1).replace(",", "")) if m else None
|
|
|
|
|
|
def clean_name(text: str) -> str:
|
|
"""오픈마켓 카드 이름에서 a11y 라벨('상품명'/'브랜드명' 등) 프리픽스를 제거."""
|
|
s = (text or "").strip()
|
|
for lab in _LABELS:
|
|
s = s.replace(lab, " ")
|
|
return re.sub(r"\s+", " ", s).strip()
|
|
|
|
|
|
def shipping_from_text(text: str, name: str | None = None) -> tuple[int | None, str | None]:
|
|
"""카드 텍스트에서 (배송비, 배송유형) 추출 — 오픈마켓 공용(로켓 없음).
|
|
'무료배송'→(0,'free'), '배송비 X원'→(X,'paid'), 그 외 (None, None).
|
|
상품명에 '무료배송'이 들어간 오탐 방지를 위해 이름 제거 후 매칭."""
|
|
t = text or ""
|
|
if name:
|
|
t = t.replace(name, " ")
|
|
if "무료배송" in t:
|
|
return 0, "free"
|
|
m = _SHIP_FEE.search(t)
|
|
if m:
|
|
return int(m.group(1).replace(",", "")), "paid"
|
|
return None, None
|