"""DECODO(구 Smartproxy) residential 프록시 제공자. 쿠팡(Akamai) 전용. 핵심: **sticky 세션 + 주기적 회전**. 매 요청 IP 변경은 Akamai 가 '쿠키-IP 불일치'로 재챌린지/차단하므로 금물. → 일정 시간(session_minutes) 같은 IP 유지 후 회전한다. DECODO 는 username 에 세션 파라미터를 붙여 sticky 를 구현한다: -session--sessionduration- 세션 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, }