improve scraper runtime

This commit is contained in:
qananasikq
2026-04-08 13:30:45 +03:00
parent 21bdf17f35
commit f96e5ca5f8
9 changed files with 207 additions and 21 deletions

View File

@@ -11,6 +11,7 @@ logger = logging.getLogger("iaai_scraper.browser")
def _build_init_script() -> str:
"""JS-патч признаков автоматизации."""
# Подмешиваем базовые browser fingerprint overrides.
hardware_concurrency = random.choice([4, 8, 12, 16])
device_memory = random.choice([4, 8, 16])
languages = ["en-US", "en"]
@@ -61,11 +62,12 @@ def _build_init_script() -> str:
class BrowserFactory:
def __init__(self, settings: Settings) -> None:
# Фабрика браузера и контекстов на основе env-настроек.
self.settings = settings
def create_browser(self, playwright: Playwright) -> Browser:
# пробуем Chrome, если нет — Chromium
launch_kwargs = {
launch_kwargs: dict = {
"headless": self.settings.headless,
"args": [
"--disable-blink-features=AutomationControlled",
@@ -74,6 +76,10 @@ class BrowserFactory:
"--disable-features=IsolateOrigins,site-per-process",
],
}
proxy_dict = self.settings.proxy.to_playwright_dict()
if proxy_dict:
launch_kwargs["proxy"] = proxy_dict
logger.info("Using proxy: %s", self.settings.proxy.server)
try:
logger.info("Trying to launch real Chrome channel")
return playwright.chromium.launch(channel="chrome", **launch_kwargs)
@@ -83,6 +89,7 @@ class BrowserFactory:
def create_context(self, browser: Browser, storage_state: str | None = None) -> BrowserContext:
# рандом viewport/timezone под каждый контекст
# Контекст изолирует cookies, headers и fingerprint-параметры.
viewport = random.choice(self.settings.fingerprint.viewport_presets)
timezone_id = random.choice(self.settings.fingerprint.timezone_candidates)
color_scheme = random.choice(["light", "dark"])

View File

@@ -23,6 +23,7 @@ class NetworkCapture:
_origin: str | None = None
def attach(self, page: Page) -> None:
# Подписываемся на request/response события страницы.
try:
self._origin = urlparse(page.url).netloc.lower() or None
except Exception:
@@ -31,6 +32,7 @@ class NetworkCapture:
page.on("response", self._on_response)
def _is_same_origin(self, url: str) -> bool:
# При необходимости ограничиваемся same-origin и доменами IAAI.
if not self.settings.gentle.capture_same_origin_only or not self._origin:
return True
netloc = urlparse(url).netloc.lower()
@@ -45,6 +47,7 @@ class NetworkCapture:
if len(self.requests) >= self.settings.gentle.max_requests:
return
key = f"{request.method}:{request.url}:{request.post_data or ''}"
# Дедупликация одинаковых запросов.
if key in self._seen_req:
return
self._seen_req.add(key)
@@ -92,6 +95,7 @@ class NetworkCapture:
@staticmethod
def _categorize(url: str) -> str:
# Простая эвристика для разбивки ответов по смыслу.
low = url.lower()
mapping = {
"images": ["image", "media", "photos", "gallery"],

View File

@@ -10,9 +10,11 @@ class HumanPacer:
"""Random паузы между действиями."""
def __init__(self, settings: Settings) -> None:
# Инкапсулирует все задержки между действиями браузера.
self.settings = settings
def pause(self, min_s: float, max_s: float) -> None:
# Базовая случайная пауза в заданном диапазоне.
if self.settings.pace.enabled:
time.sleep(random.uniform(min_s, max_s))
@@ -35,6 +37,7 @@ class HumanPacer:
self.pause(self.settings.pace.after_page_change_min_s, self.settings.pace.after_page_change_max_s)
def move_mouse_to(self, page: Page, locator: Locator) -> None:
# Небольшое движение мыши к элементу перед кликом.
try:
box = locator.bounding_box()
except Exception: