feature: действие typing — ввод как у человека, кириллица (#14)
fill остаётся мгновенным. typing подводит курсор по кривой, кликает в поле, стирает старое значение и набирает текст посимвольно с паузами; символы вне раскладки (кириллица) больше не роняют ввод. Значение typing маскируется в логах. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
49a2f3024a
commit
1075267d27
8 files changed
+74
-25
No files matched your search
+41
-21
@@ -124,28 +124,48 @@ async def wait_for_page_stability(page: Page, load_dom: bool, network_idle: bool
|
||||
await wait_for_networkidle(page)
|
||||
|
||||
|
||||
async def typing(page: Page, locator: str, text: str):
|
||||
element = page.locator(locator)
|
||||
def _key_delay_ms(char: str) -> float:
|
||||
"""Пауза после символа: строчные латинские набираются быстрее всего, остальное — медленнее."""
|
||||
if char in string.ascii_lowercase:
|
||||
delays = (50, 100)
|
||||
elif char in string.ascii_uppercase or char in string.digits:
|
||||
delays = (100, 150)
|
||||
elif char in string.punctuation:
|
||||
delays = (100, 200)
|
||||
else:
|
||||
# кириллица и прочие символы, пробел
|
||||
delays = (60, 160)
|
||||
return random.uniform(*delays)
|
||||
|
||||
|
||||
async def typing(page: Page, locator: str, text: str) -> None:
|
||||
"""Ввод как у человека: курсор подводится к полю по кривой, клик в случайную точку поля,
|
||||
старое значение стирается, текст набирается посимвольно с паузами."""
|
||||
element = page.locator(locator).first
|
||||
|
||||
await element.scroll_into_view_if_needed()
|
||||
assert await element.is_visible()
|
||||
assert await element.is_enabled()
|
||||
assert await element.is_editable()
|
||||
await element.focus()
|
||||
await element.click()
|
||||
if not await element.is_visible():
|
||||
raise ValueError(f"element is not visible: {locator}")
|
||||
if not await element.is_editable():
|
||||
raise ValueError(f"element is not editable: {locator}")
|
||||
|
||||
box = await element.bounding_box()
|
||||
if box is None:
|
||||
raise ValueError(f"element has no box: {locator}")
|
||||
await click_human(
|
||||
page,
|
||||
box["x"] + box["width"] * random.uniform(0.2, 0.8),
|
||||
box["y"] + box["height"] * random.uniform(0.3, 0.7),
|
||||
)
|
||||
|
||||
if await element.input_value():
|
||||
await page.keyboard.press("ControlOrMeta+A")
|
||||
await page.wait_for_timeout(random.uniform(80, 200))
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.wait_for_timeout(random.uniform(100, 250))
|
||||
|
||||
for char in text:
|
||||
await page.keyboard.press(char)
|
||||
|
||||
if char in string.ascii_lowercase:
|
||||
delays = (50, 100)
|
||||
elif char in string.ascii_uppercase:
|
||||
delays = (100, 150)
|
||||
elif char in string.digits:
|
||||
delays = (100, 150)
|
||||
elif char in string.punctuation:
|
||||
delays = (100, 200)
|
||||
else:
|
||||
raise SystemError("character not supported: {}".format(char))
|
||||
|
||||
await page.wait_for_timeout(random.uniform(*delays))
|
||||
# keyboard.type даёт keydown/keypress/keyup для клавиш раскладки и insertText для остальных
|
||||
# символов (кириллица и т.п.); press() на них падал
|
||||
await page.keyboard.type(char)
|
||||
await page.wait_for_timeout(_key_delay_ms(char))
|
||||
@@ -4,6 +4,7 @@ import time
|
||||
|
||||
from patchright.async_api import Page
|
||||
|
||||
from antibot.humanize import typing as human_typing
|
||||
from antibot.orchestrator import pass_challenges
|
||||
from captcha import load_captcha
|
||||
from api.schemas import ActionRequest
|
||||
@@ -32,6 +33,12 @@ async def fill(page: Page, act: ActionRequest, **kwargs) -> str | None:
|
||||
await page.fill(act.selector, act.value)
|
||||
|
||||
|
||||
@register
|
||||
async def typing(page: Page, act: ActionRequest, **kwargs) -> str | None:
|
||||
"""В отличие от fill — как человек: подвод мыши, клик, посимвольный ввод с паузами (#14)."""
|
||||
await human_typing(page, act.selector, act.value)
|
||||
|
||||
|
||||
@register
|
||||
async def captcha(page: Page, act: ActionRequest, captcha_key: str, **kwargs) -> str | None:
|
||||
captcha_solver = load_captcha(captcha_key, act.value)
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ def safe_request_dump(payload: SolveRequest) -> str:
|
||||
if payload.cookies:
|
||||
data["cookies"] = [c.get("name") for c in payload.cookies]
|
||||
for act in data["actions"]:
|
||||
if act["type"] == "fill" and "value" in act:
|
||||
if act["type"] in ("fill", "typing") and "value" in act:
|
||||
act["value"] = "***"
|
||||
return json.dumps(data, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ PROXY_SCHEMES = ("http", "https", "socks5")
|
||||
# тип действия -> обязательные поля (кроме type)
|
||||
_REQUIRED_FIELDS: dict[str, tuple[str, ...]] = {
|
||||
"fill": ("selector", "value"),
|
||||
"typing": ("selector", "value"),
|
||||
"captcha": ("selector", "value"),
|
||||
"delay": ("value",),
|
||||
"mouse_move": ("selector",),
|
||||
|
||||
Reference in new issue
Block a user