← Все задачи

🏨 Bestonline-tour.ru — Фаза 1: WordPress+Bricks инфраструктура под 7250 отелей

bestonline-tour.ru Дата: 21.04.2026 Приоритет: Высокий Исполнитель: Программист WP Оценка: 2 недели

Содержание

  1. Большая картина — что строим и зачем
  2. Концепция SEO-обёртки
  3. Что уже сделано (19-21 апреля)
  4. Стек (утверждённый)
  5. Входные данные для программиста
  6. Задачи Фазы 1 (что делать прямо сейчас)
  7. Что НЕ делаем пока (блокеры)
  8. Чеклист приёмки Фазы 1
7 250
Отелей в базе
10
Стран (приоритет)
42
С доп. деталями
~16 000
Отелей финально
8
Задач Фазы 1

1. Большая картина — что строим и зачем

Цель проекта: превратить bestonline-tour.ru из «сайта с поисковым виджетом» в SEO-машину с 16 000 отдельных посадочных страниц отелей.

Что есть сейчас

Что хотим увидеть

Объём

2. Концепция SEO-обёртки

Ключевая идея: разделяем «контент, за который отвечаем мы» и «контент, за который отвечает Tourvisor».

ЭлементКто делаетПочему
Title, Meta DescriptionМы (шаблон)SEO-уникальность, ключи, бренд
H1МыГлавный заголовок — под поисковый запрос
Breadcrumbs + schemaМыНавигация + rich snippet в выдаче
Бокс «Главное» (факты)Мы (из структурированных полей Tourvisor)Быстрый ответ, AI-friendly, AggregateRating
FAQ (5-7 вопросов)Мы (AI Claude + шаблон)FAQPage schema, PAA-захват
Schema.org (Hotel, FAQ, Breadcrumb)МыЗвёзды в SERP, понимание контента Google
OG-теги, canonical, hreflangМыСоцсети, каноникализация
Фото, описание, услуги, номераTourvisor виджетИх контент, мы НЕ копируем
Актуальные цены, доступностьTourvisor виджетОбновление онлайн с их стороны
Форма бронированияTourvisor виджетИх система, наша комиссия

⚠ Правовая граница: факты из Tourvisor (год постройки, количество вилл, название атолла, расстояние до аэропорта) — можно использовать (факты не охраняются ГК РФ). Авторский текст описания (их формулировки) — копировать нельзя, это произведение. Мы парсим поля и собираем свой шаблонный текст.

3. Что уже сделано (19-21 апреля)

19 апреля — фундамент

20 апреля — URL-структура и Мальдивы

21 апреля — пилотная выгрузка + боевое демо

4. Стек (утверждённый)

СлойРешениеСтатус
CMSWordPress 6.x✅ установлен
Page builderBricks Builder✅ установлен (лицензия 14.04)
Custom fieldsACF Pro🟡 поставить
SEORank Math Pro🟡 поставить
CacheRedis + WP Rocket🟡 настроить
HostingBeget (PHP 8.2, Nginx)
WP-CLIдля импорта 16k отелей🟡 настроить
CloudflareНЕ используем (РФ-аудитория)

5. Входные данные (что даём программисту)

Все файлы в репозитории Bestonline-tour.ru/. Запрашивать через менеджера.

Файл / ссылкаЧто внутриПрименение
meta/hotels.json 7 250 отелей — базовые поля (id, name, category, rating, country_id, country_name, region_id, region_name, subregion, slug) Источник для импорта в CPT + таксономии
meta/details/*.json (42 файла + растёт) Детали отеля: сommon.build, сommon.place, infrastructure.beach, meals.list, images[]. ⚠ Ключ сommon с кириллической «с» — опечатка Tourvisor, учитывать Заполнение ACF-полей (год, атолл, пляж, питание)
meta/countries.json, regions.json, subregions.json Справочники Tourvisor Предзаполнение terms таксономий
Боевое демо страницы Референс финального вида (HTML без dev-маркеров, с ТЗ-блоком под виджет) Визуальный референс для Bricks template
Список 50 превью Все 50 пилотных страниц с dev-маркерами H1/H2/FAQ Посмотреть разнообразие контента по странам
generate-wrappers.py Python-генератор HTML — референс логики для словарей COUNTRY_FACTS, REGION_FORMAT, SEASON_FAQ, VISA_FAQ, COUNTRY_PREP Портировать словари в WP options или term meta

6. Задачи Фазы 1

ЗАДАЧА 1 / 8

🏗 Custom Post Type hotel + rewrite URL

На WordPress отель как отдельная сущность не существует «из коробки» — есть только стандартные «Посты» и «Страницы». Для 7250 отелей нам нужен отдельный тип записи с собственной админкой, собственными полями и собственной URL-структурой.

  1. Регистрируем CPT hotel (плагин CPT UI или через functions.php в child-теме)
  2. Настраиваем rewrite rules: URL отеля должен быть /destinations/{country-slug}/{region-slug}/{hotel-slug}/
  3. Особый случай Мальдив: когда регион = стране, URL двухуровневый: /destinations/maldives/{hotel-slug}/ (не повторяем maldives дважды)
  4. Включаем has_archive для архивных страниц (см. Задачу 8)
  5. Поддерживаем меню-позицию, иконку, REST API
register_post_type('hotel', [
  'labels' => ['name' => 'Отели', 'singular_name' => 'Отель'],
  'public' => true,
  'has_archive' => 'destinations',
  'show_in_rest' => true,
  'menu_icon' => 'dashicons-building',
  'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
  'rewrite' => ['slug' => 'destinations/%country%/%region%', 'with_front' => false],
]);
// + add_rewrite_rule для обработки same-as-country case (Мальдивы)
// + фильтр post_type_link для подстановки страны/региона из таксономий
  • В админке появился раздел «Отели»
  • Создал тестовый отель в Турции/Алании → URL /destinations/turkey/alanya/test-hotel/ работает (пусть 404, главное rewrite матчится)
  • Создал тестовый отель на Мальдивах → URL /destinations/maldives/test-hotel/ (без дубля maldives)

1-2 дня нет блокеров

ЗАДАЧА 2 / 8

🏷 Таксономии (страна, регион, subregion, звёзды, тип)

Отели связаны между собой через страну/регион/звёзды. Без таксономий невозможно: (а) построить archive-страницы «все отели Турции», (б) перелинковать похожие отели, (в) сделать breadcrumbs, (г) фильтровать в админке. Это каркас связей всей базы.

ТаксономияSlugИерархичностьПример terms
Странаcountryнетturkey, maldives, egypt
Регионregionда (родитель = страна)alanya (turkey), male (maldives)
Район/субрегионsubregionда (родитель = регион)патонг (phuket)
Звёздыstarsнет1, 2, 3, 4, 5
Тип отеляhotel_typeнетisland-resort, city-hotel, beach-resort
  • Slug всегда английский — стандарт тревел-индустрии (не kitay, а china; не alania, а alanya)
  • Словари английских slug лежат в generate-wrappers.pyCOUNTRY_SLUG_EN, REGION_SLUG_EN (портировать в WP)
  • Имя terms (name) — русское, для отображения («Турция», «Аланья»)

1 день нет блокеров

ЗАДАЧА 3 / 8

📥 Импорт terms таксономий (страны / регионы / subregions)

Заводить руками 10 стран, 150+ регионов и 500+ субрегионов — неэффективно и чревато ошибками. Всё уже есть в meta/hotels.json. Один скрипт извлекает уникальные тройки (страна → регион → субрегион) и создаёт terms с правильными slug и иерархией.

  • meta/hotels.json — 7 250 отелей с полями country_name, country_slug (ru-транслит), region_name, region_slug, subregion_name, subregion_slug
  • Словари COUNTRY_SLUG_EN, REGION_SLUG_EN из generate-wrappers.py — для замены транслита на английские slug
  • В таксономии country — 10 terms (turkey, maldives, egypt, …)
  • В region — ~150 terms, каждый с parent = соответствующей страной
  • В subregion — ~500 terms, каждый с parent = региону
// wp eval-file import-terms.php
$hotels = json_decode(file_get_contents('meta/hotels.json'), true);
$country_slug_map = [
  'Турция' => 'turkey', 'Мальдивы' => 'maldives',
  // … из COUNTRY_SLUG_EN
];

foreach ($hotels as $h) {
  // 1. Создать term country если нет
  $country_slug = $country_slug_map[$h['country_name']] ?? $h['country_slug'];
  $country = wp_insert_term($h['country_name'], 'country', ['slug' => $country_slug]);
  // 2. Создать region с parent=country
  // 3. Создать subregion с parent=region
}

1 день нет блокеров — зависит от Задачи 2

ЗАДАЧА 4 / 8

📋 ACF Pro поля для CPT hotel

Стандартные поля WP-поста (title, content, excerpt) не вмещают всю информацию об отеле: рейтинг 4.8/5, категория 5★, год постройки 2022, тип пляжа, список питания, ID для Tourvisor-виджета. ACF Pro — индустриальный стандарт для структурированных метаданных в WordPress. Bricks нативно читает ACF через dynamic tags.

ACF полеТипИсточник JSONОбязательно
tv_idNumberid✅ критично (для виджета)
categoryNumber (1-5)categoryопционально (может быть 0)
ratingNumber (1-5, 0.1 шаг)ratingопционально
build_yearTextсommon.build из detailsопционально
atollTextregex из сommon.place (только Мальдивы)опционально
beach_rawTextareainfrastructure.beach (первый <LI>)опционально
meal_codesCheckbox (BB/HB/FB/AI/UAI/RO)regex из meals.listопционально
images_countNumberlen(images)опционально
hotel_formatSelectсловарь REGION_FORMAT из Pythonопционально
place_rawTextareaсommon.placeопционально (для дебага)

В Bricks-шаблоне факт-бокс показывает только заполненные поля. Если build_year пусто — строка «🔨 Открыт: 2022» не выводится. Это основа принципа «SEO-обёртка работает даже для отеля с минимальными данными».

2 дня нет блокеров

ЗАДАЧА 5 / 8

🤖 WP-CLI импорт-скрипт отелей

7 250 отелей (а финально 16 000) руками не заведёшь — это сотни часов работы. Скрипт читает наши JSON, создаёт посты CPT hotel, назначает таксономии, заполняет ACF. Должен быть идемпотентным — повторный запуск не создаёт дубликаты, а обновляет существующие.

  1. Читаем meta/hotels.json
  2. Для каждого отеля:
    • Ищем существующий пост по meta_query: tv_id = hotel.id
    • Если есть — обновляем; если нет — создаём новый
    • Slug поста = hotel.slug (с обработкой коллизий: если slug уже есть в той же country+region — добавляем суффикс -{tv_id}, см. _raw_hotel_slug() в Python)
    • Назначаем taxonomy terms: country, region, subregion, stars, hotel_type
    • Заполняем ACF поля из базового JSON
    • Если есть meta/details/{id}.json — парсим и заполняем дополнительные поля (build, atoll, beach, meals)
  3. Логируем статистику: создано / обновлено / пропущено / ошибок
// Ключ 'сommon' содержит кириллическую «с» — опечатка API Tourvisor
$common_key = 'сommon';  // НЕ 'common'!
$common = $detail[$common_key] ?? [];

// Год постройки
preg_match('/\b(19|20)\d{2}\b/', $common['build'] ?? '', $m);
$build_year = $m[0] ?? '';

// Атолл (только Мальдивы)
if ($country === 'maldives' && !empty($common['place'])) {
  preg_match('/атолл[е]?\s+([А-Яа-я\w\s]+?)(?:[,;]|$)/iu', $common['place'], $m);
  $atoll = trim($m[1] ?? '');
}

// Типы питания (BB/HB/FB/AI/UAI/RO)
$meals_raw = strip_tags($detail['meals']['list'] ?? '');
$meal_codes = [];
foreach (['RO', 'BB', 'HB', 'FB', 'AI', 'UAI'] as $code) {
  if (preg_match('/\b' . $code . '\b/', $meals_raw)) $meal_codes[] = $code;
}
wp eval-file import-hotels.php --url=bestonline-tour.ru
# или
wp hotel import meta/hotels.json --details=meta/details/

3-5 дней нет блокеров — зависит от Задач 1, 2, 3, 4

ЗАДАЧА 6 / 8

🎨 Bricks template single-hotel (скелет без финального дизайна)

Один шаблон рендерит все 7 250+ отелей. Динамические значения подтягиваются через Bricks dynamic tags: {acf:tv_id}, {post_title}, {post_terms:country}. Это фундамент. Финальный дизайн блоков (бокс «Главное», FAQ, sticky nav) — допиливаем на demo параллельно, подключим блоки к шаблону за 2-3 дня.

  1. Breadcrumbs: Главная → Страна (term link) → Регион (term link) → Название отеля
  2. Hero: звёзды (из ACF category), H1 = «{название} — отель {на/в} {регион/страна}»
  3. Бокс «Главное» (пока placeholder, финальный блок добавим позже): формат, год, рейтинг, атолл, пляж, питание, сезон, перелёт, виза
  4. Виджет Tourvisor: <div class="tv-embed" tv-hotels="{acf:tv_id}"></div> (точный класс ждём от Tourvisor)
  5. CTA-блок
  6. FAQ (5-7 вопросов, генерация через Claude API на этапе импорта)
  7. Похожие отели (Loop Query по той же таксономии region)
  8. Footer
  • Все тексты — из ACF / таксономий / словарей, никаких захардкоженных значений
  • Год в Title/Description берём через фильтр date('Y') — никогда не хардкодим 2026
  • Шаблон должен работать когда нет details — бокс «Главное» показывает только то, что есть (country facts всегда есть, остальное условно)
  • Кеш Redis — шаблон рендерится 1 раз, последующие заходы из кеша

3-5 дней частично: финальные блоки от нас (параллельно)

ЗАДАЧА 7 / 8

📊 Schema.org разметка (Hotel + FAQPage + BreadcrumbList)

Google и Яндекс показывают rich snippets (звёзды рейтинга, FAQ-аккордеон, хлебные крошки в выдаче) только если на странице есть корректная Schema.org-разметка. Это даёт +30% CTR (звёзды в выдаче). Плюс ChatGPT/Google AI Overview цитируют структурированный контент охотнее.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Hotel",
      "@id": "{canonical_url}#hotel",
      "name": "{post_title}",
      "starRating": { "@type": "Rating", "ratingValue": "{acf:category}" },
      "aggregateRating": {
        "@type": "AggregateRating",
        "ratingValue": "{acf:rating}",
        "bestRating": "5"
      }
    },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [...]
    },
    {
      "@type": "FAQPage",
      "mainEntity": [... FAQ из ACF ...]
    }
  ]
}
</script>

Можно через Rank Math Pro (встроенный Schema generator для Hotel/FAQ) или руками в functions.php через фильтр wp_head. Rank Math проще, но менее гибкий. Руками — полный контроль.

1-2 дня нет блокеров

ЗАДАЧА 8 / 8

🗂 Archive-страницы (страна / регион)

Страница «Все отели Турции» (/destinations/turkey/) — мощная SEO-посадочная по запросам «турция отели», «отели в турции 5 звёзд». И база перелинковки: с каждого отеля Турции — ссылка «все отели Турции».

  • Bricks template archive-country → рендерится для URL /destinations/{country-slug}/
  • Loop Query: все hotel с этой таксономией country, сортировка по рейтингу DESC
  • Title/H1: «Отели {country_prep_case} {current_year} — {кол-во} предложений»
  • Карточка отеля: фото (из виджета / placeholder), название, звёзды, регион, ссылка
  • Фильтры сбоку: по звёздам, по региону, по типу (island-resort/city)
  • Пагинация (по 30 на страницу)

Template archive-region/destinations/{country}/{region}/ — все отели конкретного региона.

2-3 дня частично: визуал карточек — от нас

7. Что НЕ делаем сейчас (ждём)

8. Чеклист приёмки Фазы 1