# 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] ```