← На главную

Опубликовано

Как я научил Hermes Agent превращать сайт в HTTP-клиент

Автор: Гусев Николай [портфолио]

Темы: ai-agents, llm-api

Как я научил Hermes Agent превращать сайт в HTTP-клиент

Так я подключал к личному ассистенту сайт аптеки, историю чеков ФНС и источники домашней бухгалтерии. Обычно на новый коннектор уходит пара часов. Агент не начинает исследование с пустого места: удачные приёмы, команды и известные подводные камни уже записаны в скилле har-derived-api-client.

Кто что делает

Захватываем и разбираем HAR

Сначала ставлю скилл и Playwright. Затем перехожу в каталог scripts установленного скилла. В примере селектор и адрес заменены, их нужно взять со своего сайта.

hermes skills install official/web-development/har-derived-api-client
pip install playwright requests
playwright install chromium
cd ~/.hermes/skills/har-derived-api-client/scripts
python3 har_capture.py "https://<site>/" out.har --headed \
  --action "fill:input[name=search]:test query" --action "sleep:3"
python3 har_to_client.py out.har --host <site> --max-body 400

sleep:3 здесь не украшение. Поиск часто ждёт, пока человек перестанет печатать, и только потом отправляет запрос. Если действие завершится с ошибкой, Playwright не успеет закрыть контекст и записать HAR. В таком случае я исправляю селектор и запускаю захват снова. Официальная документация Playwright отдельно напоминает, что контекст надо закрыть, иначе HAR не сбросится на диск: параметры browser.new_context.

Команда разбора группирует одинаковые пути, заменяет числовые и UUID-сегменты на {id}, показывает образец тела и печатает блок Replay hints. В нём особенно важен браузерный User-Agent. Стандартный python-requests некоторые сайты встречают ответом 403.

Если Hermes подключён к уже открытому Chrome или облачному браузеру по CDP, нужен другой захватчик. Команда выглядит так: python3 har_capture_cdp.py "$BROWSER_CDP_URL" out.har --goto "https://<site>/" --action "fill:input[name=search]:test query" --action "sleep:3" --wait 2. Скрипт подключается к существующей вкладке и не закрывает чужой браузер. Локальный har_capture.py, напротив, запускает собственный Chromium.

Переносим запрос из браузера в Python

Из отчёта har_to_client.py агент берёт URL, параметры и браузерный идентификатор. Минимальный клиент для открытого JSON-эндпоинта получается таким:

import requests

r = requests.get(
    "https://<site>/api/search",
    params={"query": "test query", "limit": 10},
    headers={"User-Agent": "Mozilla/5.0 ... Chrome/... Safari/537.36"},
    timeout=15,
)
r.raise_for_status()
print(r.json())

Строку User-Agent нельзя копировать из статьи. Её выводит анализатор из вашего HAR. Если Replay hints сообщает о cookie или заголовке с токеном, клиенту понадобится и он. Справочник по передаче заголовков есть в Requests Quickstart.

Сначала агент сравнивает ответ клиента с тем, что видел браузер. Только после этого запрос становится функцией плагина или CLI-командой. Если сервер вернул 401 или 403, причина обычно не в Python: протухла сессия, потерян служебный заголовок или библиотека отправила другой User-Agent.

Почему у сайта аптеки понадобились и HTML, и JSON-RPC

Первый практический сюрприз встретился на сайте аптеки. Поиск, цены и наличие приходили внутри серверного HTML. В HAR не было отдельного JSON со списком товаров, поэтому автоматическое выделение XHR не могло его найти. Агент оставил поиск обычным GET, а карточки разобрал из HTML.

Корзина работала иначе. Браузер отправлял JSON-RPC-запрос cart.AddV2, причём сервер связывал корзину с cartId из тела, а не с cookie. Ниже форма реального запроса, но домен, товар и регион заменены, чтобы не публиковать пользовательские данные.

{
  "method": "cart.AddV2",
  "params": {"deliveryType": 11, "force": false,
    "cartId": "<cart_id>", "cityId": "<city_id>",
    "options": {"isPartial": true, "withDisabled": true,
      "withUnchecked": true, "isDeliveryOnly": true},
    "product": {"count": 1, "shopId": null, "productId": "<product_id>"}},
  "jsonrpc": "2.0", "id": 1
}

Клиент отправляет это тело через POST https://<домен-сайта-аптеки>/rpc/?method=cart.AddV2 с Content-Type: application/json. Для автономной корзины cookie не нужны. Но если результат должен появиться в той же корзине, которую я вижу на сайте, агент один раз читает её cartId из моей открытой сессии и хранит идентификатор вне репозитория.

Здесь проверка HTML оказалась важнее красивой гипотезы об API. Если har_to_client.py пишет No API-looking entries found, данные могли прийти с первой HTML-страницей. Тогда надо разбирать HTML, а не выдумывать отсутствующий эндпоинт.

Чеки ФНС и обновляемая сессия

Для ФНС я сам вошёл в браузере по номеру телефона и коду из SMS. Агент прочитал из localStorage токен доступа, refresh-токен и срок действия, сохранил их в файл вне Git и написал обновление сессии. После этого клиент получает страницы списка чеков и отдельно запрашивает состав каждого чека.

Такой клиент использует только мою авторизованную сессию. HAR, токены и выгрузки содержат персональные данные. Я добавляю out.har, secrets/, файлы токенов и JSONL в .gitignore, не прикладываю их к задачам и удаляю HAR после того, как контракт запроса перенесён в код.

Несколько сервисов для домашней бухгалтерии

Для домашней бухгалтерии тот же приём нужен сразу для нескольких вкладок. Я вхожу в каждый сервис в Chrome. Агент подключается к браузеру по CDP, перебирает открытые вкладки и пишет отдельный файл recon/<service>.har. Затем его extract_api.py оставляет уникальные JSON-пути, методы, статусы и короткие образцы тел и собирает общую сводку recon/api_index.json.

По этой сводке видны настоящие контракты всех сервисов без гигабайтов статики. Агент пишет для каждого источника коннектор с одинаковым циклом: получить сырые данные, привести их к общей модели покупок и сохранить. Куки лежат в secrets/<service>_cookies.json, нужные заголовки в secrets/<service>_headers.json. Это важная деталь: часть личных кабинетов проверяет собственный заголовок с Bearer-токеном и не принимает одну лишь cookie.

Что остаётся после пары часов

Повторить процесс можно по исходникам самого скилла: в репозитории лежат SKILL.md и три рабочих скрипта. Как Hermes устанавливает и загружает такие пакеты, описано в документации системы скиллов. Частные API меняются без предупреждения, поэтому сломанный клиент я не чиню подбором случайных URL. Я снова записываю одно действие, сравниваю новый HAR со старым и обновляю скилл. Следующий похожий сайт после этого разбирается быстрее.