build: лимиты ресурсов, прокси и нагрузочный тест; docs: API
- docker-compose: cpus/mem_limit через API_CPUS/API_MEM_LIMIT, прокси gost в профиле loadtest - tests/loadtest.py: параллельные запросы к /v1/solve + пики docker stats - docs/API.md: справочник по запросу, действиям, ответу и сессиям - docs/Deployment.md: результаты замеров и правило подбора BROWSER_SLOTS Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df108dae3f
commit
a319b81d8b
6 files changed
+330
-105
No files matched your search
+126
@@ -0,0 +1,126 @@
|
||||
# API
|
||||
|
||||
Один эндпоинт: `POST /v1/solve`. Открывает страницу в Chrome через прокси клиента, при необходимости проходит
|
||||
антибот (Cloudflare Turnstile, reCAPTCHA), выполняет список действий и возвращает cookies, результаты действий и,
|
||||
по запросу, HTML.
|
||||
|
||||
## Запрос
|
||||
|
||||
```http
|
||||
POST /v1/solve
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
Cookie: session_id=<id> # необязательно, см. «Сессии»
|
||||
|
||||
{
|
||||
"url": "https://freelancehunt.com/projects",
|
||||
"proxy": "http://user:pass@host:port",
|
||||
"captcha_type": "cloudflare",
|
||||
"actions": [
|
||||
{"type": "wait_for", "selector": "a[href*=\"/project/\"]", "timeout": 60}
|
||||
],
|
||||
"html": true,
|
||||
"timeout": 120
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Тип | По умолчанию | Описание |
|
||||
|---|---|---|---|
|
||||
| `url` | string | — | **обязательно**. Страница, которую открываем |
|
||||
| `proxy` | string | — | **обязательно**. `http://`, `https://` или `socks5://` (Chrome не поддерживает socks5 с логином/паролем). По exit-IP прокси выставляются locale и timezone браузера |
|
||||
| `actions` | array | — | **обязательно**, может быть пустым. Действия, см. ниже |
|
||||
| `captcha_type` | string | `null` | провайдер антибота: `cloudflare` или `recaptcha`. Без него шаг антибота пропускается — для сайтов за Cloudflare обязательно |
|
||||
| `captcha` | string | `null` | ключ сервиса распознавания (2captcha), если антибот не проходится без решения капчи |
|
||||
| `captcha_detect_locator` | string | `null` | локатор, по которому определяется, что на странице антибот |
|
||||
| `captcha_success_locator` | string | `null` | локатор, по которому определяется, что антибот пройден |
|
||||
| `html` | bool | `false` | вернуть HTML страницы после выполнения действий |
|
||||
| `timeout` | int, с | `60` | таймаут навигации |
|
||||
| `user_agent` | string | `null` | свой User-Agent; без него берётся UA установленного Chrome |
|
||||
| `cookies` | array | `null` | cookies для установки до навигации, формат Playwright: `{name, value, domain, path, expires, httpOnly, secure, sameSite}` |
|
||||
| `disable_resources` | bool | `false` | не грузить картинки, шрифты, медиа |
|
||||
| `blocked_domains` | array | `null` | домены, запросы к которым блокируются |
|
||||
| `retries` | int | `1` | попыток навигации |
|
||||
| `retry_delay` | float, с | `1.0` | пауза между попытками |
|
||||
| `wait` | int, мс | `0` | пауза после загрузки страницы |
|
||||
| `google_search` | bool | `false` | выставить Referer как переход из поиска Google |
|
||||
|
||||
### Действия
|
||||
|
||||
Выполняются по порядку после прохождения антибота. На первой ошибке выполнение останавливается.
|
||||
|
||||
| `type` | Поля | Что делает |
|
||||
|---|---|---|
|
||||
| `wait_for` | `selector`, `timeout` (1–7200 с) | ждёт появления элемента; при таймауте — ошибка действия |
|
||||
| `click` | `selector` | клик по элементу |
|
||||
| `submit` | `selector` | клик и ожидание навигации |
|
||||
| `fill` | `selector`, `value` | вставка значения в поле |
|
||||
| `mouse_move` | `selector` | наведение курсора на элемент |
|
||||
| `delay` | `value` (секунды строкой) | пауза |
|
||||
| `inner_html` | `selector` | возвращает HTML элемента в `result` |
|
||||
| `captcha` | `selector`, `value` (тип: `cloudflare`, `recaptcha`, `hcaptcha`) | решить капчу внутри страницы через 2captcha, нужен `captcha` |
|
||||
| `pass_challenge` | `selector` (локатор успеха, необязательно) | повторно пройти антибот, например после перехода на другую страницу; если не пройден — ошибка действия |
|
||||
|
||||
## Ответ
|
||||
|
||||
HTTP-статус всегда `200`, результат — в теле.
|
||||
|
||||
Успех (антибот пройден, действия выполнены):
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) ... Chrome/155.0.0.0 Safari/537.36",
|
||||
"cookies": [{"name": "cf_clearance", "value": "...", "domain": ".freelancehunt.com", "...": "..."}],
|
||||
"actions": [
|
||||
{"type": "wait_for", "ok": true}
|
||||
],
|
||||
"html": "<head>...</head><body>...</body>"
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Описание |
|
||||
|---|---|
|
||||
| `status` | `"ok"` — все действия выполнены, `"err"` — одно из действий упало (выполнение на нём остановлено) |
|
||||
| `user_agent` | UA браузера, с которым проходили антибот (нужен клиенту вместе с `cf_clearance`) |
|
||||
| `cookies` | все cookies контекста |
|
||||
| `actions` | по элементу на выполненное действие: `type`, `ok`, `result` (результат или текст ошибки) |
|
||||
| `html` | только при `"html": true` |
|
||||
| `error` | при `status: "err"`: `<type>: <текст ошибки>` упавшего действия |
|
||||
|
||||
Ошибка с упавшим действием:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "err",
|
||||
"actions": [{"type": "wait_for", "ok": false, "result": "Page.wait_for_selector: Timeout 3000ms exceeded. ..."}],
|
||||
"error": "wait_for: Page.wait_for_selector: Timeout 3000ms exceeded. ..."
|
||||
}
|
||||
```
|
||||
|
||||
Ошибка до выполнения действий (навигация, прокси, неизвестный `captcha_type`):
|
||||
|
||||
```json
|
||||
{"status": false, "error": "unknown captcha_type 'datadome', expected one of: recaptcha, cloudflare"}
|
||||
```
|
||||
|
||||
Нет свободных браузеров (лимит `BROWSER_SLOTS`):
|
||||
|
||||
```json
|
||||
{"ok": false, "error": "No slots available"}
|
||||
```
|
||||
|
||||
> Форматы ошибок пока не унифицированы (тех. долг): `status` бывает `"ok"`/`"err"`/`false`, а при занятых слотах
|
||||
> поля `status` нет вовсе. Успех — строго `status == "ok"`.
|
||||
|
||||
## Сессии
|
||||
|
||||
Ответ ставит cookie `session_id`. Если передать её в следующем запросе, браузер откроется с тем же профилем
|
||||
(`extra/user_data_dir/<session_id>`): cookies, localStorage и отпечаток сохраняются, повторно проходить Cloudflare
|
||||
обычно не нужно. Без cookie каждый запрос получает новый профиль.
|
||||
|
||||
Один профиль нельзя использовать в двух запросах одновременно: Chrome заблокирует каталог профиля.
|
||||
|
||||
## Производительность
|
||||
|
||||
Каждый запрос запускает отдельный Chrome: около 0,6 ГБ памяти и до 3–4 ядер на пике при прохождении Cloudflare.
|
||||
Замеры и рекомендации по лимитам — в [Deployment.md](Deployment.md#нагрузка).
|
||||
@@ -46,9 +46,47 @@ docker compose exec api sh -c 'rm -rf /app/extra/user_data_dir/*'
|
||||
|
||||
- `shm_size: 2gb` — Chrome активно использует `/dev/shm`, дефолтных 64 МБ не хватает (вкладки падают с «Aw, Snap»).
|
||||
- `API_PORT` — порт на хосте (по умолчанию 8000): `API_PORT=8001 docker compose up -d`.
|
||||
- `API_CPUS` / `API_MEM_LIMIT` — потолок ресурсов контейнера (по умолчанию 4 CPU / 8 ГБ, swap выключен).
|
||||
При переборе памяти OOM-killer убивает процесс Chrome внутри контейнера, хост не страдает.
|
||||
- `restart: unless-stopped`.
|
||||
- `.env` подключается, если существует (шаблон — `env.example`).
|
||||
|
||||
## Нагрузка
|
||||
|
||||
Замер: `freelancehunt.com/projects` за Cloudflare Turnstile, `captcha_type: cloudflare`, ожидание списка проектов,
|
||||
каждый запрос — новый профиль. Прокси — `gost` из профиля `loadtest`. Хост: 16 ядер, 31 ГБ.
|
||||
|
||||
| Лимит | Параллельно | Успешно | Медиана, с | Пик CPU | Пик RAM |
|
||||
|---|---|---|---|---|---|
|
||||
| 4 CPU | 1 | 1/1 | 5 | 3,5 ядра | 0,5 ГБ |
|
||||
| 4 CPU | 5 | 3/5 | 68 | 4,1 | 2,8 ГБ |
|
||||
| 4 CPU | 10 | 4/10 | 229 | 4,2 | 6,0 ГБ |
|
||||
| 4 CPU | 15 | 3/15 | 261 | 4,2 | 6,1 ГБ |
|
||||
| 12 CPU | 3 | 3/3 | 8 | 7,2 | 1,4 ГБ |
|
||||
| 12 CPU | 5 | 4/5 | 8 | 12,1 | 2,4 ГБ |
|
||||
| 12 CPU | 10 | 9/10 | 23 | 12,3 | 4,7 ГБ |
|
||||
|
||||
Выводы:
|
||||
|
||||
- Узкое место — **CPU**. Один Chrome на прохождении Turnstile съедает 2–3,5 ядра. Когда процессора не хватает,
|
||||
время растёт в десятки раз, а Cloudflare перестаёт пропускать (на 4 CPU и 10 параллельных — 6 из 10 застряли
|
||||
на челлендже).
|
||||
- Память — около **0,5–0,6 ГБ** на браузер.
|
||||
- Правило подбора: `BROWSER_SLOTS ≈ число ядер`, `API_MEM_LIMIT ≈ 0,6 ГБ × BROWSER_SLOTS + 1 ГБ`.
|
||||
Пример: сервер 4 ядра / 8 ГБ → 3–4 слота; сейчас `BROWSER_SLOTS = 10` зашит в `src/main.py` и рассчитан
|
||||
на ~10–12 ядер.
|
||||
- Сверх `BROWSER_SLOTS` запросы сразу получают `No slots available`, не ставятся в очередь.
|
||||
|
||||
Повторить замер:
|
||||
|
||||
```bash
|
||||
API_PORT=8001 docker compose --profile loadtest up -d
|
||||
python tests/loadtest.py --api http://127.0.0.1:8001 --levels 1 5 10
|
||||
```
|
||||
|
||||
Параметры скрипта: `--url`, `--selector`, `--captcha-type`, `--proxy`, `--levels`. Каждый запрос создаёт новый профиль
|
||||
в `extra/user_data_dir` — после прогона их стоит почистить.
|
||||
|
||||
## Reverse proxy
|
||||
|
||||
В проде контейнер ставится за Caddy (TLS). Пример `Caddyfile`:
|
||||
|
||||
+4
-55
@@ -2,59 +2,8 @@
|
||||
|
||||
Сервис позволяет отработать на странице JS и продолжить парсинг. Полезно для ботов, которым нужно авторизоваться на сайте или пройти капчу, там где требуется рендеринг JS.
|
||||
|
||||
### Запрос и ответ
|
||||
Клиент присылает URL, свой прокси и список действий. Сервис открывает страницу в настоящем Chrome, проходит
|
||||
антибот (Cloudflare Turnstile, reCAPTCHA), выполняет действия и возвращает cookies (включая `cf_clearance`),
|
||||
User-Agent и, по запросу, HTML. С этими cookies и UA клиент продолжает парсинг своим HTTP-клиентом.
|
||||
|
||||
**Параметры запроса**:
|
||||
|
||||
- URL - обязательно нужно указать с каким сайтом будем работать
|
||||
- proxy - обязательно нужно задать прокси, система не станет палить свою инфраструктуру
|
||||
- time_zone - временная зона
|
||||
- locale - языковая настройка
|
||||
- user_agent - User-Agent браузера Chrome, если не передать будет использован свой
|
||||
- screen - размер окна, по умолчанию: 1280х920
|
||||
- timeout - общий таймаут выполнения запроса, по умолчанию: 120
|
||||
- captcha - токен сервиса распознавания, если требуется решить капчу на странице
|
||||
- actions - перечисление списка действий, которые на странице нужно выполнить
|
||||
|
||||
Настройка actions:
|
||||
|
||||
- type - тип запроса из перечисления
|
||||
- selector - css селектор
|
||||
- value - значение для вставки
|
||||
- timeout - время выполнения или ожидания
|
||||
|
||||
Доступные типы actions:
|
||||
|
||||
- wait_for - ожидание появления элемента
|
||||
- selector
|
||||
- timeout
|
||||
- mouse_move - перемещение курсора
|
||||
- selector
|
||||
- click - клик
|
||||
- selector - если не передан, то клик в текущей позиции
|
||||
- fill - заполняет поля
|
||||
- selector
|
||||
- value - значение будет впечатано
|
||||
|
||||
Будет добавлено:
|
||||
|
||||
1. `{"type": "typing", "value": "user@example.com"},` - набор текста
|
||||
2. `{"type": "innerhtml", "selector": "#email"},` - получить контент
|
||||
3. `{"type": "script", "source": "console.log(\"hello world\")"},` - выполнить скрипт
|
||||
|
||||
**Ответ**:
|
||||
|
||||
- URL - итоговый урл из адресной строки на момент окончания работы
|
||||
- user_agent - заголовок браузера
|
||||
- screen - размер окна
|
||||
- exc_time = время выполнения
|
||||
- actions_result - отчет о выполненых действиях
|
||||
- ok - выполнился ли этот запрос успешно
|
||||
- error - для сообщения об ошибке
|
||||
|
||||
Результат отработки actions
|
||||
|
||||
- type - тип события
|
||||
- ok - успешность события
|
||||
- error - появляется, когда событие сработало с ошибкой
|
||||
- message - появляется, для команд возвращающих результат
|
||||
Описание запроса и ответа — [API.md](API.md), развёртывание — [Deployment.md](Deployment.md).
|
||||
Reference in new issue
Block a user