# Коробка

> Parreq на вашем сервере: один бинарник, свой браузер, тот же API

Источник: https://docs.parreq.com/box/

Коробка — это Parreq, работающий у вас. Один бинарник поднимает на вашей машине
свой Chrome и отвечает тем же API, что и `api.parreq.com`. Страницы собираются
и разбираются на месте; наружу уходит только снятие капчи, потому что ключ
решателя обязан оставаться у нас.

Раздел с загрузками появляется в кабинете после того, как мы откроем вам право
на коробку. Напишите нам, если его там нет.

> **Примечание.**
>   Меняется ровно один адрес: вместо `https://api.parreq.com` вы указываете
>   `http://127.0.0.1:8123`. Тела запросов, ответы, коды ошибок и заголовки те же,
>   ваш код правки не требует. Библиотекам нужна версия 1.5 или новее — в ней
>   появились разбор по селекторам и живые вкладки.

## Зачем

**Данные остаются у вас**
    Разметка страниц, запросы и извлечённые поля не покидают вашу машину.
    Разбор идёт на месте — и выдачи Google, и полей по вашим селекторам.

**Дешевле**
    Половина облачной цены, а базовый Fetch и работа с живыми вкладками
    бесплатны: считает ваше железо.

**Без сроков жизни**
    Вкладка живёт, пока вы её не закроете. Ни простоя в 60 секунд, ни потолка
    в две минуты, как в облаке.

**Наши адреса выхода**
    Прокси и снятие капчи остаются нашими: сайты видят наши адреса, а не ваш
    сервер.

## Установка

**1. Скачайте**

    В кабинете, раздел «Коробка». Есть сборки для Linux (x86-64 и ARM),
    macOS (Apple silicon и Intel) и Windows.

    ```bash
    chmod +x parreq-box
    sha256sum -c parreq-box-1.0.0-linux-amd64.sha256
    ```

**2. Задайте ключ**

    ```bash
    ./parreq-box setup --key pr_ваш_ключ
    ```
    Ключ тот же, что и для облака. Записывается в файл с правами 0600 рядом с
    настройками.

**3. Запустите**

    ```bash
    ./parreq-box run
    ```
    Коробка подключится к нашим серверам и заведёт заявку. До подтверждения она
    на связи, но запросов не обслуживает.

**4. Дождитесь подтверждения**

    Мы видим заявку сразу: какой ключ, какая почта, какая машина. Подтверждение
    доезжает в уже открытый канал — перезапускать ничего не нужно.

    ```bash
    curl http://127.0.0.1:8123/v1/box/state
    ```

**5. Переключите свой код**

    ```python
    from parreq import Parreq
    client = Parreq("pr_ваш_ключ", base_url="http://127.0.0.1:8123")
    ```

    Библиотеки версии 1.5 и старше умеют и разбор по селекторам, и живые
    вкладки: `client.fetch(url, fields={…})`, `client.browser_open(url)`;
    с 1.6 — анкету целиком: `client.browser_form(tab, {...}, submit=…)`.
    Прежние версии работают с поиском, Fetch и обходом, но про коробку не
    знают — обновитесь.

## Чем коробка выходит в сеть

Три способа. Какие из них вам доступны, решаем мы; какой взять из доступных —
решаете вы.

| Режим | Чей адрес видят сайты | Когда брать |
|---|---|---|
| `parreq` | наш | по умолчанию: адреса наши, репутация наша, капчу снимаем мы |
| `own` | ваш прокси | у вас свои адреса и свои причины ими ходить |
| `direct` | адрес вашей машины | внутренние сайты, свой периметр, отладка |

Настраивается в `config.json` коробки:

```json
{
  "egress": "own",
  "proxy": "1.2.3.4:8080:логин:пароль"
}
```

Или переменными: `PARREQ_BOX_EGRESS=own`, `PARREQ_BOX_PROXY=...`. После правки
перезапустите коробку.

Запись прокси принимается в тех же видах, что и у нас: `host:port`,
`host:port:логин:пароль`, `логин:пароль@host:port`, со схемой `http://`,
`https://`, `socks5://` впереди или без неё.

> **Примечание.**
>   Режим `parreq` доступен всегда, `own` и `direct` открываем по просьбе. Если
>   выбрать неоткрытый режим, коробка не станет молчать и не подменит адрес
>   втихую: она скажет об этом в журнале и продолжит работать через наши адреса.
>   Так же она поступит, если запись своего прокси не разобралась.

Что сейчас используется, видно в состоянии:

```bash
curl http://127.0.0.1:8123/v1/box/state
```

```json
{
  "egress": {"ready": true, "via": "own", "proxy": "1.2.3.4:8080",
             "allowed": ["parreq", "own", "direct"]}
}
```

`allowed` — что открыто вам, `via` — что работает прямо сейчас. Свой адрес
показывается, наш — нет: он наш ресурс.

> **Внимание.**
>   Со своим адресом капча становится вашей заботой в том смысле, что решаем её
>   мы, но привязываем токен к тому адресу, с которого пришёл запрос. Свежий
>   адрес без истории Google встречает заслоном чаще нашего — это не поломка
>   коробки, а свойство адреса.

## Чем коробка представляется сайту

Тем же, чем и облако: настоящим Chrome под Windows, с той же версией, что у
двоичного файла на вашей машине, с правдоподобной видеокартой, числом ядер и
часовым поясом страны выхода. Отпечаток правится до первого скрипта страницы —
позже поздно, страница исходные значения уже прочитала.

> **Внимание.**
>   Так было не всегда. Коробка представлялась `HeadlessChrome` на
>   `X11; Linux x86_64`, без WebGL и с `navigator.webdriver`, и сайты под
>   защитой отвечали ей «Forbidden» — в том числе через наши адреса выхода, по
>   которым облако ту же страницу забирало с первого раза. Если у вас коробка
>   старее 1.3.4 и защищённый сайт её не пускает, дело в этом; обновитесь.

Устройство, страну и язык задаёт запрос — теми же полями, что и в облаке:

```bash
curl -X POST http://127.0.0.1:8123/v1/fetch \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://tn.ru/","device":"mobile","gl":"ru","hl":"ru","text":true}'
```

| Поле | Значения | По умолчанию |
|---|---|---|
| `device` | `desktop`, `desktop_mac`, `mobile`, `mobile_ios`, `tablet` | `desktop` |
| `gl` | страна двумя буквами: от неё часовой пояс и `Accept-Language` | `us` |
| `hl` | язык интерфейса | `en` |

Меняется не одна строка заголовка, а вся обстановка: размер окна, плотность
пикселей, касания, Client Hints, часовой пояс и языки. Витрины отдают телефону
другую вёрстку, и параметр, меняющий только `User-Agent`, вводил бы в
заблуждение.

> **Примечание.**
>   Окно по-прежнему безоконное: на сервере окна нет, а на рабочей станции
>   мелькающее окно браузера — это чужая машина, на которой что-то само
>   двигается. Два признака, которыми headless выдаёт себя, закрыты иначе:
>   строка — подменой, отсутствие WebGL — программным рендерером и подменой
>   вендора.

## Браузер

Коробке нужен Chrome или Chromium. Она сама разберётся, откуда его взять:

1. Уже установленный в системе — берётся первым. Он собран под вашу систему и
   обновляется её же средствами.
2. Если системного нет, коробка скачивает Chrome for Testing себе в каталог.

**На Alpine и других musl-системах** второй путь не работает: Chrome for Testing
собран под glibc и на musl не запускается вовсе. Коробка это видит и не тратит
минуты на бесполезную загрузку, а просит поставить браузер системой:

```sh
apk add --no-cache chromium
rc-service parreq-box restart
```

После этого она возьмёт `/usr/bin/chromium-browser` и запустится. Проверено:
браузер поднимается, страницы собираются, разбор по селекторам работает.

## Как запускать службой

```bash
sudo ./parreq-box install
```

Одна команда на любой системе: коробка сама смотрит, чем на машине поднимают
службы, и ставит себя в нужном виде.

| Система | Что создаётся | Автозапуск |
|---|---|---|
| systemd (Debian, Ubuntu, RHEL) | `/etc/systemd/system/parreq-box.service` | `Restart=always` |
| OpenRC (Alpine, Gentoo) | `/etc/init.d/parreq-box` | `supervise-daemon` |
| SysV init | `/etc/init.d/parreq-box` со сторожем | `update-rc.d` или `chkconfig` |
| macOS | `com.parreq.box.plist` | `KeepAlive` |
| Windows | служба `parreq-box` | `sc failure … restart` |

> **Внимание.**
>   Во всех вариантах настроен перезапуск после выхода, и это обязательно:
>   обновление коробка ставит сама и выходит нулём, ожидая, что её поднимут.
>   Служба без перезапуска превратила бы обновление в остановку.

Если система не опознана, коробка честно скажет об этом и не станет делать вид,
что установилась. Тогда запускайте `parreq-box run` своим способом — но
позаботьтесь, чтобы её кто-то поднимал после выхода.

Снять службу: `sudo ./parreq-box uninstall`. Настройки и профиль браузера
останутся на месте.

Проверить, что коробка видит систему правильно:

```bash
./parreq-box version
```

```
parreq-box 1.0.0
отпечаток машины: 1ced36732e68ab35795ec03650e9a08d
каталог: /var/lib/parreq-box
службы: OpenRC
```

> **Примечание.**
>   На минимальных образах (Alpine, `debian:slim`) может не оказаться корневых
>   сертификатов, и коробка не достучится до нас. Лечится одной командой:
>   `apk add ca-certificates` либо `apt install ca-certificates`. Коробка скажет
>   об этом прямо, а не «ошибкой TLS».

## Что доступно

| Возможность | В коробке | Цена |
|---|---|---|
| Поиск Google | да | 1 кредит |
| Fetch: страница браузером | да | бесплатно |
| Fetch с разбором по селекторам | да, разбор у вас | бесплатно |
| Browser API: живые вкладки | да, без сроков | бесплатно |
| Анкета целиком: `browser/form` | да | бесплатно |
| Скриншот | да | 2 кредита |
| Скрипты, стили, сетевые запросы (`include=js,css,network`) | да, с 1.3.4 | половина облачной цены |
| Копия для iframe (`include=iframe`) | нет | — |
| Снятие капчи | да, решаем мы | 10 кредитов |
| Обход сайта (Crawl) | нет | — |
| Готовые решения | нет | — |
| Яндекс | нет, пока отключён | — |

Почему база бесплатна: вычисления идут на вашей машине, а профиль, которым
браузер ходит, разогрет нашей капчей — она уже оплачена отдельной строкой.
Платите за то, что несёт нашу работу: разбор выдачи, снятие защиты и
надстройки вроде скриншота.

## Разбор страницы по селекторам

В коробке разбор ручной: поля задаёте вы, извлекает их браузер на вашей машине.
Языковой модели здесь нет — иначе текст страницы уезжал бы к нам, а весь смысл
коробки в обратном.

```bash
curl -X POST http://127.0.0.1:8123/v1/fetch \
  -H 'Content-Type: application/json' \
  -d '{
        "url": "https://example.com/product",
        "fields": {"price": ".price", "title": "h1", "stock": "[data-stock]"}
      }'
```

```json
{
  "final_url": "https://example.com/product",
  "title": "Кофемашина",
  "fields": {"price": "24 900 ₽", "title": "Кофемашина", "stock": "в наличии"},
  "credits": {"charged": 0, "confirmed": true, "remaining": 998}
}
```

Один селектор — одно значение; несколько совпадений приходят списком. У `meta`
берётся `content`, у картинок `src`, у полей ввода их значение.

> **Примечание.**
>   Чего коробка не сделала — она называет. Попросите `include=iframe` или
>   `extract` — в ответе будет `unsupported: [...]` и объяснение в `note`, а не
>   ответ, который выглядит полным и таковым не является. До 1.3.4 `js`, `css`
>   и `network` тоже выбрасывались молча; теперь они собираются на месте — те же
>   поля и те же пределы, что в облаке.

## Живые вкладки

Вкладки нумеруются с единицы, и номер — это всё, что нужно помнить.

```bash
curl -X POST http://127.0.0.1:8123/v1/browser/open \
  -H 'Content-Type: application/json' -d '{"url":"https://example.com"}'

curl -X POST http://127.0.0.1:8123/v1/browser/click \
  -H 'Content-Type: application/json' -d '{"tab":1,"ref":"b3"}'

curl -X POST http://127.0.0.1:8123/v1/browser/close \
  -H 'Content-Type: application/json' -d '{"tab":1}'
```

## Анкета целиком

Оформление заказа — это шесть-семь полей и кнопка. Отдельными вызовами `fill`
это семь обращений; ручка `form` делает то же за одно, по порядку списка, и
сразу нажимает кнопку отправки:

```bash
curl -X POST http://127.0.0.1:8123/v1/browser/form \
  -H 'Content-Type: application/json' \
  -d '{
        "tab": 1,
        "fields": [
          {"ref": "i3",  "value": "Иван Петров"},
          {"ref": "i5",  "value": "+375290001100"},
          {"ref": "i7",  "value": "да"},
          {"ref": "i13", "value": "самовывоз со склада"}
        ],
        "submit": "b5"
      }'
```

Что писать в `value`, решает вид поля — он приходит в списке элементов полем
`kind`:

| `kind` | `value` |
|---|---|
| `text`, `tel`, `email`, `number`, `textarea` | текст |
| `select` | подпись пункта: `"Самовывоз"` |
| `radio`, `checkbox` | `да` — выбрать, `нет` — снять |

В ответе `filled` — что просили и что вправду легло в поле: маску телефона и
отказ витрины видно именно там. Промах по одному полю не отменяет остальных.

> **Примечание.**
>   Переключатели на витринах спрятаны: сам `input` нулевого размера, а кружок
>   нарисован стилями. В список они всё равно попадают — по видимой подписи, — и
>   у выбранного стоит `checked`. Нажимать уже выбранный не надо: нажатие его
>   снимет. Радиокнопку снять нельзя вовсе, её отменяет выбор соседней в той же
>   группе (`group` в списке).

> **Внимание.**
>   Вкладок по умолчанию три, и закрываются они только по вашей команде. Когда
>   свободных нет, `open` отвечает `429 tabs_busy` и говорит, какие заняты, — а не
>   закрывает чужую работу молча. Нужно больше вкладок — попросите нас, потолок
>   меняется на лету, без переустановки.

## Несколько браузеров

По умолчанию коробка держит один Chrome и работает вкладками в нём: один
профиль, одни куки, одна снятая капча на всех. Когда задач много или нужна
изоляция, задайте потолок браузеров:

```json
{ "browsers": 4 }
```

Или `PARREQ_BOX_BROWSERS=4`. Браузеры поднимаются лениво — по мере того, как
задачи вправду приходят, — и не сверх того, что позволяет память: коробка
измеряет, сколько занимает живой браузер (PSS всего дерева процессов, не
константа), и не поднимает новый, если свободной памяти машины останется
меньше гигабайта. Простоявший двадцать минут браузер гасится; первый (`p0`)
живёт всегда — в нём живые вкладки и прогретый профиль.

Что происходит, видно в состоянии:

```json
"browsers": {
  "limit": 4, "alive": 2, "per_browser_mb": 392, "available_mb": 18240,
  "browsers": [{"name": "p0", "alive": true, "busy": 1, "pss_mb": 410},
               {"name": "p1", "alive": true, "busy": 0, "pss_mb": 374}]
}
```

> **Примечание.**
>   Профили лежат рядом с настройками: `profile` у первого браузера (так было
>   и раньше — прогретые куки не теряются при обновлении) и `profiles/p1`,
>   `profiles/p2`… у остальных.

## Коробка как воркер облака

Для наших собственных коробок — тех, что стоят рядом со службой, — есть
режим воркера: облако раздаёт им задачи поиска и Fetch тем же протоколом, что
домашним агентам, а отвечает клиенту само. Включается настройкой `worker` в
панели, на лету; ёмкость воркера — число браузеров коробки (`browsers`), и
координатор назначает профиль по имени: `p0`, `p1`… — тот, что уже прогрет
под нужный движок.

У заказчиков этот режим выключен и включать его мы не будем: коробка куплена
для его задач, а не для наших. Без настройки коробка на задачу отвечает
отказом словами, а не молчанием — координатор тут же уходит на резерв.

## Если агент живёт в контейнере

Локальный API коробки слушает только петлю: она обслуживает свою машину, и ключ
на петле не спрашивается — кто до неё добрался, тот и так здесь. Но у контейнера
своя петля, и до вашей он не дотянется. Тогда нужно назвать адреса, которым
можно, и слушать не только петлю.

В `config.json` рядом с настройками коробки:

```json
{
  "listen": "0.0.0.0:8123",
  "allow": ["172.17.0.0/16"]
}
```

Или переменными окружения: `PARREQ_BOX_LISTEN=0.0.0.0:8123` и
`PARREQ_BOX_ALLOW=172.17.0.0/16`. После правки перезапустите коробку.

`172.17.0.0/16` — обычная сеть Docker. Свою можно посмотреть так:

```bash
docker network inspect bridge -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
```

Изнутри контейнера коробка будет доступна по адресу шлюза, обычно
`172.17.0.1`. В настройках MCP тогда указывается он:

```json5
"env": {
  "PARREQ_API_KEY": "pr_ваш_ключ",
  "PARREQ_API_URL": "http://172.17.0.1:8123"
}
```

> **Внимание.**
>   С любого адреса, кроме петли, коробка спрашивает ключ — тот же, с которым
>   работает она сама. Иначе сосед по сети контейнеров тратил бы ваши кредиты:
>   локальный API денег не проверяет, он их списывает. Мост MCP передаёт ключ
>   сам, ничего дополнительно делать не надо.

Открывать коробку шире, чем нужно, не стоит. Список `allow` — это именно
список: `["172.17.0.0/16", "10.8.0.5"]` понимает и сети, и отдельные адреса.

## Состояние

```bash
curl http://127.0.0.1:8123/v1/box/state
```

```json
{
  "status": "approved",
  "licensed": true,
  "connected": true,
  "credits": 993,
  "tabs": {"limit": 3, "open": []},
  "egress": {"ready": true, "via": "parreq"},
  "warm": {"ok": true, "note": "готов, результатов: 9"},
  "pricing": {"free_in_box": ["browser_open", "browser_step", "html"], "captcha": 10}
}
```

Прогрев идёт сам, раз в полминуты: профиль должен быть готов раньше вашего
первого запроса, иначе он платит холодным стартом. Если прогрев упирается в
капчу, мы её снимаем, и она списывается как капча.

## Нет связи — нет работы

Коробка держит постоянный канал с нашими серверами. На них лицензия, адреса
выхода и решатель капчи, поэтому без связи она не может выполнить запрос —
и честно об этом говорит, а не отдаёт неполный результат.

| Код | Что случилось |
|---|---|
| `box_pending` | заявка отправлена, ждём нашего подтверждения |
| `box_offline` | связь с нашими серверами потеряна |
| `box_revoked` | лицензия отозвана |
| `box_outdated` | версия устарела, нужно обновиться |
| `tabs_busy` | все вкладки заняты, закройте одну |

Все они приходят с кодом 503 (кроме `tabs_busy` — 429) и объяснением словами.

## Безопасность

Канал шифруется поверх TLS отдельным слоем: эфемерные ключи на каждое
соединение, ChaCha20-Poly1305, счётчик против повторов. Внутри едут учётные
данные прокси, готовый скрипт снятия капчи и настройки лицензии — то, что
незачем видеть даже на вашем собственном сетевом оборудовании.

Ключ решателя капчи и список прокси в коробку не попадают никогда. Локальный
API слушает только петлю: он обслуживает вашу машину, а не сеть.
