feature: действие captcha value=image — капча-картинка через 2captcha (#109)

Снимок элемента картинки на текущей странице (без повторной загрузки src) уходит
в 2captcha как обычная капча (method=base64), ответ набирается в поле target
как у typing и возвращается в result действия. Необязательные подсказки:
min_len, max_len, numeric, lang, case_sensitive. Клиент: Action.captcha_image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
protokeyandClaude Opus 5.5 committed 2026-10-14 16:17:25 +04:00
1 parent 502b6e1003
commit 5ed2cfbb3d
15 files changed
+301 -9

No files matched your search

+12 -1
View File
@@ -100,5 +100,16 @@ async def main():
## Действия
`Action.fill`, `typing`, `click`, `submit`, `mouse_move`, `delay(seconds)`, `wait_for(selector, timeout=None)`,
`inner_html`, `inner_text`, `captcha(selector, kind)`, `pass_challenge()`. Вместо `Action` можно передать dict в формате
`inner_html`, `inner_text`, `captcha(selector, kind)`, `captcha_image(selector, target, **hints)`, `pass_challenge()`. Вместо `Action` можно передать dict в формате
API: `{"type": "click", "selector": "#a"}`.
Капча-картинка («код с картинки»): снимок элемента уходит в 2captcha, ответ набирается в поле и возвращается
в `result`. Подсказки: `min_len`, `max_len`, `numeric` (1 — цифры, 2 — буквы), `lang`, `case_sensitive`.
```python
res = s.run(actions=[
Action.captcha_image("#popup-signup img.captcha", '#popup-signup input[name="captcha"]', min_len=4),
Action.submit("#popup-signup button[type=submit]"),
], captcha="<ключ 2captcha>")
code = res.results[0]
```
+24 -1
View File
@@ -11,6 +11,13 @@ class Action:
selector: str | None = None
value: str | None = None
timeout: int | None = None
# только для captcha_image
target: str | None = None
min_len: int | None = None
max_len: int | None = None
numeric: int | None = None
lang: str | None = None
case_sensitive: bool | None = None
def to_dict(self) -> dict[str, Any]:
return {k: v for k, v in vars(self).items() if v is not None}
@@ -60,9 +67,25 @@ class Action:
def captcha(cls, selector: str, kind: str) -> "Action":
"""Решить капчу через 2captcha: kind — cloudflare, recaptcha или hcaptcha; нужен параметр captcha (ключ)."""
if kind not in CAPTCHA_TYPES:
raise ValueError(f"captcha kind must be one of: {', '.join(CAPTCHA_TYPES)}")
raise ValueError(f"captcha kind must be one of: {', '.join(CAPTCHA_TYPES)} (image: use captcha_image)")
return cls("captcha", selector, kind)
@classmethod
def captcha_image(
cls, selector: str, target: str, *,
min_len: int | None = None, max_len: int | None = None, numeric: int | None = None,
lang: str | None = None, case_sensitive: bool | None = None,
) -> "Action":
"""Капча-картинка: снимок элемента selector уходит в 2captcha, ответ набирается в поле target
и возвращается в result; нужен параметр captcha (ключ).
Подсказки 2captcha: min_len/max_len — 1–20 символов; numeric — 1 только цифры, 2 только буквы,
3 цифры или буквы, 4 и цифры, и буквы; lang — код языка; case_sensitive — учитывать регистр."""
return cls(
"captcha", selector, "image", target=target, min_len=min_len, max_len=max_len,
numeric=numeric, lang=lang, case_sensitive=case_sensitive,
)
@classmethod
def pass_challenge(cls) -> "Action":
"""Пройти антибот на текущей странице (например, после submit)."""