datacenter IP + Akamai 대응. 매 요청 IP 변경은 쿠키-IP 불일치로 재챌린지를 유발하므로, sticky 세션(일정 시간 같은 IP) + 주기적 회전 방식 사용. - search/proxy.DecodoProxy: username 에 -session-<시간창id>-sessionduration-<분> 부착 → 창 안에선 같은 IP, 창이 지나면 새 IP. 자격증명은 .env(DECODO_*), 4개 다 있어야 활성 - coupang/adapter: proxy 주입 + 세션창 경과 시 브라우저 재기동(IP 회전). 무프록시면 직접 연결 - worker_main: 쿠팡에만 DecodoProxy 경유(네이버는 공식 API라 미적용) - .env.example: DECODO_HOST/PORT/USERNAME/PASSWORD/SESSION_MINUTES 슬롯 - tests: 활성조건·sticky username·server형식·세션창 회전 4건 → 전체 43/43 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 lines
2.0 KiB
Python
48 lines
2.0 KiB
Python
"""DECODO(구 Smartproxy) residential 프록시 제공자.
|
|
|
|
쿠팡(Akamai) 전용. 핵심: **sticky 세션 + 주기적 회전**.
|
|
매 요청 IP 변경은 Akamai 가 '쿠키-IP 불일치'로 재챌린지/차단하므로 금물.
|
|
→ 일정 시간(session_minutes) 같은 IP 유지 후 회전한다.
|
|
|
|
DECODO 는 username 에 세션 파라미터를 붙여 sticky 를 구현한다:
|
|
<username>-session-<id>-sessionduration-<minutes>
|
|
세션 id 를 '시간창(now // window)'으로 만들면 창 안에선 같은 IP, 창이 바뀌면 새 IP 가 된다.
|
|
자격증명은 .env(DECODO_*) 에서 로드(시크릿).
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
|
|
|
|
class DecodoProxy:
|
|
def __init__(self, host=None, port=None, username=None, password=None, session_minutes=None, sticky=True):
|
|
self.host = host or os.environ.get("DECODO_HOST")
|
|
self.port = port or os.environ.get("DECODO_PORT")
|
|
self.username = username or os.environ.get("DECODO_USERNAME")
|
|
self.password = password or os.environ.get("DECODO_PASSWORD")
|
|
self.session_minutes = int(session_minutes or os.environ.get("DECODO_SESSION_MINUTES") or 10)
|
|
self.sticky = sticky
|
|
|
|
@property
|
|
def enabled(self) -> bool:
|
|
return all([self.host, self.port, self.username, self.password])
|
|
|
|
def _session_id(self) -> int:
|
|
"""시간창 기반 세션 id — 같은 창에선 동일, 창이 지나면 증가(→새 IP)."""
|
|
return int(time.time() // (self.session_minutes * 60))
|
|
|
|
def _session_username(self) -> str:
|
|
if not self.sticky:
|
|
return self.username
|
|
return f"{self.username}-session-{self._session_id()}-sessionduration-{self.session_minutes}"
|
|
|
|
def playwright_proxy(self) -> dict | None:
|
|
"""Playwright launch(proxy=...) 용 설정. 비활성 시 None(프록시 미사용)."""
|
|
if not self.enabled:
|
|
return None
|
|
return {
|
|
"server": f"http://{self.host}:{self.port}",
|
|
"username": self._session_username(),
|
|
"password": self.password,
|
|
}
|