feature: переезд на хром
This commit is contained in:
1 parent
714cf9f922
commit
f2d4d441f4
6 files changed
+355
-102
No files matched your search
+100
-27
@@ -1,34 +1,11 @@
|
||||
"""
|
||||
человекоподобные движения мыши (безье+ускорение), тайминги кликов
|
||||
|
||||
Camoufox — это форк Firefox, заточенный под антидетект-автоматизацию (спуфинг fingerprint'а, WebGL, canvas,
|
||||
шрифтов и т.д.), с Python-обёрткой поверх Playwright-протокола (Juggler).
|
||||
|
||||
У него есть параметр humanize при запуске:
|
||||
|
||||
```
|
||||
from camoufox.sync_api import Camoufox
|
||||
|
||||
with Camoufox(humanize=True) as browser:
|
||||
...
|
||||
```
|
||||
|
||||
Или с указанием максимального времени на движение:
|
||||
|
||||
```
|
||||
Camoufox(humanize=1.5) # курсор доберётся до цели не более чем за 1.5 сек
|
||||
```
|
||||
|
||||
Когда humanize включён, при вызовах вроде page.click() / locator.click() библиотека:
|
||||
|
||||
- строит траекторию курсора от текущей позиции до цели не по прямой, а по кривой (используется подход,
|
||||
похожий на ghost-cursor — Безье-путь с шумом),
|
||||
- разбивает движение на промежуточные точки с нелинейной скоростью (ускорение/замедление к цели, как у реальной руки),
|
||||
- добавляет небольшую случайную задержку перед самим кликом.
|
||||
|
||||
Это реализовано на уровне их собственного форка драйвера, а не через голый Playwright API — обычный Playwright (
|
||||
page.click()) телепортирует курсор мгновенно и такого не делает.
|
||||
Раньше это частично закрывал Camoufox (форк Firefox с движением курсора по кривой при humanize=True) —
|
||||
после перехода на голый Playwright + Chromium это ушло: обычный page.click()/locator.click() телепортирует
|
||||
курсор мгновенно. Функции ниже — единственный источник человекоподобности сейчас.
|
||||
"""
|
||||
import math
|
||||
import random
|
||||
import string
|
||||
from typing import Optional
|
||||
@@ -36,6 +13,102 @@ from typing import Optional
|
||||
from playwright.async_api import TimeoutError
|
||||
from playwright.async_api import Page
|
||||
|
||||
# последняя известная позиция курсора на страницу — Playwright не даёт её прочитать из браузера,
|
||||
# поэтому считаем сами, чтобы следующее движение начиналось не из (0, 0), а оттуда, где реально
|
||||
# остановились в прошлый раз
|
||||
_last_mouse_pos: dict[int, tuple[float, float]] = {}
|
||||
|
||||
|
||||
def _ease_in_out(t: float) -> float:
|
||||
"""smoothstep: медленный старт, разгон к середине, торможение к цели — как у реальной руки."""
|
||||
return t * t * (3 - 2 * t)
|
||||
|
||||
|
||||
def _cubic_bezier(p0, c1, c2, p1, t: float) -> tuple[float, float]:
|
||||
mt = 1 - t
|
||||
x = mt ** 3 * p0[0] + 3 * mt ** 2 * t * c1[0] + 3 * mt * t ** 2 * c2[0] + t ** 3 * p1[0]
|
||||
y = mt ** 3 * p0[1] + 3 * mt ** 2 * t * c1[1] + 3 * mt * t ** 2 * c2[1] + t ** 3 * p1[1]
|
||||
return x, y
|
||||
|
||||
|
||||
def _control_points(x0, y0, x1, y1):
|
||||
"""Два контрольных узла, смещённых перпендикулярно линии движения — даёт дугу вместо прямой,
|
||||
как у настоящего движения руки/трекпада, а не у синтетического teleport'а."""
|
||||
dx, dy = x1 - x0, y1 - y0
|
||||
dist = math.hypot(dx, dy) or 1.0
|
||||
nx, ny = -dy / dist, dx / dist
|
||||
|
||||
curve = random.uniform(0.08, 0.28) * dist * random.choice((-1, 1))
|
||||
t1 = random.uniform(0.2, 0.4)
|
||||
t2 = random.uniform(0.6, 0.8)
|
||||
|
||||
c1 = (x0 + dx * t1 + nx * curve, y0 + dy * t1 + ny * curve)
|
||||
c2 = (x0 + dx * t2 + nx * curve * random.uniform(0.5, 1.0), y0 + dy * t2 + ny * curve * random.uniform(0.5, 1.0))
|
||||
return c1, c2
|
||||
|
||||
|
||||
def _random_start_position(page: Page) -> tuple[float, float]:
|
||||
vp = page.viewport_size or {"width": 1280, "height": 720}
|
||||
return (
|
||||
random.uniform(vp["width"] * 0.3, vp["width"] * 0.7),
|
||||
random.uniform(vp["height"] * 0.3, vp["height"] * 0.7),
|
||||
)
|
||||
|
||||
|
||||
async def move_mouse_human(
|
||||
page: Page,
|
||||
x: float,
|
||||
y: float,
|
||||
*,
|
||||
steps: Optional[int] = None,
|
||||
min_duration: float = 0.25,
|
||||
max_duration: float = 0.9,
|
||||
) -> None:
|
||||
"""Двигает курсор к (x, y) по кривой Безье с ease-in-out таймингом и лёгким дрожанием на
|
||||
промежуточных точках, вместо page.mouse.move(x, y), которое телепортирует курсор мгновенно.
|
||||
|
||||
Начальная точка — последняя известная позиция курсора на этой странице (или случайная точка
|
||||
у центра вьюпорта, если это первое движение в сессии)."""
|
||||
start = _last_mouse_pos.get(id(page)) or _random_start_position(page)
|
||||
c1, c2 = _control_points(start[0], start[1], x, y)
|
||||
|
||||
dist = math.hypot(x - start[0], y - start[1])
|
||||
if steps is None:
|
||||
steps = max(8, min(60, int(dist / 8)))
|
||||
|
||||
duration = random.uniform(min_duration, max_duration)
|
||||
step_delay_ms = (duration / steps) * 1000
|
||||
|
||||
for i in range(1, steps + 1):
|
||||
t = _ease_in_out(i / steps)
|
||||
px, py = _cubic_bezier(start, c1, c2, (x, y), t)
|
||||
if i < steps:
|
||||
px += random.uniform(-1.5, 1.5)
|
||||
py += random.uniform(-1.5, 1.5)
|
||||
print("Move:", px, py)
|
||||
await page.mouse.move(px, py)
|
||||
await page.wait_for_timeout(step_delay_ms * random.uniform(0.7, 1.3))
|
||||
|
||||
_last_mouse_pos[id(page)] = (x, y)
|
||||
|
||||
|
||||
async def click_human(
|
||||
page: Page,
|
||||
x: float,
|
||||
y: float,
|
||||
*,
|
||||
button: str = "left",
|
||||
**move_kwargs,
|
||||
) -> None:
|
||||
"""move_mouse_human + клик с человеческой задержкой между наведением и down/up, вместо
|
||||
мгновенного page.mouse.click(x, y)."""
|
||||
await move_mouse_human(page, x, y, **move_kwargs)
|
||||
await page.wait_for_timeout(random.uniform(50, 180))
|
||||
await page.mouse.down(button=button)
|
||||
await page.wait_for_timeout(random.uniform(40, 99))
|
||||
await page.mouse.up(button=button)
|
||||
await move_mouse_human(page, x-50, y+50, **move_kwargs)
|
||||
|
||||
|
||||
async def wait_for_networkidle(page: Page, timeout: Optional[int] = None):
|
||||
"""Wait for the page to become idle (no network activity) even if there are never-ending requests."""
|
||||
|
||||
Reference in new issue
Block a user