# Использование API: практики для клиентов Как встроить Robotex в свой парсер или бота, чтобы антибот проходился стабильно, а полученные cookies работали. Справочник по полям и ответам — [API.md](API.md). ## Основной сценарий Robotex проходит антибот в настоящем браузере и отдаёт то, с чем клиент дальше ходит на сайт **своим HTTP-клиентом**: 1. `POST /v1/solve` с `url`, своим `proxy` и `captcha_type`. 2. Из ответа взять `cookies` (среди них `cf_clearance`) и **`user_agent`**. 3. Дальше ходить на сайт своим HTTP-клиентом: - с **тем же User-Agent**, что пришёл в ответе; - через **тот же прокси** (тот же exit-IP); - с полученными cookies. 4. Когда сайт снова отвечает челленджем (обычно `403` и страница «Just a moment…») или истекает `cf_clearance` (поле `expires` у cookie) — повторить `/v1/solve` **в той же сессии** (cookie `session_id`). `cf_clearance` выдаётся конкретному браузеру: Cloudflare связывает его с User-Agent и, как правило, с IP. С другим UA или через другой прокси cookie не сработает, и сайт снова покажет челлендж. ## Что задаёт сервис, а не клиент User-Agent, размер окна и экран, отпечаток браузера **не настраиваются запросом** — их определяет сам браузер. Клиент подстраивается под ответ, а не наоборот. Почему так: подменить можно только часть признаков. Строка UA меняется, но `navigator.userAgentData`, client hints и поведение движка остаются от реального Chrome, и Cloudflare видит расхождение. Замер на Chrome 155 / Linux: | User-Agent в браузере | Cloudflare | |---|---| | родной (Linux Chrome 155) | пройден 3 из 3, 7–9 с | | Linux Chrome 147 | не пройден, 3 из 3 | | Windows Chrome 155 | не пройден | Поэтому параметр `user_agent` убран из запроса (передача даёт `422`). Раньше по той же причине был убран параметр размера окна. Каждому профилю сервис сам генерирует правдоподобный отпечаток (экран и `devicePixelRatio` из реального распределения) и применяет его средствами Chrome. UA в ответе может измениться, когда на сервере обновится Chrome. Не храните UA у себя навсегда — берите его из последнего ответа. ## Сессии - Ответ ставит cookie `session_id`. Передавайте её в следующих запросах: профиль браузера сохраняется (cookies, localStorage, отпечаток), и повторно проходить антибот обычно не нужно — запрос заметно быстрее. - Одна сессия — один «человек»: держите для неё **один и тот же прокси**. Смена exit-IP внутри сессии выглядит подозрительно и обнуляет `cf_clearance`. - Задачи одной сессии выполняются по очереди: вторая ждёт, пока закончится первая. - Для параллельной работы заводите несколько сессий, каждую со своим прокси. ## Синхронно или через задачи - `/v1/solve` — проще: один запрос, один ответ. Держите соединение открытым до 3–4 минут (см. «Таймауты»). - `/v2/tasks` — запрос возвращается сразу, результат забираете по ссылке `GET /v2/tasks/{id}` с той же cookie `session_id`. Подходит, когда соединение нельзя держать долго, и для многошаговых сценариев. Многошаговый сценарий (войти, затем несколько раз что-то сделать на сайте) — с удержанием окна: 1. `POST /v2/tasks` с `url` и `browser_idle_timeout`: окно останется открытым на это число минут простоя. 2. Следующие задачи с той же cookie `session_id` и тем же `proxy` выполняются в открытом окне: без запуска Chrome и повторного антибота. Без `url` — на текущей странице. 3. В конце — `DELETE /v2/sessions/current` или задача с `browser_idle_timeout: 0`: окно закроется и освободит слот. Открытое окно занимает слот браузера, поэтому ставьте простой не больше, чем пауза между вашими шагами. ## Cookies - Переданные в запросе `cookies` **заменяют** cookies профиля целиком. Не передали — профиль сохраняет свои. - Для постоянных cookies указывайте `expires` (unix-время). Cookie без `expires` — сессионная и пропадёт, как только закроется окно браузера (после задачи без `browser_idle_timeout`). - Обычно передавать cookies не нужно: профиль сам хранит то, что выдал сайт. Передавайте, только если сайт ведёт себя по-разному в зависимости от cookies (например, авторизация, полученная вне Robotex). ## Ошибки и повторы | Ответ | Что делать | |---|---| | `503`, `code: no_slots` | `/v1/solve`: все браузеры заняты — повторить через `Retry-After` секунд. Задачи v2 в этом случае ждут в очереди | | `challenge_failed` | антибот не пройден за отведённое время: повторить в той же сессии; при повторных неудачах — сменить прокси (и сессию) | | `action_failed` | упало действие: смотрите `error` и `actions[].result`; для отладки повторите с `"screenshot": true` | | `request_failed` | навигация не удалась: обычно прокси недоступен или сайт не отвечает | | `401` | нет ключа в `X-API-Key`, ключ неверный или отозван | | `422` | ошибка в запросе: описание в `detail` | Успех — строго `status == "ok"`. ## Таймауты Прохождение антибота может занять до 2 минут, плюс навигация (`timeout`) и действия. Таймаут вашего HTTP-клиента к Robotex ставьте не меньше `timeout` + 120 с + сумма таймаутов действий. Так же настройте reverse proxy перед сервисом. ## Python-клиент Для Python есть готовый пакет `robotex` ([client/README.md](../client/README.md)): задачи, сессии с удержанием окна, ожидание результата, ошибки в виде исключений, sync и async. ```python from robotex import Robotex, Action with Robotex("https://app.robotex.sokolab.xyz", api_key="rbx_...") as rx: result = rx.run("https://example.com", [Action.inner_text("title")]).raise_for_status() ``` ## Пример (Python, httpx) ```python import httpx ROBOTEX = "https://app.robotex.sokolab.xyz" PROXY = "http://user:pass@host:port" robotex = httpx.Client(base_url=ROBOTEX, timeout=300, headers={"X-API-Key": "rbx_..."}) def solve(url: str) -> dict: resp = robotex.post("/v1/solve", json={ "url": url, "proxy": PROXY, "captcha_type": "cloudflare", }) # session_id сохраняется в cookies клиента robotex и уходит в следующих запросах data = resp.json() if data.get("status") != "ok": raise RuntimeError(f"{data.get('code')}: {data.get('error')}") return data solution = solve("https://freelancehunt.com/projects") site = httpx.Client( proxy=PROXY, # тот же прокси headers={"User-Agent": solution["user_agent"]}, # тот же UA cookies={c["name"]: c["value"] for c in solution["cookies"]}, ) page = site.get("https://freelancehunt.com/projects") if page.status_code == 403: solution = solve("https://freelancehunt.com/projects") # та же сессия, обновить cf_clearance ```