Снимок элемента картинки на текущей странице (без повторной загрузки 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>
116 lines
5.9 KiB
Markdown
116 lines
5.9 KiB
Markdown
# robotex — Python-клиент Robotex API
|
||
|
||
Обвязка над HTTP API Robotex (docs/API.md): задачи v2 через очередь, синхронный `/v1/solve`, сессии с удержанием окна
|
||
браузера. Синхронный `Robotex` и асинхронный `AsyncRobotex` с одинаковым интерфейсом. Зависимость одна — `httpx`.
|
||
|
||
## Установка
|
||
|
||
```bash
|
||
pip install "robotex @ git+https://git.sokolab.xyz/s0k0l/robotex.git@v0.1.0#subdirectory=client"
|
||
```
|
||
|
||
или из wheel (`make client-build` в репозитории кладёт его в `client/dist/`):
|
||
|
||
```bash
|
||
pip install robotex-0.1.0-py3-none-any.whl
|
||
```
|
||
|
||
## Быстрый старт
|
||
|
||
```python
|
||
from robotex import Robotex, Action
|
||
|
||
with Robotex("https://robotex.example.com", api_key="rbx_...") as rx:
|
||
result = rx.run(
|
||
"https://example.com",
|
||
[Action.wait_for("h1"), Action.inner_text("h1")],
|
||
html=True,
|
||
).raise_for_status()
|
||
print(result.results[0], result.user_agent, result.cookie("cf_clearance"))
|
||
```
|
||
|
||
`base_url` и `api_key` можно не передавать — тогда берутся из `ROBOTEX_URL` и `ROBOTEX_API_KEY`.
|
||
|
||
Остальные параметры запроса передаются именованными аргументами как есть: `proxy`, `cookies`, `html`, `screenshot`,
|
||
`timeout`, `captcha`, `captcha_type`, `disable_resources`, `blocked_domains`, `retries`, `wait`, `google_search` и т. д.
|
||
|
||
## Методы
|
||
|
||
| Метод | Что делает |
|
||
|---|---|
|
||
| `run(url, actions, **opts)` | задача v2: поставить в очередь и дождаться результата (`wait=` секунд, по умолчанию 600) |
|
||
| `submit(url, actions, **opts)` | только поставить; вернёт `TaskRef` с `.status()` и `.wait()` |
|
||
| `solve(url, actions, **opts)` | синхронный `/v1/solve`; `NoSlotsError`, если свободных браузеров нет |
|
||
| `session(session_id=None, idle_timeout=5)` | сессия: один профиль и окно, которое держится `idle_timeout` минут |
|
||
| `close_session(session_id)` | закрыть окно сессии и освободить слот |
|
||
| `health()` | слоты и воркеры |
|
||
|
||
Вызовы без сессии каждый раз получают новый профиль браузера.
|
||
|
||
## Сессии и удержание окна
|
||
|
||
```python
|
||
with rx.session(idle_timeout=10) as s:
|
||
s.run("https://site/login", [
|
||
Action.typing("#login", "me"),
|
||
Action.typing("#password", "secret"),
|
||
Action.submit("button[type=submit]"),
|
||
]).raise_for_status()
|
||
# без url — на текущей странице того же окна, без повторного запуска Chrome и антибота
|
||
balance = s.run(actions=[Action.inner_text(".balance")]).results[0]
|
||
saved = s.id
|
||
# на выходе из with окно закрывается (слот освобождается через секунду-две), профиль остаётся
|
||
|
||
again = rx.session(saved).run("https://site/account") # продолжить сессию позже
|
||
```
|
||
|
||
## Асинхронно
|
||
|
||
```python
|
||
import asyncio
|
||
from robotex import AsyncRobotex, Action
|
||
|
||
async def main():
|
||
async with AsyncRobotex() as rx:
|
||
results = await asyncio.gather(*(rx.run(url, [Action.inner_text("h1")]) for url in urls))
|
||
async with rx.session(idle_timeout=5) as s:
|
||
await s.run("https://example.com")
|
||
```
|
||
|
||
## Результат и ошибки
|
||
|
||
`Result`: `status` (`ok`/`err`), `code`, `error`, `user_agent`, `cookies`, `actions` (`ActionResult`), `results`
|
||
(тексты действий по порядку), `html`, `screenshot` (base64, `screenshot_bytes()`), `duration_ms`, `session_id`.
|
||
|
||
Ошибка выполнения (`challenge_failed`, `action_failed`, `request_failed`) — это результат со `status="err"`, исключения
|
||
нет; `raise_for_status()` превращает её в `ResultError` (в `e.result` всё собранное: cookies, html, скриншот).
|
||
|
||
Исключения (все от `RobotexError`):
|
||
|
||
| Исключение | Когда |
|
||
|---|---|
|
||
| `AuthError` | 401 — ключ не передан, неверен или отозван |
|
||
| `InvalidRequest` | 422 — неверное действие, нет `url` без открытого окна и т. п. |
|
||
| `NoSlotsError` | 503 у `solve()`; `retry_after` — через сколько секунд повторить |
|
||
| `NotFound` | 404 — задача истекла или из другой сессии |
|
||
| `TaskFailed` | задача `failed`/`cancelled` (например, упал воркер) |
|
||
| `TaskTimeout` | `wait` истёк; задача продолжает выполняться, `TaskRef.wait()` можно повторить |
|
||
| `ResultError` | `raise_for_status()` на результате со `status="err"` |
|
||
|
||
## Действия
|
||
|
||
`Action.fill`, `typing`, `click`, `submit`, `mouse_move`, `delay(seconds)`, `wait_for(selector, timeout=None)`,
|
||
`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]
|
||
```
|