Skip to content

Раздел 3: Оркестратор и исполнитель

Проект: ADOLF — AI-Driven Operations Layer Framework
Модуль: Watcher / Orchestrator & Runner
Версия: 4.0
Дата: Февраль 2026


3.1 Обзор

Раздел описывает три модуля, обеспечивающих выбор ПК, управление пулом браузеров и выполнение задач сбора данных.

МодульФайлПроцессРоль
CDP Poolcdp-pool.jscdp-pool.service (:3000)Обнаружение, мониторинг и блокировка браузеров
Orchestratororchestrator.jsвнутри bot.jsВыбор оптимального ПК для задачи
Runnerrunner.jsвнутри bot.jsЗапуск сканеров/обогатителей как дочерних процессов

Цепочка вызовов: scheduler.tryRunNext()orchestrator.choosePC()cdp.acquireBrowser()runner.runScan() / runner.runEnrich().


3.2 CDP Pool (cdp-pool.js)

Автономный HTTP-сервис на порту 3000 (localhost). Управляет жизненным циклом подключений к Chrome-браузерам на домашних ПК через FRP-туннели.

Цикл обнаружения

CDP Pool каждые 10 секунд выполняет полный цикл обнаружения и проверки.

Состояния ПК

Только ПК в состоянии Stable (alive + stable + не busy) доступны для назначения задач.

Структура объекта PC

ПолеТипОписание
namestringИмя ПК (из FRP: cdp-<name><name>)
cdpPortnumberПорт на VPS (9300–9399)
alivebooleanCDP отвечает
stablebooleanOnline более 60 секунд
busybooleanЗанят задачей
busyBystring/nullID задачи, занявшей ПК
clientIPstring/nullIP клиента (зарезервировано)
browserstring/nullВерсия Chrome (из /json/version)
osstring/nullОС (из User-Agent)
connectedAtISO stringВремя обнаружения
lastSeenAtISO stringВремя последнего ответа CDP
tasksCompletednumberСчётчик выполненных задач

HTTP API (:3000)

МетодEndpointОписаниеОтвет
GET/statusСписок всех ПК[{name, cdpPort, alive, stable, busy, ...}]
GET/summaryАгрегированная статистика{total, online, stable, busy, free}
GET/acquire?task=X&port=PЗанять ПК для задачи200: {name, cdpPort, browser}
GET/release?port=PОсвободить ПК200: {ok: true}
GET/syncПринудительный цикл обнаружения{total, online, stable, busy, free}

Логика /acquire

Если preferPort указан (от оркестратора) — используется конкретный ПК. Если нет — выбирается свободный с наименьшим tasksCompleted (балансировка нагрузки на уровне пула).

Константы

КонстантаЗначениеОписание
POOL_PORT3000Порт HTTP API
FRP_API_PORT7500Порт FRP Admin API
POLL_INTERVAL10 000 мсИнтервал обнаружения
STABLE_AFTER_MS60 000 мсВремя до стабильности
OFFLINE_REMOVE_MS1 800 000 мс (30 мин)Время до удаления из пула

3.3 Orchestrator (orchestrator.js)

Модуль выбора оптимального ПК для задачи. Реализует скоринговую модель, предотвращающую детектирование паттернов маркетплейсами.

Алгоритм choosePC

Вход: marketplace (wildberries | ozon | yandex_market), sellerId.

Скоринговая модель

ФакторВлияние на scoreОписание
Тот же продавец+100Последняя задача на этом ПК была для того же продавца
Тот же маркетплейс+50Последняя задача на том же маркетплейсе
Количество задач+NЧисло задач за последние 24 часа
Простой−(до 20)1 балл за каждые 10 мин простоя (макс. −20)
Никогда не использовался−20Максимальный бонус для нового ПК

Пример расчёта для 3 ПК при задаче wildberries / seller_123:

ПКПоследняя задачаЗадач/24чПростойScore
PC-1ozon / seller_45655 − 12 = −7
PC-2wildberries / seller_123330мин100 + 50 + 3 − 3 = 150
PC-3(нет)00 − 20 = −20

Результат: выбран PC-3 (score −20, наименьший).

Cooldown

Если все кандидаты нарушают правила чередования (тот же продавец или маркетплейс), оркестратор устанавливает cooldown для лучшего кандидата и возвращает null.

ПараметрЗначениеHot-reloadОписание
orchestrator.cooldownMinMs300 000 (5 мин)ДаМинимальный cooldown
orchestrator.cooldownMaxMs600 000 (10 мин)ДаМаксимальный cooldown

Cooldown хранится in-memory (Map: port → cooldownUntil). Очищается при завершении задачи на данном ПК (completeAssignment).

История назначений

Оркестратор записывает каждое назначение в SQLite-таблицу assignments:

sql
CREATE TABLE assignments (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    scan_id INTEGER REFERENCES scans(id),
    pc_port INTEGER NOT NULL,
    pc_name TEXT,
    marketplace TEXT NOT NULL,
    seller_id TEXT NOT NULL,
    started_at TEXT DEFAULT (datetime('now')),
    completed_at TEXT
);

Функции:

ФункцияОписание
recordAssignment(scanId, port, name, mp, sellerId)Запись нового назначения
completeAssignment(scanId, port)Установка completed_at, очистка cooldown
nextCooldownExpiry()Секунд до ближайшего освобождения cooldown

Параметры конфигурации

ПараметрПо умолчаниюМинМаксHot-reloadОписание
orchestrator.cooldownMinMs300 00003 600 000ДаМин. cooldown
orchestrator.cooldownMaxMs600 00007 200 000ДаМакс. cooldown
orchestrator.penaltySameSeller10001 000ДаШтраф за того же продавца
orchestrator.penaltySameMarketplace5001 000ДаШтраф за тот же маркетплейс
orchestrator.idleBonusMax200100ДаМакс. бонус за простой
orchestrator.idleBonusIntervalMs600 000 (10 мин)60 0003 600 000ДаИнтервал начисления бонуса
orchestrator.neverUsedBonus200100ДаБонус для нового ПК

Кросс-валидация: cooldownMinMscooldownMaxMs.


3.4 Runner (runner.js)

Модуль запуска и управления задачами сбора данных. Выполняет сканеры и обогатители как дочерние процессы Node.js (child_process.spawn).

Архитектура запуска

Типы задач

ТипКарта скриптовТаймаутПовторCDP
scanSCANNER_MAP3 часаДо 3 разВсегда
enrichENRICHER_MAP30 минНетWB: нет; Ozon, YM: да

Карты скриптов:

SCANNER_MAP:
  wildberries   → /opt/watcher/SKILL/scanner_wb.js
  ozon          → /opt/watcher/SKILL/scanner_ozon.js
  yandex_market → /opt/watcher/SKILL/scanner_ymarket.js

ENRICHER_MAP:
  wildberries   → /opt/watcher/SKILL/enricher_wb.js
  ozon          → /opt/watcher/SKILL/enricher_ozon.js
  yandex_market → /opt/watcher/SKILL/enricher_ymarket.js

ENRICHER_NEEDS_CDP:
  ozon: true
  yandex_market: true
  (wildberries — HTTP, без CDP)

Параметры дочернего процесса

Аргументы командной строки:

АргументПозицияОписание
sellerId1ID продавца на маркетплейсе
resultDir2Директория для записи результатов (/opt/watcher/results)

Переменные окружения:

ПеременнаяОписаниеНаличие
CDP_PORTПорт Chrome CDP на VPSScan: всегда; Enrich: только Ozon/YM
MARKETPLACEИдентификатор маркетплейсаТолько scan
SELLER_SLUGSlug продавца (для URL)Только scan

stdio: stdin — ignore, stdout/stderr — pipe в лог-файл.

Обработка результатов скана

При exit code 0 и наличии файла results_seller_<id>.json:

Обе вставки (products и price_history) выполняются в транзакциях (db.transaction) для атомарности.

Обработка результатов обогащения

При exit code 0 и наличии файла enriched_seller_<id>.json:

Автоактивация: при первом обогащении продавца с enrich_schedule_hours = 0 автоматически устанавливается расписание из scheduler.defaultEnrichScheduleHours (24ч).

Обработка ошибок

СитуацияПоведение
Exit code ≠ 0scans.status = 'failed', error_message = 'Exit code: N'
Файл результата отсутствуетАналогично exit code ≠ 0
Ошибка парсинга JSONscans.status = 'failed', error_message = 'Parse error: ...'
Таймаут (scan: 3ч, enrich: 30мин)SIGTERM дочернему процессу, освобождение ПК
Pool недоступен при acquireИсключение, обработка в scheduler
Нет свободных браузеровИсключение, обработка в scheduler

Автоповтор (только scan)

При exit code ≠ 0, если retry_count < maxRetries:

  1. Создаётся новая запись в scans для того же продавца (status: queued)
  2. Отправляется алерт: 🔄 Автоповтор (попытка N/3)
  3. Новая задача будет подхвачена scheduler.tryRunNext() в следующем цикле

retry_count инкрементируется на уровне БД при создании повторной задачи. После 3 неудачных попыток задача остаётся в failed без автоповтора.

Обогащение не имеет автоповтора — при необходимости перезапускается вручную через /enrich.

Параллельность

Runner поддерживает одновременное выполнение нескольких задач:

ТрекерХранилищеОписание
runningTasksMap<scanId, taskInfo>Активные scan-задачи
runningEnrichTasksMap<scanId, taskInfo>Активные enrich-задачи

Лимит scan-задач определяется количеством свободных ПК в CDP Pool. Лимит enrich-задач — параметром scheduler.enrichLimit (по умолчанию 2).

Структура taskInfo:

ПолеТипОписание
scanIdnumberID задачи в scans
sellerIdstringID продавца
sellerNamestringИмя продавца
startedAtnumberDate.now() при запуске
cdpPortnumber/nullПорт CDP (null для WB enrich)
taskTypestring'enrich' (только для enrich-задач)

Параметры конфигурации

ПараметрПо умолчаниюМинМаксHot-reloadОписание
runner.scanTimeoutMs10 800 000 (3 ч)60 00086 400 000ДаТаймаут сканирования
runner.enrichTimeoutMs1 800 000 (30 мин)60 00086 400 000ДаТаймаут обогащения
runner.maxRetries3010ДаМакс. автоповторов (scan)

3.5 CDP Client Module (cdp.js)

Модуль-клиент для взаимодействия с CDP Pool из bot.js / runner.js.

ФункцияHTTP-вызовОписание
getPoolSummary()GET :3000/summary{total, online, stable, busy, free}
getPoolStatus()GET :3000/statusМассив объектов ПК
acquireBrowser(taskId, preferPort)GET :3000/acquire?task=X&port=PЗанять ПК; null если нет свободных
releaseBrowser(port)GET :3000/release?port=PОсвободить ПК
checkBrowser()getPoolSummary + getPoolStatusЕсть ли хотя бы один alive ПК (fallback)
takeScreenshot(port)CDP /json/list + execСкриншот текущей страницы

Таймауты: 3 сек для summary/status/release, 5 сек для acquire.


3.6 Утилиты (utils.js)

computeDiff(current, previous)

Сравнение двух наборов продуктов по SKU. Используется командой /diff.

Вход: два массива объектов {sku, name, price, ...}.

Выход:

ПолеОписание
newProductsТовары, присутствующие в current, но не в previous
removedТовары, присутствующие в previous, но не в current
priceChangesТовары с изменённой ценой (отсортированы по

Каждый элемент priceChanges содержит: исходные поля товара + prevPrice, change (абсолютное), pct (процентное).


3.7 Полная цепочка выполнения

Диаграмма объединяет все три модуля раздела в единый поток:


Документ подготовлен: Февраль 2026
Версия: 4.0
Статус: Черновик

Документация ADOLF Platform