Перейти к содержанию

Импорт консьержа с сайта отеля

Для кого: суперадмин (лиды) и отельер (онбординг)

Парсер обходит сайт отеля (главная + внутренние страницы до 2-го уровня, максимум 12 страниц) и собирает черновик консьержа: название, услуги, контакты и (опционально) фото. Для обычных сайтов черновик строится по каркасу базового шаблона (template=hotbot): информационные страницы, сервисы, транспорт, сценарии и т.д. Черновик ревьюится в админке и применяется одним из двух способов:

  1. К компании — создаёт QR-табличку и company_cards (как раньше).
  2. В шаблон консьержа (суперадмин) — услуги становятся собственными карточками шаблона (concierge_template_custom_cards), страницы «Об отеле» / «Контакты» — static pages шаблона. Отельер потом выбирает шаблон из галереи.

Hotbot Shop (дополнение)

Если в то же поле URL вставить витрину вида https://shop.hotbot.ai/{alias} (пример: Grand Chalet Altay), срабатывает отдельная ветка без Firecrawl и без LLM:

  1. Читается SSR window.HotbotConfig со страницы магазина.
  2. Подтягиваются категории (GET /v1/shop/category/{alias}) и статичные страницы (GET /v1/shop/static-page/{id}).
  3. Собирается черновик с template=hotbot: секции главной, static pages, каталог (подкатегория Hotbot → company_card, товары → options; цены в копейках делятся на 100). Плитка с rootCategoryAlias без categoryAlias раскрывается в список подкатегорий (Завтраки / Супы / …); categoryAlias ведёт на конкретную карточку.

Обычные URL сайтов отелей по-прежнему идут через Firecrawl/LLM → каркас hotel-base (hotbot). В админке Hotbot-job помечается бейджем.

Не переносится 1:1: вложенное меню глубже одного уровня idea-категорий, мультиязычность, слоты SPA/расписание, формы обратной связи Hotbot (гость пишет в наш чат), эквайринг/loyalty, туры как модуль Tourmakers.

GoodStay (дополнение)

Если в то же поле URL вставить витрину вида https://{alias}.goodstay.io (пример: Cosmos Прибалтийская), срабатывает отдельная ветка без Firecrawl и без LLM:

  1. Читается SSR window.__PUBLIC_DATA__.hotelEntity (название, бренд mainColor, логотип, фон).
  2. Через GraphQL https://api.goodstay.io/graphql (hotel scope через заголовок Origin, локаль через Accept-Language: ru) подтягиваются:
  3. группы главной (getHotelGroups + sequence SHOP/SERVICE);
  4. список витрин (getShops — в т.ч. main, которых может не быть в groups);
  5. страницы (getPageById) и контакты (getHotelContacts);
  6. магазины/меню (getShopById, getShopSections);
  7. баннер (getBanner).
  8. Собирается черновик с template=hotbot: группы → секции, pages → static pages, shopSection → company_card, позиции секции → options (как у Hotbot subcategory→card), плитки главной → guest_order на карточку категории; main-shop вне groups → отдельная секция с категориями; баннер → external_url, мессенджеры → ссылки или open_chat.

В админке GoodStay-job помечается бейджем; в discovered_urls.parser будет "goodstay".

Не переносится 1:1: виджеты погоды и курсов валют, stories, квизы/опросы (гость пишет в наш чат), заказ/эквайринг/socket TeamJet, мультиязычный draft (берём RU).

Как это устроено

  1. Firecrawl (self-hosted) — краулит сайт через POST /v2/crawl: maxDiscoveryDepth=2, limit=12, sitemap=skip. Отдаёт markdown, ссылки и URL картинок с каждой страницы.
  2. site_parser (Playwright) — fallback, если Firecrawl недоступен или SITE_PARSE_CRAWLER=playwright. В этом режиме парсится только главная.
  3. app (Celery) — склеивает markdown всех страниц, вызывает LLM через HydraAI (deepseek-v4-flash для текста, vision-модель для фото), собирает draft_payload в формате экспорта консьержа. Для Hotbot-URL — HotbotClient + HotbotDraftBuilder; для GoodStay-URL — GoodstayClient + GoodstayDraftBuilder.
  4. Админка — ревью черновика и:
  5. Применить к компании → ConciergeExportService.import_company;
  6. Перенести в шаблон → ConciergeTemplateImportService (только суперадмин).

Список реально обойдённых URL сохраняется в discovered_urls.crawled_pages. Ссылки на номера/услуги/контакты — в rooms_urls / services_urls / contacts_urls. Для Hotbot в discovered_urls.parser будет "hotbot", для GoodStay — "goodstay".

Structured JSON-формат Firecrawl не используется: у self-hosted он требует OpenAI Responses API и ломается на OpenAI-совместимых прокси. Извлечение структуры остаётся на HydraAI (кроме веток Hotbot и GoodStay).

Запуск парсинга

Суперадмин

Меню → Парсинг сайтов отелей (/superadmin/site-parse):

  1. Введите URL главной страницы.
  2. При необходимости отметьте «Даю согласие на импорт фото».
  3. Дождитесь статуса review.
  4. Поправьте услуги/фото при необходимости.
  5. Либо Применить к компании → выберите отельера → Прикрепить.
  6. Либо Перенести в шаблон → создайте новый шаблон или допишите карточки в существующий.

Отельер

Пока у компании нет QR-табличек, на экране онбординга доступна кнопка Импортировать с сайта рядом с галереей шаблонов. После применения открывается редактор созданной QR-таблички. Перенос в глобальный шаблон отельеру недоступен.

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

В app/.env (см. также app/.env.site_parse.example):

HYDRA_API_BASE_URL=https://api.hydraai.ru/v1
HYDRA_API_KEY=sk-hydra-ai-...
HYDRA_TEXT_MODEL=deepseek-v4-flash
HYDRA_VISION_MODEL=gpt-4.1-nano

SITE_PARSE_CRAWLER=firecrawl
SITE_PARSE_CRAWL_MAX_DEPTH=2
SITE_PARSE_CRAWL_MAX_PAGES=12
SITE_PARSE_CRAWL_TIMEOUT_SEC=300

FIRECRAWL_BASE_URL=http://firecrawl-api:3002
FIRECRAWL_API_KEY=

SITE_PARSER_URL=http://site_parser:8090
PARSER_SERVICE_TOKEN=тот-же-токен-что-у-site_parser

В site_parser/.env (fallback):

PARSER_SERVICE_TOKEN=тот-же-токен
PARSER_PROXY_URL=   # опционально
RENDER_MAX_RETRIES=2

Контейнер site_parser на проде: shm_size: 256mb, лимит ~768M. При Page crashed рендер повторяется до RENDER_MAX_RETRIES раз с перезапуском браузера.

Docker

Сервисы Firecrawl добавлены в docker-compose.yml / docker-compose.prod.yml (только app-network, порты наружу не публикуются):

Сервис Локально Прод (Selectel) Роль
firecrawl-api ghcr.io/firecrawl/firecrawl cr.selcloud.ru/tourmakers/firecrawl-api API + workers
firecrawl-playwright ghcr.io/firecrawl/playwright-service cr.selcloud.ru/tourmakers/firecrawl-playwright Рендер страниц
firecrawl-postgres ghcr.io/firecrawl/nuq-postgres cr.selcloud.ru/tourmakers/firecrawl-postgres Очередь NuQ
firecrawl-redis redis:alpine redis:alpine Кэш/рейтлимит Firecrawl
firecrawl-rabbitmq rabbitmq:3-management rabbitmq:3-alpine Очередь сообщений

В CI job mirror-firecrawl тянет образы с ghcr.io и пушит в Selectel; деплой ждёт и build-and-push, и mirror-firecrawl.

backend и celery_worker зависят от firecrawl-api и site_parser.

Прод (≈8G RAM + Supabase): лимиты Firecrawl-стека суммарно ≈2G (api 1G / 1 CPU, playwright 768M, redis/rabbit/pg с потолками), concurrency = 1, BLOCK_MEDIA=true. Без этого стек (api 3G + playwright 2G + site_parser 2G) уводит хост в thrashing (load 50+, iowait, avail Mem → 0). На машине с ≥16G можно поднять лимиты ближе к локальным.

API

Метод Путь Описание
POST /site-parse/jobs Создать задачу
GET /site-parse/jobs Список
GET /site-parse/jobs/{id} Статус и черновик
PATCH /site-parse/jobs/{id}/draft Правки перед применением
POST /site-parse/jobs/{id}/apply?company_id= Применить к компании
POST /site-parse/jobs/{id}/apply-template Перенести в шаблон (суперадмин)

Тело apply-template:

{
  "template_id": null,
  "title": "Название нового шаблона",
  "slug": null,
  "guest_template": "classic",
  "is_active": true,
  "append_cards": true
}

Если template_id задан — карточки дописываются в существующий шаблон (append_cards=true) или заменяют собственные (append_cards=false).

Статусы: queued → rendering → analyzing → photos → review → applied (или failed). После переноса в шаблон в job пишется applied_template_id.