Поиск упущенного спроса
Анализ существующей рекламной кампании Яндекс Директ для нахождения ключевых фраз, которые не покрыты текущей семантикой.
Требования
- XLSX-выгрузка из Яндекс Директ (лист должен называться «Тексты» — стандартный формат экспорта)
- Формат файла:
.xlsx(не.xls) - Доступ к Wordstat API через ключ сервисного аккаунта по инструкции настройки
Ссылки на редактирование групп
В начале работы спроси у пользователя логин Яндекс Директ (ulogin). Это нужно для формирования прямых ссылок на редактирование групп.
ID кампании = имя файла без расширения (например, 54939351.xlsx → 54939351).
Формат ссылки:
https://direct.yandex.ru/dna/groups-edit?ulogin={login}&campaigns-ids={campaign_id}&groups-ids={group_id}В отчёте по каждой группе добавляй ссылку, чтобы пользователь мог сразу перейти и добавить найденные фразы.
Режим работы: последовательный vs параллельный
Анализ всех групп кампании занимает время (1 API-запрос ~ 1-3 сек, 2 запроса на группу).
Если Tasks (субагенты) доступны (например, Claude Code с Task tool):
- Можно запустить анализ всех групп параллельно, по 10-15 групп на субагента
- Сначала покажи пользователю все группы, дай выбрать scope
- Запускай 3-5 субагентов параллельно, каждый обрабатывает свой батч
- В конце — объединённый отчёт
Если Tasks НЕ доступны (Claude Web, API без субагентов):
- Работай последовательно, по 1-2 группы за раз
- После каждой группы обсуждай результаты с пользователем
- Спрашивай: «Продолжить со следующей группой или остановимся?»
- Это экономит контекст и даёт пользователю контроль
Типы запросов и стратегии расширения
Перед расширением определи тип группы. Разные типы запросов расширяются по-разному.
Транзакционные запросы
Группы с коммерческим интентом (купить, заказать, цена). Расширение:
- Действия: купить -> заказать, цена, стоимость, где купить, сколько стоит
- Объекты: синонимы, разговорные формы (проигрыватель -> вертушка, музыкальный центр -> муз центр)
- Модификаторы: стилевые (ретро -> винтажный, в стиле ретро, под ретро, старинный)
- Дополнительные: география (в москве -> в интернет магазине, недорого, с доставкой)
Брендированные запросы
Группы по названию бренда (Crosley, Victrola, Muse). Расширение ограничено:
- Транслит: crosley -> кросли, victrola -> виктрола
- Пробелы/дефисы: playbox -> play box, roadstar -> road star
- Расшифровка аббревиатур: если бренд — аббревиатура
- НЕ расширяй брендовую компоненту на другие бренды или generic-слова
- Добавлять действия/модификаторы можно, но бренд — only transliteration
Навигационные запросы
Группы с географическим интентом (адрес, как проехать, где находится). Расширение:
- Метро/улицы: автосалон белорусская, магазин беломорская
- Районы: автосалон юзао, магазин центр москвы
- Проверь флаг: навигационные запросы релевантны только для offline-точек. Для чисто онлайн-магазинов — не ищи адреса
- Спроси пользователя: «У вас есть офлайн-точка/шоурум? Стоит ли искать навигационные запросы (метро, улицы)?»
Информационные запросы
Группы с информационным интентом (как выбрать, какой лучше, обзор, отзывы). Расширение:
- Перефразировка задачи: как выбрать проигрыватель -> какой проигрыватель лучше, рейтинг проигрывателей
- Смежные задачи: обзор проигрывателей -> сравнение проигрывателей, топ проигрывателей
- НЕ добавляй транзакционные слова к информационным группам (и наоборот)
Workflow
Шаг 1: Проверка подключения
bash scripts/quota.shШаг 2: Парсинг XLSX
uv run --script scripts/missed_demand.py parse-xlsx /path/to/export.xlsxВыход — JSON со всеми группами. Покажи пользователю таблицу:
| ID группы | Название | Фраз |
|---|---|---|
| 4302852986 | Ретро-телефоны | 52 |
| ... | ... | ... |
Спроси пользователя, какую группу (или все) анализировать.
Шаг 3: Получение фраз группы
uv run --script scripts/missed_demand.py parse-xlsx /path/to/export.xlsx --group <group_id>Шаг 4: LLM-сегментация
Разбей фразы группы на слоты. Используй plain слова без кавычек и операторов. Операторы (+ для стоп-слов) добавит build-query автоматически.
Не используй знаки препинания в слотах. Пиши муз центр, а не муз. центр. Скрипт автоматически удаляет точки, запятые и прочую пунктуацию, но лучше не давать её изначально.
Слоты:
- objects — объект рекламирования (телефон, проигрыватель, приёмник). Это то, ЧТО продают. Не используй абстрактные слова вроде «аппарат» — проверяй, ищут ли так реально.
- actions — действия (купить, заказать, продажа, ремонт)
- modifiers — свойства/определения (ретро, винтажный, старинный, дисковый, телефонный, настенный). Сюда идут прилагательные и характеристики объекта.
- additional — дополнительные (в москве, недорого, срочно)
Пример для группы «Ретро-телефоны»:
{
"objects": ["телефон"],
"actions": ["купить", "заказать"],
"modifiers": ["ретро", "винтажный", "старинный", "телефонный"],
"additional": ["в москве"]
}Правила:
objectsобязателен (если не ясен — фолбэк на частотное ядро фраз)- Остальные слоты опциональны
- Дефис внутри слова допустим (санкт-петербург, б/у)
- НЕ ставь
+,-,!,",|,(,)— build-query сделает сам - Синонимы объекта должны быть самостоятельными поисковыми словами. «Аппарат» без контекста — не ищут. «Телефонный аппарат» — ищут, но «телефонный» лучше в modifiers.
- Определи тип группы (транзакционная/брендированная/навигационная/информационная) и применяй соответствующую стратегию расширения
- Убирай избыточные варианты в OR-группе. Если есть однословный вариант «ретро», то многословные «под ретро», «в стиле ретро» избыточны — они буквально содержат слово «ретро» и уже покрыты им. Общее правило: многословный вариант, содержащий слово из однословного варианта в той же OR-группе, всегда является его подмножеством и не добавляет спроса. Но семантические синонимы с другими словами — всегда добавляют: «состаренный», «под старину», «старинный» — это другие леммы, не покрытые словом «ретро», и их надо включать
Шаг 4.1: Батчинг для больших групп (20+ фраз)
Если в группе больше 15-20 фраз, сегментируй батчами, чтобы не потерять редкие модификаторы и дополнительные конструкции (например, «до 5000», «из кожи»):
- Раздели фразы на батчи по 10-15 штук
- Для каждого батча выполни сегментацию (Шаг 4) и получи JSON слотов
- Объедини слоты из всех батчей:
- Добавь все уникальные варианты (дедупликация по нормализованной форме)
- Убери подмножества (как в правилах Шага 4)
- Проверь покрытие: все ли исходные фразы представлены хотя бы одним токеном в слотах?
Для автоматизации объединения используй merge-slots:
echo '<input_json>' | uv run --script scripts/missed_demand.py merge-slotsФормат входа (stdin JSON):
{
"phrases": ["купить телефон ретро", "заказать трубку винтажную", "..."],
"batches": [
{
"phrase_indexes": [0, 1],
"slots": {"objects": ["телефон"], "actions": ["купить", "заказать"], "modifiers": ["ретро"], "additional": []}
},
{
"phrase_indexes": [2],
"slots": {"objects": ["телефон", "трубка"], "actions": [], "modifiers": ["винтажный"], "additional": []}
}
]
}Выход включает:
slots_pre_trim/slots_post_trim— слоты до и после обрезки по лимитамquery— собранный OR-запросcoverage— отчёт:uncovered_phrases,uncovered_tokens,additional_patternsdebug— какие батчи породили каждый вариант
Если в coverage.uncovered_phrases есть фразы — сегментация возможно их потеряла (coverage эвристический, без лемматизации возможны ложные срабатывания). Проверь и добавь пропущенные токены в слоты при необходимости.
Шаг 4.2: Валидация сегментации с пользователем
ОБЯЗАТЕЛЬНО покажи результат сегментации пользователю перед запросом Wordstat:
Группа «Ретро-телефоны» (транзакционная):
objects: телефон
actions: купить, заказать
modifiers: ретро, винтажный, старинный, телефонный
additional: в москве
Всё верно? Что добавить/убрать?Это экономит API-квоту и предотвращает ошибки сегментации (например, «аппарат» вместо «телефонный»).
Шаг 5: Сборка текущего OR-запроса
uv run --script scripts/missed_demand.py build-query '<slots_json>'Шаг 6: Запрос текущего спроса (X)
Сформируй full_phrase:
- Возьми
queryиз build-query - Добавь
group_minusиз parse-xlsx: объедини непустые части одним пробелом - Посчитай
len(full_phrase)в символах Юникода, включая разделительные пробелы. Облачный Wordstat принимает не более 400 символов во всём полеphrase - Добавляй
campaign_minus, только если длина новой строки, также собранной через один пробел, остаётся не больше 400; иначе пропусти их и явно укажи это в отчёте
Если query + group_minus уже длиннее 400 символов, сократи OR-варианты либо выполни несколько независимых запросов. Не суммируй результаты пересекающихся запросов как точное значение: одни и те же поисковые фразы могут попасть в несколько ответов.
bash scripts/query_total.sh --phrase "<full_phrase>" --regions "<region_id>"query_total.sh проверяет окончательную строку до сетевого запроса. Если она не помещается, сначала убери необязательные campaign_minus, затем сокращай OR-схему.
Шаг 7: LLM-расширение
ВАЖНО: Сохраняй структуру запроса. Если в оригинале нет actions (действий) — не добавляй их как обязательный слот. Добавление обязательного (купить|заказать) к запросу без actions сужает запрос, а не расширяет.
Правило: расширяй только те слоты, которые уже есть в оригинальном запросе. Новые слоты (например, actions для запроса без действий) предлагай как отдельные дополнительные запросы, не смешивая с основной OR-схемой.
Пример:
- Оригинал:
(муз центр|муз центры) ретро— нет actions, нет additional - Расширение основного:
(муз центр|музыкальный центр|музцентр|аудиоцентр) (ретро|винтажный|+в стиле ретро)— расширяем objects + modifiers - Дополнительный запрос (опционально):
(купить|заказать) (муз центр|музыкальный центр) ретро— отдельно оценить транзакционный спрос
Предложи новые варианты для каждого слота в зависимости от типа группы (см. раздел «Типы запросов»):
- Синонимы (телефон -> трубка, телефонный аппарат)
- Написание по-разному (volkswagen -> фольксваген, фольцваген)
- Опечатки (тигуан -> тигуанн)
- Жаргон и разговорные формы (проигрыватель -> вертушка)
- Сокращения без пунктуации (муз центр, а не муз. центр)
- Новые действия (купить -> заказать, цена, стоимость) — только если actions уже есть
- Новые модификаторы (ретро -> антикварный, раритетный)
Post-фильтр: проверь предложенные термины на совпадение с campaign_minus. Совпадающие — исключи (они заминусованы не просто так).
Шаг 8: Запрос расширенного спроса (Y)
Повтори шаги 5-6 с расширенными слотами -> получи Y.
Шаг 8.1: Проверка мусора при большой дельте
Если дельта > 200% И Y > 100, запроси развёрнутый ответ Wordstat (topRequests) по расширенному OR-запросу. Посмотри фразы в выдаче:
- Есть ли нерелевантный мусор? (например, добавили «вертушка» и получили «вертушка рыболовная»)
- Если мусор найден — предложи изолировать с помощью
!(принудительная словоформа) или дополнительных минус-слов - Покажи пользователю топ-10 фраз из выдачи с комментарием
Пример:
Дельта большая (+478%). Проверяю, что стоит за расширенным запросом...
Топ-фразы в Wordstat:
1. вертушка для пластинок купить — OK
2. вертушка рыболовная — МУСОР (нужен минус: -рыболовная)
3. граммофон ретро купить — OK
...
Рекомендация: добавить минус-слова: -рыболовная -рыбалкаШаг 9: Отчёт
Для каждой группы выведи:
Группа «Ретро-телефоны» (транзакционная):
Редактировать: https://direct.yandex.ru/dna/groups-edit?ulogin={login}&campaigns-ids={campaign_id}&groups-ids={group_id}
OR-схема ДО:
(купить|цена) (телефон|телефоны|трубка) (ретро|старинный|винтаж) +в москве
OR-схема ПОСЛЕ:
(купить|цена|заказать|стоимость) (телефон|телефоны|трубка|телефонный аппарат) (ретро|старинный|винтаж|винтажный|антикварный) +в москве
Новые термины:
actions: +заказать, +стоимость
objects: +телефонный аппарат
modifiers: +винтажный, +антикварный
Спрос: 651 -> 679 (+4.3%)
[!] Без campaign_minus — реальный спрос может быть ниже
[!] Дельта < 10% — расширение минимальное, можно пропуститьГде {login} — логин Яндекс Директ (спросить в начале сессии), {campaign_id} — имя файла без .xlsx, {group_id} — ID группы из parse-xlsx.
Спроси пользователя:
- Принять расширение?
- Доработать (убрать/добавить термины)?
- Перейти к следующей группе?
Шаг 10: Следующая группа
Повтори с шага 3 для другой группы. В последовательном режиме — спроси перед каждой группой. В параллельном режиме — обработай все группы и покажи сводный отчёт.
Стоп-слова
Предлоги на, в, к, за, с, по, из, от, до, для, без, при, под, над, между, через, об, перед являются стоп-словами в Wordstat — они игнорируются без оператора +.
Скрипт build-query автоматически добавляет + к стоп-словам в слотах. Ставить + вручную не нужно.
Ограничения
- Лист «Тексты»: скрипт работает только с XLSX-экспортом, где лист называется «Тексты». При другом названии — ошибка с перечислением доступных листов.
- Длина phrase: облачный Wordstat принимает не более 400 символов Юникода во всей строке, включая OR-схему,
group_minusиcampaign_minus. Если кампанийные минус-фразы не помещаются, метрика X/Y учитывает толькоgroup_minus, что нужно отметить в отчёте. - Длина query:
query_lengthизbuild-queryотносится только к базовой OR-схеме и не учитывает минус-фразы. Окончательный источник истины — длинаphraseнепосредственно перед запросом. - OR-операторы: если Wordstat API неожиданно не поддерживает
(a|b)синтаксис — fallback: запросить каждый вариант отдельно и суммировать (upper bound — пересечения дадут оверкаунт). - Квота API: при анализе множества групп следить за расходом и учитывать актуальные квоты Wordstat.
- Пунктуация: скрипт автоматически удаляет точки, запятые и прочие знаки из слотов (
муз. центр->муз центр). Это безопасно — Wordstat всё равно игнорирует пунктуацию.