fix real ip

This commit is contained in:
Yuriy Yuriev
2026-05-18 22:49:03 +07:00
parent 0b2f4e9021
commit 306bec1324
+105 -254
View File
@@ -20,22 +20,18 @@ from services.socks5_to_http_proxy import Socks5ProxyPool
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _get_proxy_geo(ip: str) -> dict: def _locale_for_ip(ip: str) -> str:
"""Возвращает geoip-данные для реального IP прокси в виде kwargs для AsyncCamoufox.""" """Возвращает BCP47 locale без script-тега по реальному IP прокси."""
try: try:
from camoufox.utils import get_geolocation from camoufox.utils import get_geolocation
geo = get_geolocation(ip) geo = get_geolocation(ip)
lang = geo.locale.language lang = geo.locale.language
region = geo.locale.region region = geo.locale.region
locale = f"{lang}-{region}" if lang and region else "en-US" if lang and region:
result = {"locale": locale} return f"{lang}-{region}"
if geo.timezone:
result["timezone"] = geo.timezone
if geo.longitude is not None and geo.latitude is not None:
result["geolocation"] = {"longitude": geo.longitude, "latitude": geo.latitude}
return result
except Exception: except Exception:
return {"locale": "en-US"} pass
return "en-US"
@dataclass @dataclass
@@ -57,13 +53,13 @@ class BrowserService:
Сервис для посещения страниц через Camoufox. Сервис для посещения страниц через Camoufox.
Поддерживает HTTP, SOCKS5 (через туннель) и прямое соединение. Поддерживает HTTP, SOCKS5 (через туннель) и прямое соединение.
""" """
def __init__(self, proxy_manager: ProxyManager): def __init__(self, proxy_manager: ProxyManager):
self.proxy_manager = proxy_manager self.proxy_manager = proxy_manager
self.socks5_pool = Socks5ProxyPool(idle_timeout=300) self.socks5_pool = Socks5ProxyPool(idle_timeout=300)
self.screenshots_dir = Path(settings.SCREENSHOTS_DIR) self.screenshots_dir = Path(settings.SCREENSHOTS_DIR)
self.screenshots_dir.mkdir(exist_ok=True) self.screenshots_dir.mkdir(exist_ok=True)
async def visit_page( async def visit_page(
self, self,
url: str, url: str,
@@ -75,85 +71,83 @@ class BrowserService:
settings.DEFAULT_MIN_READING, settings.DEFAULT_MIN_READING,
settings.DEFAULT_MAX_READING settings.DEFAULT_MAX_READING
) )
proxy = None
use_socks5 = False
try: for attempt in range(3):
if self.proxy_manager.has_proxies(): proxy = None
proxy = await self.proxy_manager.acquire_proxy(timeout=30) use_socks5 = False
real_proxy_ip = None try:
if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]: if self.proxy_manager.has_proxies():
proxy_config = await self.socks5_pool.get_proxy_config(proxy) proxy = await self.proxy_manager.acquire_proxy(timeout=30)
use_socks5 = True
# Извлекаем реальный IP прокси до туннелирования
real_proxy_ip = proxy.id.split("://")[-1].split(":")[0]
logger.info(f"Using SOCKS5 tunnel for: {proxy.id}")
elif proxy:
proxy_config = proxy.proxy_config
logger.info(f"Using HTTP proxy: {proxy.id}")
else:
proxy_config = None
logger.info("Direct connection")
if proxy_config: socks5_locale = None
# Camoufox при SOCKS5 видит 127.0.0.1 вместо реального IP прокси — if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]:
# geoip lookup делаем сами и передаём timezone/locale/geolocation явно, proxy_config = await self.socks5_pool.get_proxy_config(proxy)
# это полный эквивалент geoip=True но без внутреннего constraint'а camoufox use_socks5 = True
geo_kwargs = _get_proxy_geo(real_proxy_ip) if real_proxy_ip else {"locale": "en-US"} # При SOCKS5 camoufox видит 127.0.0.1 → geoip берёт IP сервера → ru-Cyrl-RU
kwargs = dict( # Делаем lookup по реальному IP прокси сами и передаём чистый locale
headless=True, real_ip = proxy.id.split("://")[-1].split(":")[0]
geoip=True, socks5_locale = _locale_for_ip(real_ip)
humanize=True, logger.info(f"Using SOCKS5 tunnel for: {proxy.id} locale={socks5_locale}")
exclude_addons=[DefaultAddons.UBO], elif proxy:
proxy=proxy_config, proxy_config = proxy.proxy_config
**geo_kwargs, logger.info(f"Using HTTP proxy: {proxy.id}")
) else:
try: proxy_config = None
async with AsyncCamoufox(**kwargs) as browser: logger.info("Direct connection")
result = await self._browse_page(
browser, url, if proxy_config:
proxy.id if proxy else "direct", camoufox_kwargs = dict(
reading_time headless=True,
) geoip=True,
except ValueError: humanize=True,
logger.warning("geoip fingerprint failed, retrying with en-US locale") exclude_addons=[DefaultAddons.UBO],
kwargs["locale"] = "en-US" proxy=proxy_config,
kwargs.pop("geolocation", None)
async with AsyncCamoufox(**kwargs) as browser:
result = await self._browse_page(
browser, url,
proxy.id if proxy else "direct",
reading_time
)
else:
async with AsyncCamoufox(
headless=True,
geoip=False,
humanize=True,
locale="ru-RU",
exclude_addons=[DefaultAddons.UBO]
) as browser:
result = await self._browse_page(
browser, url, "direct", reading_time
) )
if socks5_locale:
camoufox_kwargs["locale"] = socks5_locale
async with AsyncCamoufox(**camoufox_kwargs) as browser:
result = await self._browse_page(
browser, url,
proxy.id if proxy else "direct",
reading_time
)
else:
async with AsyncCamoufox(
headless=True,
geoip=False,
humanize=True,
locale="ru-RU",
exclude_addons=[DefaultAddons.UBO]
) as browser:
result = await self._browse_page(
browser, url, "direct", reading_time
)
if proxy: if proxy:
await self.proxy_manager.release_proxy(proxy, result.success) await self.proxy_manager.release_proxy(proxy, result.success)
if use_socks5: if use_socks5:
await self.socks5_pool.release(proxy) await self.socks5_pool.release(proxy)
return result return result
except ValueError as e:
logger.warning(f"Visit attempt {attempt + 1} fingerprint error, retrying with new proxy: {e}")
if proxy:
await self.proxy_manager.release_proxy(proxy, success=False)
if use_socks5:
await self.socks5_pool.release(proxy)
except Exception as e:
logger.error(f"Visit error: {e}", exc_info=True)
if proxy:
await self.proxy_manager.release_proxy(proxy, success=False)
if use_socks5:
await self.socks5_pool.release(proxy)
return VisitResult(url=url, success=False, error=str(e))
return VisitResult(url=url, success=False, error="All attempts failed (fingerprint error)")
except Exception as e:
logger.error(f"Visit error: {e}", exc_info=True)
if proxy:
await self.proxy_manager.release_proxy(proxy, success=False)
if use_socks5:
await self.socks5_pool.release(proxy)
return VisitResult(url=url, success=False, error=str(e))
async def _browse_page( async def _browse_page(
self, browser, url: str, proxy_info: str, reading_time: int self, browser, url: str, proxy_info: str, reading_time: int
) -> VisitResult: ) -> VisitResult:
@@ -219,31 +213,31 @@ class BrowserService:
) )
finally: finally:
await page.close() await page.close()
async def _move_mouse(self, page, task): async def _move_mouse(self, page, task):
"""Двигает мышь.""" """Двигает мышь."""
w, h = settings.VIEWPORT_WIDTH, settings.VIEWPORT_HEIGHT w, h = settings.VIEWPORT_WIDTH, settings.VIEWPORT_HEIGHT
try: try:
while not task.done(): while not task.done():
await page.mouse.move( await page.mouse.move(
random.randint(100, w-100), random.randint(100, w - 100),
random.randint(100, h-100) random.randint(100, h - 100)
) )
await asyncio.sleep(random.uniform(0.1, 0.3)) await asyncio.sleep(random.uniform(0.1, 0.3))
except: except Exception:
pass pass
async def _wait_redirect(self, page, initial_url): async def _wait_redirect(self, page, initial_url):
"""Ждет редирект.""" """Ждет редирект."""
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
start = loop.time() start = loop.time()
redirect = False redirect = False
current = initial_url current = initial_url
while loop.time() - start < settings.MAX_REDIRECT_WAIT: while loop.time() - start < settings.MAX_REDIRECT_WAIT:
await asyncio.sleep(2) await asyncio.sleep(2)
new_url = page.url new_url = page.url
if new_url != current: if new_url != current:
current = new_url current = new_url
redirect = True redirect = True
@@ -253,25 +247,25 @@ class BrowserService:
await self._move_mouse(page, task) await self._move_mouse(page, task)
await task await task
break break
await self._random_action(page) await self._random_action(page)
return page.url, redirect return page.url, redirect
async def _simulate_reading(self, page, duration): async def _simulate_reading(self, page, duration):
"""Симулирует чтение.""" """Симулирует чтение."""
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
start = loop.time() start = loop.time()
while loop.time() - start < duration: while loop.time() - start < duration:
await self._random_action(page) await self._random_action(page)
async def _random_action(self, page): async def _random_action(self, page):
"""Случайное действие.""" """Случайное действие."""
try: try:
action = random.choice(['move', 'scroll', 'pause']) action = random.choice(['move', 'scroll', 'pause'])
if action == 'move': if action == 'move':
x = random.randint(100, settings.VIEWPORT_WIDTH-100) x = random.randint(100, settings.VIEWPORT_WIDTH - 100)
y = random.randint(100, settings.VIEWPORT_HEIGHT-100) y = random.randint(100, settings.VIEWPORT_HEIGHT - 100)
await page.mouse.move(x, y) await page.mouse.move(x, y)
await asyncio.sleep(random.uniform(0.3, 1.0)) await asyncio.sleep(random.uniform(0.3, 1.0))
elif action == 'scroll': elif action == 'scroll':
@@ -279,9 +273,9 @@ class BrowserService:
await asyncio.sleep(random.uniform(0.3, 0.8)) await asyncio.sleep(random.uniform(0.3, 0.8))
else: else:
await asyncio.sleep(random.uniform(1, 3)) await asyncio.sleep(random.uniform(1, 3))
except: except Exception:
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
async def _take_screenshot(self, page, url): async def _take_screenshot(self, page, url):
"""Скриншот.""" """Скриншот."""
try: try:
@@ -293,7 +287,7 @@ class BrowserService:
except Exception as e: except Exception as e:
logger.error(f"Screenshot: {e}") logger.error(f"Screenshot: {e}")
return None return None
async def cleanup(self): async def cleanup(self):
"""Очистка с таймаутом.""" """Очистка с таймаутом."""
try: try:
@@ -302,25 +296,25 @@ class BrowserService:
logger.warning("SOCKS5 pool cleanup timeout") logger.warning("SOCKS5 pool cleanup timeout")
except Exception as e: except Exception as e:
logger.error(f"Cleanup error: {e}") logger.error(f"Cleanup error: {e}")
async def visit_page_from_twitch(self, url: str, channel: str, reading_time: int = None) -> VisitResult: async def visit_page_from_twitch(self, url: str, channel: str, reading_time: int = None) -> VisitResult:
if reading_time is None: if reading_time is None:
reading_time = random.randint(settings.DEFAULT_MIN_READING, settings.DEFAULT_MAX_READING) reading_time = random.randint(settings.DEFAULT_MIN_READING, settings.DEFAULT_MAX_READING)
proxy = None proxy = None
for attempt in range(2): # Две попытки с разными прокси for attempt in range(2):
try: try:
if self.proxy_manager.has_proxies(): if self.proxy_manager.has_proxies():
proxy = await self.proxy_manager.acquire_proxy(timeout=90) proxy = await self.proxy_manager.acquire_proxy(timeout=90)
if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]: if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]:
proxy_config = await self.socks5_pool.get_proxy_config(proxy) proxy_config = await self.socks5_pool.get_proxy_config(proxy)
elif proxy: elif proxy:
proxy_config = proxy.proxy_config proxy_config = proxy.proxy_config
else: else:
proxy_config = None proxy_config = None
browser_kwargs = { browser_kwargs = {
"headless": True, "headless": True,
"geoip": True, "geoip": True,
@@ -338,164 +332,21 @@ class BrowserService:
proxy.id if proxy else "direct", proxy.id if proxy else "direct",
reading_time reading_time
) )
if proxy: if proxy:
await self.proxy_manager.release_proxy(proxy, result.success) await self.proxy_manager.release_proxy(proxy, result.success)
if result.success: if result.success:
return result return result
except Exception as e: except Exception as e:
logger.warning(f"Attempt {attempt + 1} failed: {e}") logger.warning(f"Attempt {attempt + 1} failed: {e}")
if proxy: if proxy:
await self.proxy_manager.release_proxy(proxy, success=False) await self.proxy_manager.release_proxy(proxy, success=False)
await self.socks5_pool.release(proxy) await self.socks5_pool.release(proxy)
proxy = None proxy = None
if attempt < 1: if attempt < 1:
await asyncio.sleep(2) await asyncio.sleep(2)
return VisitResult(url=url, success=False, error="All attempts failed") return VisitResult(url=url, success=False, error="All attempts failed")
async def _browse_twitch_ref(self, browser, url: str, channel: str, proxy_info: str, reading_time: int) -> VisitResult:
"""Посещает страницу как переход с Twitch канала."""
page = await browser.new_page()
twitch_url = f"https://www.twitch.tv/{channel}"
try:
await page.set_viewport_size({"width": settings.VIEWPORT_WIDTH, "height": settings.VIEWPORT_HEIGHT})
# Заголовки имитирующие переход с Twitch
await page.set_extra_http_headers({
"Referer": twitch_url,
"Origin": "https://www.twitch.tv",
})
logger.info(f"📺 From Twitch: {channel}{url}")
# Переход с Referer
goto = asyncio.create_task(page.goto(url, referer=twitch_url, wait_until="commit", timeout=30000))
await self._move_mouse(page, goto)
await goto
initial_url = page.url
final_url, redirect = await self._wait_redirect(page, initial_url)
await self._simulate_reading(page, reading_time)
return VisitResult(
url=url, initial_url=initial_url, final_url=final_url,
redirect_occurred=redirect, proxy_info=proxy_info,
reading_time=reading_time, success=True
)
except Exception as e:
return VisitResult(url=url, success=False, error=str(e))
finally:
await page.close()
async def _browse_from_twitch(
self,
browser,
url: str,
channel: str,
proxy_info: str,
reading_time: int
) -> VisitResult:
"""
Эмулирует переход с Twitch:
1. Открывает страницу стримера
2. Прокручивает чат
3. Переходит по ссылке
"""
page = await browser.new_page()
twitch_url = f"https://www.twitch.tv/{channel}"
try:
await page.set_viewport_size({
"width": settings.VIEWPORT_WIDTH,
"height": settings.VIEWPORT_HEIGHT
})
# === Шаг 1: Заходим на Twitch ===
logger.info(f"📺 Opening Twitch: {twitch_url}")
goto_task = asyncio.create_task(
page.goto(twitch_url, wait_until="commit", timeout=30000)
)
await self._move_mouse(page, goto_task)
await goto_task
# Ждем загрузки
await asyncio.sleep(random.uniform(2, 4))
# === Шаг 2: Имитируем просмотр стрима ===
logger.info("👀 Watching stream...")
# Прокручиваем страницу как будто смотрим
for _ in range(random.randint(2, 4)):
scroll = random.randint(200, 500)
await page.evaluate(f"window.scrollBy(0, {scroll})")
await asyncio.sleep(random.uniform(0.5, 1.5))
# Двигаем мышь (как будто читаем чат)
w, h = settings.VIEWPORT_WIDTH, settings.VIEWPORT_HEIGHT
for _ in range(random.randint(3, 6)):
x = random.randint(100, w - 100)
y = random.randint(100, h - 100)
await page.mouse.move(x, y)
await asyncio.sleep(random.uniform(0.3, 0.8))
# === Шаг 3: Переходим по ссылке ===
logger.info(f"🔗 Clicking link: {url}")
# Создаем новую вкладку для перехода (как target="_blank")
# Или просто переходим с Referer
await page.evaluate(f"""
window.open('{url}', '_blank');
""")
# Ждем открытия новой вкладки
await asyncio.sleep(2)
# Получаем новую вкладку
pages = await browser.pages()
if len(pages) > 1:
new_page = pages[-1]
await new_page.bring_to_front()
else:
# Если вкладка не открылась - переходим в этой же
new_page = page
await new_page.goto(url, referer=twitch_url, wait_until="commit")
initial_url = new_page.url
logger.info(f"📍 Landed: {initial_url}")
# === Шаг 4: Ждем редирект ===
final_url, redirect = await self._wait_redirect(new_page, initial_url)
# === Шаг 5: Читаем страницу ===
await self._simulate_reading(new_page, reading_time)
# Закрываем новую вкладку если она отдельная
if new_page != page:
await new_page.close()
# Возвращаемся на Twitch и закрываем
await page.close()
return VisitResult(
url=url,
initial_url=initial_url,
final_url=final_url,
redirect_occurred=redirect,
proxy_info=proxy_info,
reading_time=reading_time,
success=True
)
except Exception as e:
logger.error(f"Browse from Twitch error: {e}")
try:
await page.close()
except:
pass
return VisitResult(url=url, reading_time=reading_time, success=False, error=str(e))