Как настроить автоматический контроль рекламы в Яндекс Директе — инструкция
Всего одна настройка — и у вас есть скрипт, который каждый час проверяет расход, CTR и CPC в Директе и сообщает, если что-то пошло не так. Бесплатно и без сторонних сервисов. Как это собрать, рассказывает основатель и генеральный директор AiHummer Леон Сокиркин.
Автоматический контроль рекламы — это когда кампаниями управляют программы, а не человек вручную. Они сами следят за ставками, бюджетом и аудиторией, смотрят на данные и решают, что поменять, чтобы реклама работала лучше и средств уходило меньше.
В этой инструкции разберем разновидность такого контроля, где программа только находит отклонение и сообщает о нем, а решение — остановить кампанию, снизить бюджет или оставить как есть — принимает специалист:
Кто сможет настроить автоматический контроль рекламы в Директе
Чтобы не тратить время зря, сначала убедитесь, что задача вам подходит. В таблице ниже — краткая выжимка по требованиям к навыкам, оборудованию и итоговому результату работы.
|
Кому подойдет инструкция |
Специалистам по контекстной рекламе и маркетологам, которые самостоятельно ведут кампании в Яндекс Директе |
|
Необходимые навыки |
Понимание показателей расхода, CTR, CPC и CPA |
|
Умение редактировать CSV-файлы |
|
|
Базовое выполнение команд в терминале |
|
|
Если вы новичок в терминале |
Прежде чем приступать, освойте три базовых действия: установку пакета, запуск .py-файла и чтение сообщения об ошибке. Разрабатывать интеграцию с нуля не потребуется — весь код в статье приведен полностью |
|
Что вы получите на выходе |
Локальный инструмент из двух Python-скриптов: первый собирает статистику, второй сравнивает ее с заданными вами ориентирами |
|
Результат работы скриптов |
report.csv — результаты текущей проверки |
|
signals.csv — история отклонений (сигналов) |
|
|
Папка snapshots — архив исходных отчетов (решения специалист вносит вручную в отдельный файл decisions.csv. Изменения в рекламу также вносятся вручную через кабинет) |
|
|
Требования к оборудованию |
Компьютер на Linux (Debian 12+ или Ubuntu 24.04+). Рабочий пример рассчитан на аккаунт прямого рекламодателя |
|
Требования к сети |
Для первого теста подойдет обычный рабочий компьютер. Для регулярной автоматической проверки он должен оставаться включенным и иметь постоянный доступ в интернет |
|
Ограничения |
Windows и macOS требуют другого способа установки окружения и настройки расписания задач — в этой статье они не рассматриваются |
Базовый вариант скрипта проверяет три внутридневных показателя:
темп расхода;
падение CTR;
рост CPC.
Вся логика построена на обычных математических формулах в Python — для работы не нужны ни языковая модель, ни платные сервисы автоматизации. Возможность генерации ИИ-пояснений к сигналам в этой версии не реализована.
Проверки CPA и визитов описаны отдельно как возможные расширения: для них сначала нужно подготовить дополнительные данные и учесть задержку конверсий.
Что нужно подготовить до старта
Что понадобится для настройки автоконтроля рекламы в Директе
Ниже — полный перечень с указанием конкретных действий и инструментов, где выполняется каждый шаг.
|
Инструмент |
Что делаем |
Где находится настройка |
|
Яндекс Директ |
Даем доступ к статистике и смотрим итоговые изменения |
Браузер: «Инструменты → Ваши представители» |
|
«Настройки API» |
||
|
Яндекс OAuth |
Регистрируем приложение и получаем токен |
Браузер: oauth.yandex.ru; вход представителя «Только чтение» |
|
Python 3.11+ и requests |
Собираем отчет и выполняем правила |
Локальная папка ~/ad-control; файлы fetch_direct.py и check.py |
|
Текстовый редактор |
Задаем доступ и ориентиры кампаний |
В той же папке: direct.env и targets.csv |
|
LibreOffice Calc или редактор CSV |
Просматриваем сигналы и ведем решения |
report.csv, signals.csv и decisions.csv |
|
Планировщик cron |
Запускаем проверку раз в час |
Терминал: crontab -e |
|
Единый файл run.sh |
Примечание: пороговые значения хранятся в check.py (словарь LIMITS, параметры MIN_IMPRESSIONS, MIN_CLICKS, MIN_HOURS), а планы конкретных кампаний — в targets.csv.
Что подготовить до запуска автоконтроля в Директе
Прежде чем настраивать скрипты, убедитесь, что у вас есть все необходимое.
|
Требование |
Комментарий |
|
Кабинет Директа |
Действующий аккаунт с хотя бы одной активной кампанией |
|
Права главного представителя |
Необходимы для того, чтобы иметь возможность выдавать доступ другим пользователям |
|
Доступ к API Метрики |
Не требуется. В базовом варианте расход, показы и клики берутся напрямую из Директа |
Перед началом настройки:
-
Выберите кампании для наблюдения и выпишите их числовые ID.
-
Зафиксируйте суточный план, рабочие часы и ориентиры CTR/CPC.
-
Под главным представителем откройте «Инструменты → Ваши представители».
-
Добавьте отдельный Яндекс-логин, который еще не использовался для входа в Директ, и назначьте «Только чтение».
-
Авторизуйте будущий скрипт именно от этого пользователя, а не от владельца кабинета. Права API наследуют ограничения роли.
-
На oauth.yandex.ru зарегистрируйте приложение «Для доступа к API или отладки».
-
Добавьте разрешения direct:api и passport:business, сохраните ClientID. В настройках платформы задайте Redirect URI: https://oauth.yandex.ru/verification_code.
-
В Директе под логином разработчика откройте «Настройки API → Мои заявки → Новая заявка».
-
Укажите ClientID и назначение: внутренний скрипт получения отчетов. Дождитесь одобрения заявки. Яндекс указывает срок до семи дней.
Для внутреннего скрипта можно получить токен вручную:
-
В отдельном профиле браузера войдите под представителем «Только чтение», откройте адрес ниже, заменив CLIENT_ID своим ClientID, разрешите доступ.
-
Сохраните access_token со страницы результата в direct.env, как показано дальше:
-
ClientID и OAuth-токен — разные значения;
-
Client secret в этот скрипт не вставляется.
https://oauth.yandex.ru/authorize?response_type=token&client_id=CLIENT_ID
-
Не отправляйте токен в чат и не включайте его в статью, скриншоты или общий репозиторий. Название разрешения direct:api само по себе не означает «только чтение»: защиту дает роль пользователя в Директе. Если вы не можете выдать такую роль или получить одобрение API, остановитесь на подготовке — заменять ее токеном владельца кабинета нельзя.
Что именно будем автоматизировать
Процесс состоит из следующих шагов:
-
Сборщик получает статистику рекламных кампаний через отчетный API.
-
Проверяющий модуль сопоставляет показатели с планом и порогами.
-
При отклонении в signals.csv появляется запись с исходными значениями и порогом. Краткий результат выводится в терминал.
-
Специалист проверяет сигнал и принимает решение.
-
Решение фиксируется в журнале, а изменение вручную вносится в рекламном кабинете.
Граница системы: в этой инструкции «автоматический контроль» означает автоматическое обнаружение отклонений. Автоматического управления ставками, лимитами, статусами и объявлениями нет.
Односторонний контур: сбор и анализ отделены от управления рекламными кампаниями.
8 шагов настройки автоматического контроля
Шаг 1. Подготовьте рабочую папку
Откройте терминал. Команды для установки пакетов необходимо выполнять с правами суперпользователя (sudo), а все остальные — от имени обычного пользователя. Python, requests, cron, текстовый редактор и LibreOffice — бесплатные инструменты.
Покупать отдельную платформу для этой схемы не требуется.
sudo apt update
sudo apt install python3 python3-venv python3-pip cron nano util-linux tzdata
python3 —version
mkdir -p «$HOME/ad-control»
cd «$HOME/ad-control»
python3 -m venv .venv
.venv/bin/python -m pip install requests
mkdir -p snapshots
sudo systemctl enable —now cron
Требования: версия Python должна быть 3.11 или выше. Виртуальное окружение .venv необходимо для изолирования зависимостей проекта.
Просмотр таблиц: для работы с CSV достаточно текстового редактора. Если нужен табличный процессор, установите LibreOffice Calc командой: sudo apt install libreoffice-calc.
Создание файлов: создавайте файлы в рабочей директории с помощью nano ИМЯ_ФАЙЛА. Вставляйте только содержимое кода (без заголовков блоков):
-
источники кода — fetch_direct.py, check.py, targets.csv, run.sh;
-
управление nano (сохранение — Ctrl+O затем Enter; выход — Ctrl+X).
Настройка окружения: создайте файл конфигурации direct.env. Замените ВАШ_ТОКЕН на актуальный access_token (важно: оставьте значение в одинарных кавычках). Значение AD_TIMEZONE должно совпадать с часовым поясом рекламного кабинета и временем в targets.csv (пример рассчитан на Москву).
YD_TOKEN=’ВАШ_ТОКЕН’
AD_TIMEZONE=’Europe/Moscow’
chmod 600 direct.env
Client-Login в базовом сценарии прямого рекламодателя не нужен. Агентский доступ требует отдельной проверки прав: заголовок Client-Login выбирает клиента, но не превращает токен агентства в токен «только чтение». Этот вариант не входит в первый запуск.
Автоматический контроль рекламы полезен, пока система помогает увидеть проблему, а не принимает финансовое решение вместо специалиста. Если тот же процесс умеет и анализировать статистику, и менять кампанию, ошибка в формуле, задержка конверсий или неверно выбранная цель могут остановить работающую рекламу.
Ниже — схема для Яндекс Директа, в которой сборщик использует пользователя с правом только на чтение, а проверяющий скрипт работает с готовым файлом. Приведенный код не вызывает методы изменения кампаний. Сигнал записывается локально. Отправка в почту, мессенджер или CRM в базовый пример не входит.
Шаг 2. Отделите сбор данных от управления
Используйте представителя с правами «Только чтение». Через API он также сможет исключительно получать данные. Вся архитектура построена на строгом разделении ролей:
-
Безопасный сбор данных: скрипт-сборщик обращается только к сервису Reports и сохраняет скачанные данные в виде исходного TSV или нормализованного CSV. В коде полностью отсутствуют методы, изменяющие настройки кампаний.
-
Изолированная проверка: проверяющий модуль работает исключительно с готовым файлом статистики. Он не использует сетевые библиотеки и вообще не имеет доступа к OAuth-токену.
-
Аудит: снимок исходного отчета сохраняется вместе с каждым зафиксированным сигналом.
TEXT
REPORTS_URL=https://api.direct.yandex.com/json/v501/reports
# У проверяющего процесса переменной YD_TOKEN нет
Файл direct.env доступен только владельцу благодаря chmod 600. Перед запуском проверки run.sh удаляет токен из окружения. Это защита от случайной передачи секрета, а не изоляция двух процессов одного пользователя: они всё еще могут читать файлы владельца.
Основная граница в этом примере — права «Только чтение» на стороне Директа. Для строгого разделения в промышленной системе используйте разные системные учетные записи и отдельные права на секреты.
Шаг 3. Опишите цели и ограничения кампаний
В папке ~/ad-control создайте файл targets.csv и скопируйте в него пример ниже. Замените ID кампаний и значения своими.
Требования к формату: кодировка UTF-8, разделитель — запятая, дробная часть через точку.
Подсказка: при открытии файла в LibreOffice Calc укажите запятую в качестве разделителя, а для столбцов campaign_id, work_start и work_end задайте формат «Текст». CTR задается долей: 0.035 означает 3,5%, а не 0,035%.
CSV
platform,campaign_id,daily_plan,cpa_target,ctr_baseline,cpc_baseline,work_start,work_end,owner
yandex,123456789,12000,1800,0.035,90,09:00,21:00,marketing@example.ru
yandex,987654321,6000,900,0.028,55,00:00,24:00,marketing@example.ru
Описание параметров:
-
platform — рекламная площадка (всегда оставляйте yandex);
-
daily_plan — внутренний план расходов на сутки;
-
cpa_target — целевая стоимость выбранной бизнес-конверсии;
-
ctr_baseline и cpc_baseline — базовые значения для сопоставимого типа трафика;
-
work_start и work_end — фактическое окно открута;
-
owner — сотрудник, который обязан проверить сигнал.
Важные нюансы:
-
Налоги. Все денежные поля указываются в рублях. В данном примере статистика выгружается с учетом НДС, поэтому ваши плановые показатели (daily_plan, cpc_baseline) также должны включать НДС.
-
Расписание. Для круглосуточных кампаний указывайте 00:00 и 24:00. Окна показов, переходящие через полночь (например, с 23:00 до 06:00), этот базовый вариант скрипта не поддерживает.
-
Бизнес-логика. Не применяйте один и тот же норматив ко всем кампаниям. Поиск, РСЯ и ретаргетинг кардинально отличаются по поведению. Для новых кампаний сначала накопите статистику хотя бы за 7–14 дней, и только потом используйте эти усредненные цифры как стартовые настройки, а не как универсальный стандарт.
Шаг 4. Получите статистику через Reports API
Сервис Reports принимает POST-запросы и возвращает данные в виде TSV-таблицы. В базовом варианте запрашиваем только показы, клики и расход за текущую дату аккаунта.
Пример тела запроса (параметры отчета):
JSON · ПАРАМЕТРЫ ОТЧЕТА
{
«params»: {
«SelectionCriteria»: {«DateFrom»: «2026-09-04», «DateTo»: «2026-09-04»},
«FieldNames»: [«CampaignId», «CampaignName», «Impressions», «Clicks», «Cost»],
«ReportName»: «control_20260904_150000»,
«ReportType»: «CAMPAIGN_PERFORMANCE_REPORT»,
«DateRangeType»: «CUSTOM_DATE»,
«Format»: «TSV»,
«IncludeVAT»: «YES»,
«IncludeDiscount»: «YES»
}
}
Сохраните полный код из приложения в fetch_direct.py. Этот скрипт берет на себя всю рутину: он самостоятельно подставляет текущую дату, использует единый ReportName для всех попыток и корректно повторяет запрос при получении статусов HTTP 201/202 (дожидаясь времени, указанного в параметре retryIn).
При успешной загрузке новый файл current.tsv заменяет старый атомарно. В случае ошибки в консоль выводится HTTP-код и RequestId. Заголовки столбцов сохраняются, а денежные значения автоматически возвращаются в рублях.
Для ручной проверки работы скрипта выполните в терминале следующие команды:
ТЕРМИНАЛ · ПЕРВАЯ ВЫГРУЗКА
cd «$HOME/ad-control»
set -a
. ./direct.env
set +a
.venv/bin/python fetch_direct.py
unset YD_TOKEN YD_CLIENT_LOGIN
head -n 3 current.tsv
Проверка выгрузки. Первая строка файла current.tsv должна содержать заголовки: CampaignId, CampaignName, Impressions, Clicks, Cost. Возьмите одну кампанию из файла и сверьте ее данные с интерфейсом Яндекс Директа за ту же дату. Убедитесь, что совпадают часовые пояса и настройки учета НДС:
-
если возникла ошибка доступа — проверьте логин пользователя, корректность токена и наличие доступа к API;
-
если нужной кампании нет в отчете — помните, что отсутствие кампании в выгрузке не всегда означает именно нулевой расход (статистика по ней могла просто не сформироваться или отсутствовать за этот день).
В базовом скрипте данные о визитах и конверсиях не запрашиваются. Если вы захотите расширить логику для контроля CPA, вам потребуется:
-
Задать в запросе конкретные цели и модель атрибуции.
-
Использовать сопоставимый завершенный период (а не текущий день) и повторно загружать данные с учетом задержки атрибуции конверсий.
-
Учесть, что при добавлении параметра Goals Директ динамически меняет имя столбца Conversions (например, на Conversions_<goal>_<model>). Для таких столбцов потребуется отдельная нормализация в коде.
Шаг 5. Задайте прозрачные правила аномалий
В скрипте check.py уже реализован контроль темпа расхода, CTR и CPC. Пороги срабатывания задаются в словаре LIMITS, а условия, при которых правило начинает работать, — в переменных MIN_HOURS, MIN_IMPRESSIONS и MIN_CLICKS.
Ниже приведена таблица с правилами. Обратите внимание: значения демонстрационные, их необходимо откалибровать на основе ваших исторических данных.
|
Правило |
Желтый |
Красный |
Минимум |
|
Темп расхода |
> +20% |
> +35% или превышен план |
Два часа открута |
|
Без конверсий* |
> 1 CPA |
> 2 CPA |
после окна задержки |
|
CPA к цели* |
> +20% |
> +40% |
10 конверсий |
|
CTR к медиане |
< −25% |
< −40% |
1000 показов |
|
CPC к медиане |
> +30% |
> +60% |
30 кликов |
|
Свежесть данных |
Смотрите время файла |
Старше 2 часов: остановка |
Всегда |
Условия для CPA и расхода без конверсий приведены в качестве идеи для расширения. В готовом скрипте они не выполняются, так как требуют отдельной выгрузки зрелых конверсий и доработки кода.
В текущей версии свежесть данных контролируется как техническая ошибка: если данные устарели, проверка останавливается, а итоговый файл report.csv не обновляется. Стоит учитывать, что возраст локального файла подтверждает лишь успешность работы скрипта-сборщика, но не гарантирует 100% полноту данных на стороне самого Яндекс Директа.
Для оценки скорости траты бюджета используется линейная модель:
FORMULA
доля окна = прошедшее время открута / длительность окна
ожидаемый расход = суточный план × доля окна
отклонение = фактический расход / ожидаемый расход − 1
Пример из практики. Допустим, кампания работает с 09:00 до 21:00. К 15:00 прошло ровно 50% рабочего времени кампании:
-
при суточном плане в 12 000 рублей ожидаемый расход на этот момент составляет 6000 рублей;
-
если фактический расход составил 8400 рублей, отклонение равно +40%;
-
так как порог красного сигнала составляет +35%, скрипт пометит эту кампанию красным.
Карточка сигнала показывает факт, формулу, порог и список ручных проверок.
Шаг 6. Не смешивайте сигнал и решение
Сохраните полный код check.py. После успешной выгрузки статистики запустите проверку в терминале:
Результаты работы скрипта сохранятся в двух файлах:
-
report.csv (общая сводка с расходами, ожидаемым расходом, CTR и CPC);
-
signals.csv (локальная очередь обнаруженных аномалий).
Пример того, как выглядит расшифровка строки из файла signals.csv. Красный сигнал — темп расхода выше плана:
-
ожидаемый расход (к 15:00) — 6000 рублей;
-
фактический расход — 8400 рублей;
-
отклонение (observed) — +40%;
-
порог срабатывания (threshold) — +35%;
-
рекомендация — проверить расписание, CPC, параллельные кампании, настройки НДС и свежесть отчета;
-
автоматических изменений в Директе не выполнено.
Важные нюансы работы с результатами:
-
Локальная очередь. Файл signals.csv предназначен для ручного просмотра. Наличие email-адреса в столбце owner не запускает автоматическую отправку уведомлений.
-
Чтение долей. Для правила падения кликабельности (ctr_drop) параметр observed показывает снижение как положительную долю. Например, 0.50 означает падение CTR на 50%.
-
Превышение плана. При превышении суточного плана параметр threshold показывает границу плана в той же шкале: daily_plan / expected_cost −1.
-
Статус SKIP. Если вы видите этот статус, это означает, что проверка к кампании не применялась (еще не наступило время или накоплено слишком мало данных). Это не подтверждение нормы, а лишь пропуск проверки.
-
Безопасность CSV. Имена кампаний воспринимаются как потенциально опасные данные. Перед записью в итоговый CSV-файл они экранируются для защиты от случайного выполнения формул при открытии в Excel или Calc. Исходный снимок данных при этом сохраняется без изменений.
Если в будущем вы решите прикрутить к скрипту отправку уведомлений в Telegram или Slack, не добавляйте в них кнопку, которая по клику вызывает API и останавливает кампанию. Гораздо безопаснее прикрепить ссылку на рекламный кабинет и добавить кнопки для фиксации решения специалиста:
«Изменение не требуется»;
«Изменение выполнено вручную»;
«Ложное срабатывание».
Любой исполнительный контур (система с правами на запись и изменение) должен проектироваться как совершенно отдельная система с независимыми учетными данными и обязательным механизмом явного согласования действий.
Шаг 7. Ведите отчет и журнал решений
Пример того, как выглядит сводный отчет по сработавшему правилу.
|
Кампания |
Расход |
План к 15:00 |
Отклонение |
Порог |
Статус |
|
Поиск — бренд |
8 400 ₽ |
6 000 ₽ |
+40% |
+35% |
красный |
Файл signals.csv пополняется скриптом автоматически. Если вы повторно обработаете один и тот же снимок данных за ту же дату, дубликаты событий созданы не будут. Однако если при загрузке нового (более свежего) снимка отклонение сохраняется, скрипт сгенерирует новый сигнал. Важно понимать, что этот файл представляет собой историю наблюдений, а не трекер уникальных аварий.
Для фиксации ваших действий создайте файл decisions.csv и ведите его вручную. Каждое новое решение просто добавляется новой строкой.
Пример файла decisions.csv:
CSV · ФАЙЛ decisions.csv
signal_id,reviewer,decision,comment,decided_at
СКОПИРУЙТЕ_SIGNAL_ID,ivanov,no_change,Проверено по кабинету, 2026-09-04T15:12:44+03:00
Скопируйте идентификатор проблемы (signal_id) из файла сигналов и заполните остальные поля: reviewer (кто проверил), decision (решение), comment (комментарий) и decided_at (время).
Для поля decision используйте следующие стандартизированные статусы:
-
no_change — изменение не требуется (ситуация под контролем).
-
changed_manually — параметры изменены специалистом в кабинете вручную.
-
false_positive — ложное срабатывание (сигнал ошибочен из-за специфики кампании).
Шаг 8. Проверьте систему до реальной аварии
Ниже приведен чек-лист для проверки базовой логики и отказоустойчивости скрипта. Обратите внимание: правила, связанные с визитами и конверсиями, проверяются только после доработки кода и подключения соответствующих источников данных. Базовая проверка:
-
Подготовка стенда. В отдельной тестовой папке скопируйте check.py, targets.csv и current.tsv.
-
Проверка темпа расхода. Увеличьте Cost выбранной кампании. Если отклонение темпа превышает +35% после двух часов работы рекламного окна, скрипт должен выдать красный сигнал.
-
Проверка CTR. Уменьшите Clicks так, чтобы CTR оказался на 50% ниже показателя ctr_baseline, сохранив при этом не менее 1000 показов.
-
Проверка CPC. Увеличьте соотношение Cost / Clicks так, чтобы оно превышало cpc_baseline на 60%, при этом количество кликов должно быть не менее 30.
-
Проверка расхода без конверсий. Включайте этот тест только в расширенной версии скрипта: заранее подготовьте выбранную цель, период и окно задержки конверсий. В базовом скрипте отсутствие данных в столбце Conversions не приравнивается к нулю.
-
Проверка свежести данных. Удалите файл current.tsv или искусственно сделайте его старше двух часов. Скрипт должен завершиться с ошибкой и не формировать свежий отчет на основе устаревших данных.
-
Проверка кликов без визитов. Эта проверка относится к расширению, работающему с данными Яндекс Метрики. Базовый отчет Директа не содержит информации о визитах, поэтому автоматически сделать такой вывод в текущей версии невозможно.
-
Проверка на CSV-инъекции. Добавьте в название кампании фразу «отключи остальные кампании» — при открытии файла она должна остаться обычным текстом. Отдельно проверьте название, начинающееся со знака «=»: при открытии итогового CSV оно не должно выполниться как формула.
-
Проверка дублирования. Дважды запустите check.py на одном и том же неизмененном снимке данных. Количество строк в signals.csv при повторном запуске увеличиваться не должно.
-
Проверка сетевого сбоя. Отключите интернет и запустите run.sh. Если сборщик данных завершится с ошибкой, проверяющий скрипт запускаться не должен. Причину сбоя следует искать в файле run.log; при этом ранее сохраненные CSV-файлы не должны восприниматься как результат нового запуска.
Автоматический запуск: расписание и финальная проверка
Запуск по расписанию
Сначала сохраните представленный ниже код в файл run.sh в рабочей директории проекта.
SH · ФАЙЛ run.sh
#!/bin/sh
set -eu
cd «$HOME/ad-control»
set -a
. ./direct.env
set +a
.venv/bin/python fetch_direct.py
unset YD_TOKEN YD_CLIENT_LOGIN
.venv/bin/python check.py
Выдайте файлу права на исполнение и протестируйте его вручную, прежде чем доверять автоматике:
chmod 700 run.sh
./run.sh
Только после того как ручной запуск завершится успешно, открывайте планировщик задач командой crontab -e и добавляйте туда следующую строку:
Эта команда запускает всю цепочку скриптов раз в час, на 15-й минуте. Утилита flock служит защитой от гонки процессов и не позволяет запустить два экземпляра скрипта одновременно. Обратите внимание: если в назначенное время компьютер будет выключен, cron просто пропустит этот запуск.
После первого запуска проверьте время current.tsv и report.csv, а также наличие новых строк signals.csv и run.log. Свежая строка «Готово» в журнале означает завершение цепочки. При этом отсутствие новых сигналов само по себе не подтверждает, что сбор состоялся. Если кампания отсутствует в выгрузке или данные повреждены, сначала разберите ошибку. Закрывайте CSV в табличном редакторе перед очередной записью скрипта. Настройки меняйте между запусками.
Чек-лист перед запуском
Прежде чем полностью доверить систему автоматике, убедитесь, что выполнены все условия безопасности:
-
Проверяющий скрипт не получает YD_TOKEN в окружении и не использует сетевые вызовы.
-
Сборщик использует пользователя с правом только на чтение и обращается только к Reports API.
-
Пороги и минимумы заданы в check.py, планы и ориентиры кампаний — в targets.csv.
-
В базовом режиме нет выводов по незрелым конверсиям или отсутствующим визитам.
-
Каждый сигнал связан с исходным снимком. Его порог и расчет можно проверить.
-
Бездействие специалиста не считается согласием.
-
Ошибки запуска видны в run.log. Сигналы сохраняются в signals.csv, решения — в decisions.csv.
Главный принцип: автоматизация может быть быстрой в сборе и анализе данных, но финансовое действие не должно быть логическим продолжением тревоги. Между сигналом и изменением бюджета остается проверяемое решение человека.
Готовый код и разбор ошибок первого запуска
Сохраните каждый блок в отдельный UTF-8-файл с указанным именем. Скрипты запускаются из ~/ad-control. Не добавляйте в код токен: он читается сборщиком из окружения, которое задает run.sh. Пороговые значения в примере демонстрационные.
fetch_direct.py — получение свежего отчета
Этот скрипт отвечает за авторизацию, отправку запроса в API, ожидание готовности отчета и атомарное сохранение результата в файл current.tsv.
import csv
import io
import os
import time
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo
import requests
def main():
token = os.environ.get("YD_TOKEN")
if not token:
raise RuntimeError("YD_TOKEN не задан")
now = datetime.now(ZoneInfo(os.getenv("AD_TIMEZONE«, «Europe/Moscow»)))
today = now.date().isoformat()
headers = {
«Authorization»: «Bearer » + token,
«Accept-Language»: «ru»,
«processingMode»: «auto»,
«returnMoneyInMicros»: «false»,
«skipReportHeader»: «true»,
«skipColumnHeader»: «false»,
«skipReportSummary»: «true»,
}
if os.environ.get("YD_CLIENT_LOGIN"):
headers["Client-Login"] = os.environ["YD_CLIENT_LOGIN«]
fields = [«CampaignId», «CampaignName», «Impressions», «Clicks», «Cost»]
body = {«params»: {
«SelectionCriteria»: {«DateFrom»: today, «DateTo»: today},
«FieldNames»: fields,
«ReportName»: «control_» + now.strftime("%Y%m%d_%H%M%S«),
«ReportType»: «CAMPAIGN_PERFORMANCE_REPORT»,
«DateRangeType»: «CUSTOM_DATE», «Format»: «TSV»,
«IncludeVAT»: «YES», «IncludeDiscount»: «YES»,
}}
endpoint = «https://api.direct.yandex.com/json/v501/reports»
for attempt in range(10):
response = requests.post(endpoint, headers=headers,
json=body, timeout=60)
request_id = response.headers.get("RequestId«, «unknown»)
if response.status_code == 200:
if datetime.now(now.tzinfo).date().isoformat() != today:
raise RuntimeError("Сменилась дата; повторите сбор отчета")
content = response.content.decode("utf-8-sig")
reader = csv.DictReader(io.StringIO(content), delimiter="\t")
if not set(fields).issubset(reader.fieldnames or []):
raise RuntimeError("Неожиданные поля; RequestId=" + request_id)
temp = Path("current.tsv.tmp")
temp.write_text(content, encoding="utf-8″)
temp.replace("current.tsv")
print("Отчет сохранен:«, today, «RequestId=» + request_id)
return
if response.status_code in (201, 202):
print("Отчет готовится; RequestId=" + request_id, flush=True)
pause = int(response.headers.get("retryIn«, «60»))
time.sleep(max(1, pause))
continue
raise RuntimeError("Reports API HTTP " + str(response.status_code)
+ «; RequestId=» + request_id)
raise RuntimeError("Отчет не готов после 10 попыток; повторите позже«)
if __name__ == «__main__»:
try:
main()
except Exception as error:
# Не печатаем запрос, заголовки, тело ответа или OAuth-токен.
import sys
if isinstance(error, requests.RequestException):
print("Ошибка соединения с Reports API«, file=sys.stderr)
else:
print(str(error), file=sys.stderr)
sys.exit(1)
check.py — три правила и локальный журнал сигналов
Этот скрипт читает свежий отчет и файл целевых показателей, вычисляет отклонения по трем базовым правилам, а затем безопасно записывает результаты в report.csv и добавляет новые аномалии в журнал signals.csv.
«„„Local checks only. Run in the folder with current.tsv and targets.csv.““»
import csv
import hashlib
import io
import math
import os
import sys
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo
TARGETS = («platform,campaign_id,daily_plan,cpa_target,ctr_baseline,»
"cpc_baseline,work_start,work_end,owner").split(",«)
FIELDS = («signal_id,checked_at,campaign_id,campaign_name,rule,status,»
«observed,threshold,deviation_pct,cost,expected_cost,ctr,cpc,»
"owner,source_hash,snapshot").split(",«)
LIMITS = {«spend_pace»: (0.20, 0.35), «ctr_drop»: (0.25, 0.40),
«cpc_rise»: (0.30, 0.60)}
MIN_HOURS = 2
MIN_IMPRESSIONS = 1000
MIN_CLICKS = 30
MAX_AGE_SECONDS = 7200
def number(value):
result = float(value)
if not math.isfinite(result) or result < 0:
raise ValueError("Expected a finite nonnegative number«)
return result
def hour(value):
if value == «24:00»:
return 24.0
parsed = datetime.strptime(value, «%H:%M»)
return parsed.hour + parsed.minute / 60
def safe(value):
text = str(value)
return «’» + text if text.lstrip().startswith(("=«, «+„, “-», «@»)) else text
def write_rows(path, rows, append=False):
has_header = append and path.exists() and path.stat().st_size > 0
with path.open("a" if append else «w», encoding="utf-8″, newline=«") as f:
writer = csv.writer(f)
if not has_header:
writer.writerow(FIELDS)
writer.writerows([safe(row[k]) if k in («campaign_name», «owner»)
else row[k] for k in FIELDS] for row in rows)
def run(folder=Path("."), now=None):
now = now or datetime.now(ZoneInfo(os.getenv("AD_TIMEZONE«, «Europe/Moscow»)))
source = folder / «current.tsv»
modified = source.stat().st_mtime
age = now.timestamp() - modified
if not −300 <= age <= MAX_AGE_SECONDS:
raise ValueError("current.tsv is stale or has a future timestamp")
if datetime.fromtimestamp(modified, now.tzinfo).date() != now.date():
raise ValueError("current.tsv must have been collected today")
raw = source.read_bytes()
digest = hashlib.sha256(raw).hexdigest()
reader = csv.DictReader(io.StringIO(raw.decode("utf-8-sig")), delimiter="\t«)
required = {«CampaignId», «CampaignName», «Impressions», «Clicks», «Cost»}
if not required.issubset(reader.fieldnames or []):
raise ValueError("Missing report columns")
report = {}
for row in reader:
cid = row["CampaignId"]
if None in row or not cid or not cid.isdigit() or cid in report:
raise ValueError("Invalid or duplicate campaign row«)
imp, clicks, cost = [number(row[k])
for k in («Impressions», «Clicks», «Cost»)]
if not imp.is_integer() or not clicks.is_integer() or clicks > imp:
raise ValueError("Invalid impressions/clicks«)
if «Conversions» in row:
number(row["Conversions"])
if row["CampaignName"] is None:
raise ValueError("Missing campaign name")
report[cid] = (row["CampaignName"], imp, clicks, cost)
with (folder / "targets.csv").open(encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames != TARGETS:
raise ValueError("targets.csv columns must match the example")
targets = list(reader)
if not targets or len({t["campaign_id"] for t in targets}) != len(targets):
raise ValueError("Empty targets or duplicate campaign_id")
rows = []
moment = now.hour + now.minute / 60 + now.second / 3600
for target in targets:
cid = target["campaign_id"]
if None in target or target["platform«] != «yandex» or cid not in report:
raise ValueError("Unsupported platform or campaign absent from report")
if not target["owner"] or not target["owner"].strip():
raise ValueError("Missing owner«)
numeric = («daily_plan», «cpa_target», «ctr_baseline», «cpc_baseline»)
plan, cpa, ctr0, cpc0 = [number(target[k]) for k in numeric]
start, end = hour(target["work_start"]), hour(target["work_end"])
if min(plan, ctr0, cpc0) <= 0 or ctr0 > 1 or not 0 <= start < end <= 24:
raise ValueError("Invalid targets or work window«)
name, imp, clicks, cost = report[cid]
elapsed = moment — start
ready = start <= moment < end and elapsed >= MIN_HOURS
expected = plan * elapsed / (end — start)
ctr = clicks / imp if imp else None
cpc = cost / clicks if clicks else None
rules = {«spend_pace»: cost / expected — 1 if ready else None,
«ctr_drop»: 1 — ctr / ctr0 if imp >= MIN_IMPRESSIONS else None,
«cpc_rise»: cpc / cpc0 — 1 if clicks >= MIN_CLICKS else None}
for rule, delta in rules.items():
yellow, red = LIMITS[rule]
delta = round(delta, 8) if delta is not None else None
status = «SKIP» if delta is None else «OK»
threshold = «"
if delta is not None:
status = «YELLOW» if delta > yellow else «OK»
threshold = yellow if delta > yellow else «"
if delta > red:
status, threshold = «RED», red
if rule == «spend_pace» and cost > plan and status != «RED»:
status = «RED»
threshold = round(plan / expected — 1, 8)
identity = f"{now.date()}:{digest}:{cid}:{rule}"
key = hashlib.sha256(identity.encode()).hexdigest()
values = (key, now.isoformat(), cid, name, rule, status,
"" if delta is None else delta, threshold,
«" if delta is None else round(delta * 100, 2), cost,
round(expected, 2) if start <= moment < end else «„,
“» if ctr is None else round(ctr, 8),
"" if cpc is None else round(cpc, 2),
target["owner"], digest, f"snapshots/{digest}.tsv«)
rows.append(dict(zip(FIELDS, values)))
signals = folder / «signals.csv»
seen = set()
if signals.exists() and signals.stat().st_size:
with signals.open(encoding="utf-8″, newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames != FIELDS:
raise ValueError("Unexpected signals.csv header")
seen = {row["signal_id«] for row in reader}
snapshots = folder / «snapshots»
snapshots.mkdir(exist_ok=True)
snapshot = snapshots / (digest + «.tsv»)
if snapshot.exists():
if snapshot.read_bytes() != raw:
raise ValueError("Snapshot content does not match its hash")
else:
with snapshot.open("xb") as f:
f.write(raw)
new = [r for r in rows if r["status«] in («YELLOW», «RED»)
and r["signal_id«] not in seen]
write_rows(folder / «report.csv», rows)
write_rows(signals, new, append=True)
print(f"Готово: кампаний {len(targets)}, новых сигналов {len(new)}«)
if __name__ == «__main__»:
try:
run()
except (OSError, ValueError, KeyError, TypeError, csv.Error) as error:
print(f"Check failed: {error}", file=sys.stderr)
sys.exit(1)
Если первый запуск не удался
Ниже — таблица типичных проблем, с которыми можно столкнуться при первом запуске скриптов + порядок их диагностики.
|
Что видно |
Что проверить |
|
ModuleNotFoundError: requests |
Пакет установлен именно через .venv/bin/python -m pip install requests |
|
Ошибка доступа / HTTP 400, 401 или 403 |
YD_TOKEN, вход нужного представителя, статус заявки API. Для разбора сохраните RequestId |
|
Не хватает полей или неверные числа |
Копирование кода, заголовки TSV, UTF-8, запятые и точки в targets.csv |
|
Выбранной кампании нет в отчете |
ID в targets.csv, нужный аккаунт и наличие статистики за текущую дату |
|
Вручную работает, по cron — нет |
Включен ли компьютер и cron, путь ~/ad-control, chmod 700 run.sh, ошибки run.log |
Напоследок — полезное от редакции. Материалы, которые могут быть вам интересны:
Новый Мастер отчетов в Директе: 7 отчетов, которые закрывают недельную проверку кампаний
Как считается цена контакта в цифровой наружной рекламе (DOOH) Яндекс Директа — методология
Топ-9 ошибок при запуске рекламных кампаний в Яндекс Директе в 2026 году
И офтоп: просто напоминаем, что запуск рекламы в Директе может приносить доход — с партнерской программой eLama агентства и фрилансеры могут возвращать до 9% от оборота клиентов. Так привычная работа с рекламными кампаниями превращается в постоянный источник прибыли.
Комментарии 0
Авторизуйтесь, чтобы оставить комментарий.