Ни одного из этих слов в моих планах не было. Я просто задолбался вручную таскать ключи из Wordstat в Excel.
Версия 1: лишь бы не копировать руками
Знакомая ситуация: открываешь Wordstat, вводишь маску, ждёшь, копируешь, вставляешь в Excel. Следующая маска. И так по кругу. Каждый раз одно и то же.
Написал скрипт. Никакой архитектуры просто цикл, запросы к Bukvarix (у них есть бесплатный API), файл на выходе. Работало. На этом стоило остановиться.
Не остановился.
Через месяц понял: данные в Bukvarix отстают на несколько месяцев. Если собираешь семантику под Директ - это проблема. Бюджет уходит на ключи с устаревшей частотностью, а ты потом сидишь и думаешь, почему CTR такой грустный.
Версия 2: свежие данные и первые метрики
Добавил второй источник - XMLRiver. Платный пулинг прокси к Яндекс XML: те же данные что Wordstat, только через API, без капч, в реальном времени.
Важный момент: Bukvarix и XMLRiver не цепочка, а два независимых режима. Bukvarix быстро и бесплатно даёт широкий список с синонимами, но частотности протухшие. XMLRiver нужен, когда важна свежесть например перед запуском кампании.
В XMLRiver-режиме прикрутил три типа частотности:
Параллельно на 10 потоков, retry-логика на случай когда XMLRiver присылает{"error": "Выполните перезапрос"}- значит их пул не справился с капчей Яндекса, надо повторить.
Заодно сразу добавил конкурентность ключа. Покупать данные Ahrefs ради десктопного инструмента дороговато, поэтому обошёлся эвристикой:
Логика простая: длинный хвост с низкой частотой - конкурентов мало, короткий высокочастотный - конкуренция высокая. Не Ahrefs, понятно, но как первичный фильтр при разборе тысяч ключей вполне рабочий.
На этом инструмент уже был полезным. Можно было остановиться.
Не остановился. Опять.
Версия 3: кластеризация и скрытая проблема пайплайна
Когда у тебя 3000 ключей по нише мало их просто собрать. Надо понять структуру: какие запросы конкурируют за одну страницу, а какие требуют отдельных посадочных.
Прикрутил SentenceTransformers с модельюparaphrase-multilingual-MiniLM-L12-v2— 120 МБ, работает на CPU, русский понимает нормально.
И тут же словил ловушку чистой NLP-кластеризации: семантически похожие запросы не всегда конкурируют в выдаче. «Ремонт квартир Москва» и «Ремонт квартир Воронеж» смысл одинаковый, но это две разные страницы. NLP радостно их объединит, а потом удивляйся почему структура кривая.
Решение - SERP Veto. Перед кластеризацией собираем ТОП-10 Яндекса для каждого ключа и смотрим пересечение URL. Менее 2 общих URL - разные кластеры, даже если NLP говорит «одно и то же»:
Три режима кластеризации:
- NLP Only- чистая семантическая близость через эмбеддинги. Быстро, SERP-запросов не нужно.
- SERP Only- только по пересечению URL в выдаче. Медленно, зато точно.
- Hybrid- NLP формирует кандидатов, SERP Veto отсекает ложные объединения. Лучший баланс.
Гео-изоляция работает во всех режимах: если у одного ключа «Москва», а у другого «Воронеж» они не попадут в один кластер даже при полном совпадении NLP и SERP. Отдельная проверка до любого сравнения.
И вот тут вылезла проблема, которую я сразу не заметил.
Первая версия пайплайна работала в лоб: получил список → сразу за SERP → потом кластеризация. На 3000 ключей это 3000 SERP-запросов до того как убрали хоть один дубль.
А Bukvarix и XMLRiver щедро возвращают морфологические дубли:
NLP-кластеризатор честно пытался разобраться, что это одно и то же. SERP тратил запросы на дубли. Медленно и бессмысленно.
Переделал порядок, каждый этап теперь уменьшает список перед следующей дорогой операцией:
Почему порог 82? Подбирал руками: при 90 «ремонт квартиры» и «ремонт однушки» ложно схлопываются, при 75 «ремонт квартиры цена» и «ремонт квартиры стоимость» остаются как разные ключи. 82 на реальных данных убирает 30-40% списка до SERP.
Fuzzy Dedup сравнивает не все N² пар, а только внутри блоков с одинаковым первым словом после лемматизации секунды вместо минут на 3000 ключей.
К этому моменту инструмент умел собирать, частотить, дедуплицировать и группировать. На выходе красивая структурированная таблица. В которой по-прежнему сидело 15-30% мусора.
Версия 4: AI-фильтрация, или почему один промпт - это несерьёзно
Чистить 3000 ключей вручную часа три-четыре. Каждый раз. Минус-слова помогают, но не закрывают пограничные случаи: агрегаторы, запросы от людей ищущих работу, информационный интент, который маскируется под коммерческий.
Ну, думаю, тут-то LLM и поможет. Накидал промпт: «оставь коммерческие, удали мусор». Казалось, что хватит.
Нет. Не хватит.
DeepSeek не знает контекст. «Ремонт» - это квартир, телефонов или двигателей? «Бригада» - строительная или из сериала? Без контекста модель опирается на общие знания, и результат - лотерея.
PlannerAgent: сначала объясни, про что ниша
Добавил агента, который перед классификацией получает описание ниши и генерирует план: нишеспецифичные few-shot примеры, список ловушек, гео-фильтр:
Стало лучше. Но стабильности по-прежнему не хватало.
ID-нумерация: экономия токенов
Прежде чем решать проблему качества, разобрался со стоимостью. При наивном подходе промпт с 20 ключами - около 600 токенов. А модели не нужны сами строки в ответе, только решение по каждой. Решение: нумеруем ключи, просим вернуть только ID:
Ответ сократился с ~400 до ~80 токенов на батч. На 3000 ключей - экономия 30-40% от общего расхода.
Замер нестабильности
Прогнал один и тот же датасет из 671 ключа три раза подряд. Вот что получилось:
38% стабильности. На трети датасета хуже монетки.
Причина: PlannerAgent каждый раз генерировал чуть разные few-shot примеры, которые тянули за собой разные решения на пограничных кейсах. Даже при temperature=0 DeepSeek не гарантирует идентичный вывод на длинных генерациях.
Ensemble Voting
Раз один прогон нестабилен прогоняем три раза и берём большинство голосов. Каждый батч из 20 ключей получает три независимых решения параллельно:
Ничья (1-1-1) не уходит в мусор и не классифицируется автоматически. Она идёт в отдельный лист «Проверить». Позже к этому листу я приделал арбитражного агента: он получает аргументы всех трёх голосов и выносит финальный вердикт. Снимает примерно 90% ручной работы с этого листа.
Расход токенов, понятно, вырос в три раза. На DeepSeek это не страшно - 3000 ключей с votes=3 стоят примерно $0.3.
Подвох, который я не ожидал
После всей этой работы - параллельность, ансамбль, арбитр я обнаружил забавное. «Ремонт квартир под ключ» с частотностью 45 661 стабильно улетал в ПРОВЕРИТЬ. Флагманский коммерческий запрос. Самый очевидный.
Разобрался: в промпте были правила «оставляй если содержит цену, стоимость, заказ, гео». А «под ключ» не содержит ничего из этого. Три агента честно не могли договориться потому что все трое работали по одному промпту с логической дырой.
Фикс три строки в промпте. Три строки. А я перед этим неделю строил архитектуру.
Урок: валидируй промпт на 50 крайних случаев прежде чем городить ансамбль поверх него.
Что ещё добавил по ходу
Paranoid Mode.Реальный кейс: у заказчика бренд с названием, похожим на обычное слово. AI выбрасывал все брендовые запросы как информационные. Решение - Whitelist: слова, которые AI не трогает вообще. Ключи с ними идут в ПОДХОДЯЩИЕ, минуя DeepSeek. Проверка по токенам, не по substring - иначе слово «ключ» в whitelist защитило бы «ключи от квартиры». Побочный эффект: брендовые ключи не тратят токены.
SERP модуль.Отдельная вкладка для быстрой проверки конкурентной среды. XMLRiver отдаёт Яндекс XML - парсим органику (кто в топе, доминируют ли агрегаторы), related queries (бесплатные LSI от Яндекса), рекламные блоки (есть реклама - есть деньги в нише), и нейроответы Яндекса (если Яндекс уже отвечает своим AI - органический трафик на запрос будет падать). Всё параллельно на 10 потоках.
AI-ассистент.Чат поверх загруженного датафрейма. Вместо того чтобы каждый раз открывать Excel - пишешь «покажи коммерческие с частотой выше 500», ассистент генерируетpandas.query()и предлагает применить кнопкой.
Начинал с «хочу не копировать из Wordstat». Получилось вот это:
Стек:Python 3.11, DeepSeek API, XMLRiver, SentenceTransformers, rapidfuzz, pymorphy2, pandas, customtkinter.
Стоимость прогона3000 ключей с votes=3 - $0.3. Время 20-30 минут вместо 3-4 часов руками.
Что намеренно не стал делать:RAG на разовых нишах (каждый заказ новая ниша, накопленное не переиспользуется), Keys.so API (9300₽/мес не отбивается при текущих объёмах), FAISS на 100k ключей (нет такого кейса в реальности).
Если интересно
Инструмент пока для личного использования, публичного релиза нет.
Если хочешь обсудить архитектуру, поспорить про ensemble voting или рассказать как решал похожую задачу - вэлкам