feature: архитектура v2 — api + очередь + worker, удержание окна браузера (#78)

- Сервис api (FastAPI, без Chrome): POST /v2/tasks, GET /v2/tasks/{id} (доступ по cookie
  сессии), DELETE /v2/sessions/current; /v1/solve — прежний контракт поверх очереди.
- Сервис worker (dramatiq + RabbitMQ): слоты браузера, окна сессий между задачами
  (browser_idle_timeout 0–120 мин, простой — TTL ключа в Redis), heartbeat.
- Маршрутизация задач сессии в персональную очередь воркера с открытым окном.
- Redis: задачи, результаты (RESULT_TTL_HOURS), окна, воркеры. Прокси и ключ 2captcha
  в Redis не сохраняются.
- Движок: AsyncStealthySession.run() — действия на текущей странице без навигации.
- Два образа (robotex-api, robotex-worker), compose с rabbitmq и redis,
  make up/down/logs/smoke, релиз собирает все образы.
- Тесты: api + worker в одном процессе (fakeredis), настоящий воркер dramatiq на
  StubBroker, браузерные тесты на новой схеме, smoke для запущенного стенда.
- Документация: README, API (v2), Deployment, Usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
protokeyandClaude Opus 5.5 committed 2026-10-13 19:13:52 +04:00
1 parent fe509c25c3
commit 7b511d5d38
38 files changed
+2142 -545

No files matched your search

+76 -7
View File
@@ -2,11 +2,17 @@
Как встраивать API в свой клиент (UA, прокси, сессии, повторы) — [Usage.md](Usage.md).
Основной эндпоинт: `POST /v1/solve`. Открывает страницу в Chrome (через прокси клиента, если он задан), при необходимости проходит
антибот (Cloudflare Turnstile, reCAPTCHA), выполняет список действий и возвращает cookies, результаты действий и,
по запросу, HTML и скриншот.
Задача открывает страницу в Chrome (через прокси клиента, если он задан), при необходимости проходит антибот
(Cloudflare Turnstile, reCAPTCHA), выполняет список действий и возвращает cookies, результаты действий и, по запросу,
HTML и скриншот. Задачи выполняет сервис worker из очереди; API работает в двух режимах:
`GET /health` (и `GET /`) — `{"status": "ok", "browser_slots": N, "free_slots": M}`.
* `POST /v1/solve` — синхронно: ответ приходит, когда задача выполнена;
* `POST /v2/tasks` + `GET /v2/tasks/{id}` — асинхронно, с возможностью держать окно браузера открытым между
задачами (см. [API v2](#api-v2)).
`GET /health` (и `GET /`) — `{"status": "ok", "browser_slots": N, "free_slots": M, "workers": [...]}`: слоты всех
живых воркеров; `workers` — по каждому `worker_id`, `browser_slots`, `free_slots`, `windows` (открытые окна),
`running` (выполняемые задачи).
> `X-API-Key` пока не проверяется ([#4](https://redmine.sokolab.xyz/issues/4)).
@@ -104,7 +110,7 @@ Cookie: session_id=<id> # необязательно, см. «Сесси
| `code` | HTTP | Когда | Что ещё есть в ответе |
|---|---|---|---|
| `no_slots` | `503` + `Retry-After` | все браузеры заняты (`BROWSER_SLOTS`), запрос не ставится в очередь | — |
| `no_slots` | `503` + `Retry-After` | только `/v1/solve`: все слоты браузеров заняты, или воркер не взял задачу за `V1_QUEUE_WAIT` секунд (задача отменяется) | — |
| `request_failed` | `200` | ошибка до действий: навигация, прокси, неизвестный `captcha_type` | `actions: []` |
| `challenge_failed` | `200` | антибот не пройден за отведённое время, действия не выполнялись | cookies, html, screenshot |
| `action_failed` | `200` | упало действие, выполнение остановлено на нём | cookies, html, screenshot, `actions` до упавшего включительно |
@@ -139,9 +145,72 @@ canvas. Ограничение: canvas, WebGL и `hardwareConcurrency` у все
User-Agent и размер окна запросом не задаются — это родные значения браузера (почему — [Usage.md](Usage.md#что-задаёт-сервис-а-не-клиент)).
Поле `user_agent` в запросе даёт `422`.
Один профиль нельзя использовать в двух запросах одновременно: Chrome заблокирует каталог профиля.
Задачи одной сессии выполняются по очереди: профиль Chrome нельзя открыть дважды. Вторая задача ждёт, пока
закончится первая.
## API v2
Асинхронный режим: задача ставится в очередь, клиент опрашивает её по ссылке. Тело задачи — как у `/v1/solve`, плюс:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
| `browser_idle_timeout` | int, мин | `0` | держать окно браузера открытым после задачи столько минут простоя, `0`–`120`. `0` — закрыть сразу, как в v1 |
| `url` | string | `null` | необязателен, если окно сессии открыто: тогда действия выполняются на текущей странице, без навигации |
### POST /v2/tasks
```http
POST /v2/tasks
Content-Type: application/json
Cookie: session_id=<id> # необязательно
{"url": "https://freelancehunt.com/projects", "captcha_type": "cloudflare", "actions": [], "browser_idle_timeout": 10}
```
Ответ `202` и cookie `session_id`:
```json
{"task_id": "0b9c…", "status": "queued", "url": "/v2/tasks/0b9c…"}
```
Без `url` и без открытого окна сессии — `422`.
### GET /v2/tasks/{id}
Только с cookie `session_id` той сессии, в которой задача создана; иначе, а также для несуществующей или истёкшей
задачи — `404`.
```json
{"task_id": "0b9c…", "status": "done", "result": {"status": "ok", "user_agent": "…", "cookies": […], "actions": […], "duration_ms": 7120}}
```
| `status` | Значение |
|---|---|
| `queued` | ждёт свободного слота браузера |
| `running` | выполняется |
| `done` | готово, ответ в `result` — в том же формате, что ответ `/v1/solve` (со своим `status` `ok`/`err`) |
| `failed` | задачу не удалось выполнить (например, воркер упал дважды), причина в `error` |
Результат хранится `RESULT_TTL_HOURS` часов (по умолчанию 24), затем `404`. Опрашивать раз в 0,5–2 секунды.
### Окно браузера между задачами
С `browser_idle_timeout > 0` Chrome сессии не закрывается после задачи. Следующая задача с той же cookie
`session_id` выполняется в этом окне: без запуска Chrome и обычно без повторного антибота, а без `url` — прямо на
текущей странице (например, продолжить заполнение формы). Каждая задача в окно заново отсчитывает простой — по
своему `browser_idle_timeout`; задача с `0` закрывает окно после себя.
* Открытое окно занимает слот браузера, пока не закроется. Не держите окна дольше, чем нужно.
* Задача с другим `proxy`, чем у открытого окна, перезапускает Chrome (прокси задаётся при запуске).
* `cookies` в задаче заменяют cookies открытого окна, как и при запуске.
* Если задача в окне упала с `request_failed`, окно закрывается; следующая задача откроет его заново из профиля.
### DELETE /v2/sessions/current
Закрыть окно сессии (по cookie `session_id`) досрочно и освободить слот. Если в окне идёт задача — сразу после неё.
Ответ `{"closed": true}`, или `{"closed": false}`, если окна нет.
## Производительность
Каждый запрос запускает отдельный Chrome: около 0,6 ГБ памяти и до 3–4 ядер на пике при прохождении Cloudflare.
Каждое окно — отдельный Chrome: около 0,6 ГБ памяти и до 3–4 ядер на пике при прохождении Cloudflare.
Замеры и рекомендации по лимитам — в [Deployment.md](Deployment.md#нагрузка).