В этом пошаговом руководстве рассмотрим, как из набора поисковых запросов можно выстроить готовую структуру сайта с помощью пайплайна BERTopic. В качестве инструментария будем использовать Screaming Frog SEO Spider для парсинга, Anaconda, локальный сервер (LM Studio) и бесплатные модели. Любой элемент можно заменить другими средствами с походим функционалом. На выходе мы получим готовую иерархическую схему разделов и подразделов сайта с чёткими семантическими границами, которую можно сразу переносить в ТЗ на разработку структуры или визуализировать в майндмап.
Подготовка семантического ядра #
Вам нужен CSV-файл минимум с двумя колонками: keyword и frequency (частотность). Источники — любые: Key Collector, Ahrefs, Serpstat, парсинг подсказок, выгрузка из Google Search Console, Яндекс-Вебмастер, keys.so и т.п..
Функции Screaming Frog на этом этапе:
- Выгрузка запросов из GSC через API (Configuration → API Access → Google Search Console → подключите аккаунт). Затем в режиме Search Console → загрузите запросы за большой период (например, 16 месяцев). Экспортируйте в CSV через Reports → Search Console → Queries.
- Сбор запросов конкурентов (опционально). Можно скраудить страницы конкурентов (режим Spider), а затем собрать их Title, H1, мета-описания. Эти текстовые поля потом тоже можно прогнать через BERTopic для сравнения структуры. Но для начала достаточно своего ядра.
Положите итоговый CSV, например, keywords.csv в папку проекта.
Настройка LM Studio как embedding-сервера #
BERTopic нуждается в преобразовании текста в векторы (эмбеддинги). LM Studio умеет запускать embedding-модели с интерфейсом, совместимым с OpenAI API. Используем это.
Скачайте подходящую embedding-модель, например:
- intfloat/multilingual-e5-large (хорошо работает с русским языком)
- sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
- embeddingGemma
Загрузите её в LM Studio. Перейдите во вкладку Local Server:
- Выберите модель.
- В настройках сервера (Settings) включите Embeddings endpoint.
- Укажите порт (по умолчанию 1234).
- Запустите сервер.
Теперь у вас есть локальный API, который по адресу http://localhost:1234/v1/embeddings принимает тексты и возвращает векторы.
Дополнительно: LLM-модель нам понадобится позже для генерации человекопонятных названий тем. Держите наготове любую русскоязычную инструктивную модель (например, IlyaGusev/saiga_nemo_12b в формате GGUF), она будет работать параллельно на том же сервере или на другом порту.
Запуск Anaconda и установка библиотек
Откройте Anaconda Prompt, создайте окружение и установите зависимости:
conda create -n bert_seo python=3.10 conda activate bert_seo pip install bertopic pandas plotly openai hdbscan umap-learn scikit-learn
(openai нужен для обращения к LM Studio, plotly — для иерархических графиков).
Запустите Jupyter:
jupyter notebook
Создайте новый ноутбук и начните работу.
Загрузка и кластеризация запросов с BERTopic #
Импорт и подключение к LM Studio #
import pandas as pd
from bertopic import BERTopic
from openai import OpenAI
# Подключение к LM Studio как к embedding-провайдеру
client = OpenAI(
base_url="http://localhost:1234/v1", # адрес вашего сервера
api_key="not-needed" # LM Studio не требует ключа
)
# Загрузка запросов
df = pd.read_csv("keywords.csv")
# Приводим к списку, можно дополнительно повторять запросы пропорционально частоте
# если хотите учесть важность, либо использовать поле frequency позже
queries = df["keyword"].astype(str).tolist()
# Создание кастомной функции для эмбеддингов через LM Studio
def lm_studio_embed(texts):
"""Принимает список текстов, возвращает список векторов"""
response = client.embeddings.create(
model="multilingual-e5-large", # точное имя загруженной модели в LM Studio
input=texts
)
return [item.embedding for item in response.data]
Запуск BERTopic с кастомным эмбеддером
from bertopic.backend import BaseEmbedder
class LMStudioEmbedder(BaseEmbedder):
def __init__(self, embedding_func):
super().__init__()
self.embedding_func = embedding_func
def embed(self, documents, verbose=False):
return self.embedding_func(documents)
embedding_model = LMStudioEmbedder(lm_studio_embed)
topic_model = BERTopic(
embedding_model=embedding_model,
hdbscan_model=None, # можно настроить min_cluster_size ниже
min_cluster_size=10, # минимальный размер кластера (тем)
min_samples=5,
nr_topics="auto", # или число, если хотите управлять вручную
calculate_probabilities=True,
verbose=True
)
# Запуск модели
topics, probs = topic_model.fit_transform(queries)
На выходе:
- topics — список номеров тем для каждого запроса (-1 — шум, не попавшие в кластеры).
- topic_model.get_topic_info() — датафрейм с обзором тем.
Иерархическая структура для навигации сайта #
Это ключевой момент. BERTopic умеет строить дендрограмму тем и редуцировать количество кластеров, объединяя их по смыслу.
# Уменьшим количество тем, объединив близкие (опционально) topic_model.reduce_topics(queries, nr_topics=20) # задаём желаемое число основных разделов # Построим иерархическое дерево hierarchical_topics = topic_model.hierarchical_topics(queries) # Визуализация fig = topic_model.visualize_hierarchy(hierarchical_topics=hierarchical_topics) fig.show()
Вы увидите интерактивный дендрит: толстые ветки — крупные тематические категории, тонкие подветки — более узкие подтемы. Именно это станет основой структуры:
- Главная ветка → раздел каталога / услуга.
- Дочерние ветки → подразделы, фильтры, страницы блога.
Нейминг разделов с помощью LLM (через LM Studio) #
Технические названия тем типа 0_keyword1_keyword2 плохо подходят для меню. Отправим каждую тему в LLM, чтобы получить красивое коммерческое название.
Предварительно запустите в LM Studio выбранную текстовую модель на другом порту (например, 1235) или на том же, но с переключением. Код для генерации названий:
def get_topic_label(topic_num, top_n_words=10, top_n_docs=5):
# Получаем ключевые слова и примеры запросов
words = [word for word, _ in topic_model.get_topic(topic_num)[:top_n_words]]
docs = [queries[i] for i in range(len(queries)) if topics[i] == topic_num][:top_n_docs]
prompt = (
f"Ты SEO-специалист. Придумай короткое, ёмкое название раздела сайта (до 5 слов) "
f"на основе следующих ключевых слов и запросов.\n"
f"Ключевые слова: {', '.join(words)}\n"
f"Примеры запросов: {'; '.join(docs)}\n"
f"Название раздела:"
)
# Отправляем в LM Studio
response = client.chat.completions.create(
model="saiga-nemo-12b", # ваша модель
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
max_tokens=20
)
return response.choices[0].message.content.strip()
# Применяем ко всем темам, кроме -1 (шум)
topic_labels = {}
for topic in topic_model.get_topic_info().Topic:
if topic != -1:
topic_labels[topic] = get_topic_label(topic)
# Смотрим результат
print(topic_labels)
Теперь у нас есть словарь {0: “Ремонт квартир под ключ”, 1: “Натяжные потолки цена”, …}.
Сведение результатов в итоговую структуру #
Создадим датафрейм, где каждый запрос отнесён к разделу и подразделу.
result = df.copy()
result["Topic"] = topics
result["Section"] = result["Topic"].map(topic_labels).fillna("Прочее")
# Добавим подраздел из родительского узла (можно достать из иерархии)
# Упрощённо: если есть иерархия, можно назначить родительскую тему через hierarchical_topics
# Вручную вы потом всё равно доработаете, здесь просто экспорт
result[["keyword", "frequency", "Section"]].to_csv("structured_keywords.csv", index=False)
Валидация и дополнение через Screaming Frog #
Теперь используем краулер для привязки новой структуры к реальному сайту и анализа конкурентов.
Аудит текущего сайта:
- Запустите краулер на своём сайте. Экспортируйте все URL с Title и H1.
- Сравните, все ли ваши новые разделы (Section) имеют соответствующие посадочные страницы. Это можно сделать в Excel через ВПР или визуально.
Лайфхак: в Screaming Frog перейдите в меню Content → Search, введите название раздела — сразу увидите, есть ли страницы с таким заголовком.
Поиск контентных пробелов: если раздел есть в CSV, но отсутствует на сайте — крепите его в план разработки.
Анализ конкурентной структуры (опционально). Проведите парсинг топ-10 конкурентов по главным запросам. Соберите их H1/H2. Загрузите эти заголовки как отдельный список и примените BERTopic. Сравните 2 дерева тем (ваше ядро vs реальная выдача). Так вы найдёте подтемы, которые покрывают конкуренты, а вы — нет.
Финальная карта сайта #
На основе иерархического графика и таблицы с лейблами вы рисуете структуру:
- Раздел 0 “Ремонт квартир”
- Подраздел 0.1 “Черновая отделка” (тема №3)
- Подраздел 0.2 “Чистовая отделка” (тема №7)
- Раздел 1 “Натяжные потолки”
- Подраздел 1.1 “Матовые потолки” (тема №12)
…
Каждая такая ветка получает своё ЧПУ, а входящие в неё запросы становятся контентным ТЗ для страницы.
Краткий чек-лист вашего пайплайна #
- Screaming Frog → экспорт запросов из GSC или сбор конкурентов.
- Anaconda + Jupyter → загрузка CSV, кластеризация через BERTopic.
- LM Studio (embedding) → векторизация запросов.
- BERTopic → автоматическая группировка и иерархия.
- LM Studio (LLM) → генерация коммерческих названий разделов.
- Screaming Frog (валидация) → проверка наличия страниц на сайте + анализ конкурентов.
- Excel / MindMap → чистовая структура.
В итоге вы получаете не просто группировку «ключ–страница», а семантически обоснованную архитектуру, которая соответствует реальным кластерам пользовательского спроса и готова к реализации.
