feature: переезд на хром
This commit is contained in:
1 parent
714cf9f922
commit
f2d4d441f4
6 files changed
+355
-102
No files matched your search
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Резолвинг геолокации/таймзоны исходящего IP через сам прокси (не локальный IP), с кэшем в файле.
|
||||
|
||||
Раньше это делал Camoufox (geoip=True) под капотом. Смысл: если браузер репортит IP из
|
||||
одной страны/города (через прокси), а navigator.timezone/geolocation — из другой, это
|
||||
явное рассогласование, которое легко ловится антиботом. Поэтому таймзону/координаты нужно
|
||||
брать по тому же IP, что увидит целевой сайт — то есть запрашивать через прокси, а не с
|
||||
локальной машины.
|
||||
|
||||
Кэш нужен по двум причинам: у ipinfo.io есть квота на бесплатные запросы, и результат для
|
||||
одного и того же прокси (пока он не сменил exit-IP) не меняется от запроса к запросу — незачем
|
||||
дёргать внешний сервис на каждый /v1/solve.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from playwright.async_api import ProxySettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
IPINFO_URL = "https://ipinfo.io/json"
|
||||
DEFAULT_CACHE_FILE = Path(__file__).resolve().parent.parent.parent / "extra" / "geoip_cache.json"
|
||||
DEFAULT_TTL = 24 * 3600 # прокси может сменить exit-IP — не кэшируем навечно
|
||||
|
||||
_cache_lock = asyncio.Lock()
|
||||
|
||||
|
||||
def proxy_settings_to_url(proxy: ProxySettings) -> str:
|
||||
server = proxy["server"]
|
||||
username = proxy.get("username")
|
||||
password = proxy.get("password")
|
||||
if not username:
|
||||
return server
|
||||
scheme, _, rest = server.partition("://")
|
||||
return f"{scheme}://{username}:{password}@{rest}"
|
||||
|
||||
|
||||
def _load_cache(cache_file: Path) -> dict:
|
||||
if not cache_file.exists():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(cache_file.read_text())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return {}
|
||||
|
||||
|
||||
def _save_cache(cache_file: Path, cache: dict) -> None:
|
||||
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_file.write_text(json.dumps(cache, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
async def resolve_geo(
|
||||
proxy: ProxySettings,
|
||||
timeout: float = 10.0,
|
||||
cache_file: Path = DEFAULT_CACHE_FILE,
|
||||
ttl: float = DEFAULT_TTL,
|
||||
) -> dict | None:
|
||||
"""Возвращает {"timezone_id", "geolocation": {"latitude", "longitude"}, "country", "ip"}
|
||||
по IP, который виден снаружи через прокси, или None, если резолв не удался и в кэше
|
||||
тоже ничего нет — в этом случае вызывающий код просто не переопределяет timezone/geolocation.
|
||||
|
||||
Язык (locale) сюда намеренно не подмешиваем: Accept-Language — это предпочтение пользователя,
|
||||
а не производная от геолокации IP, и его уже согласованно генерирует fingerprint (см.
|
||||
engine/fingerprint.py). Подменять его на "en-<COUNTRY>" было бы отдельным, никак не
|
||||
связанным с реальным поведением допущением."""
|
||||
key = proxy_settings_to_url(proxy)
|
||||
|
||||
async with _cache_lock:
|
||||
cache = _load_cache(cache_file)
|
||||
entry = cache.get(key)
|
||||
if entry and (time.time() - entry["ts"]) < ttl:
|
||||
return entry["data"]
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(proxy=key, timeout=timeout) as client:
|
||||
resp = await client.get(IPINFO_URL)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
except Exception as e:
|
||||
log.warning("не удалось резолвнуть geoip через прокси: %r", e)
|
||||
# сеть/квота ipinfo могли отказать разово — лучше отдать протухший кэш, чем ничего,
|
||||
# если прокси тот же самый (скорее всего и IP тот же)
|
||||
if entry:
|
||||
log.info("используем протухший geoip-кэш из-за сбоя запроса")
|
||||
return entry["data"]
|
||||
return None
|
||||
|
||||
loc = data.get("loc") # "lat,lon"
|
||||
geolocation = None
|
||||
if loc and "," in loc:
|
||||
lat_str, lon_str = loc.split(",", 1)
|
||||
try:
|
||||
geolocation = {"latitude": float(lat_str), "longitude": float(lon_str)}
|
||||
except ValueError:
|
||||
geolocation = None
|
||||
|
||||
result = {
|
||||
"ip": data.get("ip"),
|
||||
"country": data.get("country"), # "RU", "US", ...
|
||||
"timezone_id": data.get("timezone"),
|
||||
"geolocation": geolocation,
|
||||
}
|
||||
|
||||
async with _cache_lock:
|
||||
cache = _load_cache(cache_file)
|
||||
cache[key] = {"ts": time.time(), "data": result}
|
||||
_save_cache(cache_file, cache)
|
||||
|
||||
return result
|
||||
Reference in new issue
Block a user