Автоматический контроль рекламы в Директе: инструкция на Python
UnionCreated using FigmalectureCreated using Figma
new

⭐️Курс по настройке рекламы в мессенджере MAX Пройти бесплатно

10671 https://ppc.world/uploads/images/2b/b8/6af1dbf913d64-Kategoriya-Yandeks.jpg 2026-11-10 Директ ppc.world 160 31

Как настроить автоматический контроль рекламы в Яндекс Директе — инструкция

Всего одна настройка — и у вас есть скрипт, который каждый час проверяет расход, CTR и CPC в Директе и сообщает, если что-то пошло не так. Бесплатно и без сторонних сервисов. Как это собрать, рассказывает основатель и генеральный директор AiHummer Леон Сокиркин.

Ещё больше полезных статей и свежих новостей в нашем Telegram-канале
Telegram Подписаться

Автоматический контроль рекламы — это когда кампаниями управляют программы, а не человек вручную. Они сами следят за ставками, бюджетом и аудиторией, смотрят на данные и решают, что поменять, чтобы реклама работала лучше и средств уходило меньше.

В этой инструкции разберем разновидность такого контроля, где программа только находит отклонение и сообщает о нем, а решение — остановить кампанию, снизить бюджет или оставить как есть — принимает специалист:

Кто сможет настроить автоматический контроль рекламы в Директе

Это руководство подойдет специалистам по контекстной рекламе и маркетологам, которые ведут кампании в Яндекс Директе. Для настройки потребуются: доступ к Директу, OAuth-приложение, Python 3.11+, текстовый редактор, редактор CSV и планировщик cron.

Чтобы не тратить время зря, сначала убедитесь, что задача вам подходит. В таблице ниже — краткая выжимка по требованиям к навыкам, оборудованию и итоговому результату работы.

Кому подойдет инструкция

Специалистам по контекстной рекламе и маркетологам, которые самостоятельно ведут кампании в Яндекс Директе

Необходимые навыки

Понимание показателей расхода, 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 и визитов описаны отдельно как возможные расширения: для них сначала нужно подготовить дополнительные данные и учесть задержку конверсий.

Что нужно подготовить до старта

Перед настройкой автоконтроля нужно подготовить рабочие инструменты, проверить доступы и убедиться, что всё соответствует техническим требованиям. На этом этапе также важно определить, где будут храниться скрипты, настройки и отчеты, а затем проверить, что все компоненты готовы к совместной работе.

Что понадобится для настройки автоконтроля рекламы в Директе

Для настройки автоконтроля потребуется шесть основных компонентов: доступ к Яндекс Директу, OAuth-приложение, Python 3.11+, текстовый редактор, редактор CSV и планировщик cron. Все настройки сгруппированы в конкретных файлах одной папки — никаких скрытых модулей или черных ящиков.

Ниже — полный перечень с указанием конкретных действий и инструментов, где выполняется каждый шаг.

Инструмент

Что делаем

Где находится настройка

Яндекс Директ

Даем доступ к статистике и смотрим итоговые изменения

Браузер: «Инструменты → Ваши представители»

«Настройки 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.

Что подготовить до запуска автоконтроля в Директе

До запуска нужно иметь действующий кабинет Директа с правами главного представителя, выбрать кампании для наблюдения и зафиксировать их ID, суточные планы и ориентиры CTR/CPC.

Прежде чем настраивать скрипты, убедитесь, что у вас есть все необходимое.

Требование

Комментарий

Кабинет Директа

Действующий аккаунт с хотя бы одной активной кампанией

Права главного представителя

Необходимы для того, чтобы иметь возможность выдавать доступ другим пользователям

Доступ к API Метрики

Не требуется. В базовом варианте расход, показы и клики берутся напрямую из Директа

Перед началом настройки:

  1. Выберите кампании для наблюдения и выпишите их числовые ID.

  2. Зафиксируйте суточный план, рабочие часы и ориентиры CTR/CPC.

  3. Под главным представителем откройте «Инструменты → Ваши представители».

  4. Добавьте отдельный Яндекс-логин, который еще не использовался для входа в Директ, и назначьте «Только чтение».

  5. Авторизуйте будущий скрипт именно от этого пользователя, а не от владельца кабинета. Права API наследуют ограничения роли.

  6. На oauth.yandex.ru зарегистрируйте приложение «Для доступа к API или отладки».

  7. Добавьте разрешения direct:api и passport:business, сохраните ClientID. В настройках платформы задайте Redirect URI: https://oauth.yandex.ru/verification_code.

  8. В Директе под логином разработчика откройте «Настройки API → Мои заявки → Новая заявка».

  9. Укажите ClientID и назначение: внутренний скрипт получения отчетов. Дождитесь одобрения заявки. Яндекс указывает срок до семи дней.

Для внутреннего скрипта можно получить токен вручную:

  1. В отдельном профиле браузера войдите под представителем «Только чтение», откройте адрес ниже, заменив CLIENT_ID своим ClientID, разрешите доступ.

  2. Сохраните access_token со страницы результата в direct.env, как показано дальше:

    1. ClientID и OAuth-токен — разные значения;

    2. Client secret в этот скрипт не вставляется.

      https://oauth.yandex.ru/authorize?response_type=token&client_id=CLIENT_ID

Не отправляйте токен в чат и не включайте его в статью, скриншоты или общий репозиторий. Название разрешения direct:api само по себе не означает «только чтение»: защиту дает роль пользователя в Директе. Если вы не можете выдать такую роль или получить одобрение API, остановитесь на подготовке — заменять ее токеном владельца кабинета нельзя.

Что именно будем автоматизировать

Автоматизируется только обнаружение отклонений: сборщик собирает статистику через API, система сравнивает ее с планом и порогами, фиксирует отклонения в signals.csv и выводит в терминал — а решение и все изменения в кампании специалист принимает и вносит вручную. Автоматического управления ставками, лимитами и объявлениями нет — только выявление проблем, отдельно от их устранения.

Процесс состоит из следующих шагов:

  1. Сборщик получает статистику рекламных кампаний через отчетный API.

  2. Проверяющий модуль сопоставляет показатели с планом и порогами.

  3. При отклонении в signals.csv появляется запись с исходными значениями и порогом. Краткий результат выводится в терминал.

  4. Специалист проверяет сигнал и принимает решение.

  5. Решение фиксируется в журнале, а изменение вручную вносится в рекламном кабинете.

Граница системы: в этой инструкции «автоматический контроль» означает автоматическое обнаружение отклонений. Автоматического управления ставками, лимитами, статусами и объявлениями нет.

Односторонний контур: сбор и анализ отделены от управления рекламными кампаниями.

8 шагов настройки автоматического контроля

Восемь шагов проведут вас от чистой папки до рабочей системы контроля: установите окружение, разделите сбор статистики и управление кампаниями, задайте планы и пороги, подключите Reports API, настройте правила аномалий, разделите сигналы и решения, начните вести журнал и протестируйте всё на смоделированных данных.

Шаг 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 ИМЯ_ФАЙЛА. Вставляйте только содержимое кода (без заголовков блоков):

Настройка окружения: создайте файл конфигурации 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 он также сможет исключительно получать данные. Вся архитектура построена на строгом разделении ролей:

  1. Безопасный сбор данных: скрипт-сборщик обращается только к сервису Reports и сохраняет скачанные данные в виде исходного TSV или нормализованного CSV. В коде полностью отсутствуют методы, изменяющие настройки кампаний.

  2. Изолированная проверка: проверяющий модуль работает исключительно с готовым файлом статистики. Он не использует сетевые библиотеки и вообще не имеет доступа к OAuth-токену.

  3. Аудит: снимок исходного отчета сохраняется вместе с каждым зафиксированным сигналом.

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 — сотрудник, который обязан проверить сигнал.

Важные нюансы:

  1. Налоги. Все денежные поля указываются в рублях. В данном примере статистика выгружается с учетом НДС, поэтому ваши плановые показатели (daily_plan, cpc_baseline) также должны включать НДС.

  2. Расписание. Для круглосуточных кампаний указывайте 00:00 и 24:00. Окна показов, переходящие через полночь (например, с 23:00 до 06:00), этот базовый вариант скрипта не поддерживает.

  3. Бизнес-логика. Не применяйте один и тот же норматив ко всем кампаниям. Поиск, РСЯ и ретаргетинг кардинально отличаются по поведению. Для новых кампаний сначала накопите статистику хотя бы за 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, вам потребуется:

  1. Задать в запросе конкретные цели и модель атрибуции.

  2. Использовать сопоставимый завершенный период (а не текущий день) и повторно загружать данные с учетом задержки атрибуции конверсий.

  3. Учесть, что при добавлении параметра 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. После успешной выгрузки статистики запустите проверку в терминале:

.venv/bin/python check.py

Результаты работы скрипта сохранятся в двух файлах:

  • report.csv (общая сводка с расходами, ожидаемым расходом, CTR и CPC);

  • signals.csv (локальная очередь обнаруженных аномалий).

Пример того, как выглядит расшифровка строки из файла signals.csv. Красный сигнал — темп расхода выше плана:

  • ожидаемый расход (к 15:00) — 6000 рублей;

  • фактический расход — 8400 рублей;

  • отклонение (observed) — +40%;

  • порог срабатывания (threshold) — +35%;

  • рекомендация — проверить расписание, CPC, параллельные кампании, настройки НДС и свежесть отчета;

  • автоматических изменений в Директе не выполнено.

Важные нюансы работы с результатами:

  1. Локальная очередь. Файл signals.csv предназначен для ручного просмотра. Наличие email-адреса в столбце owner не запускает автоматическую отправку уведомлений.

  2. Чтение долей. Для правила падения кликабельности (ctr_drop) параметр observed показывает снижение как положительную долю. Например, 0.50 означает падение CTR на 50%.

  3. Превышение плана. При превышении суточного плана параметр threshold показывает границу плана в той же шкале: daily_plan / expected_cost −1.

  4. Статус SKIP. Если вы видите этот статус, это означает, что проверка к кампании не применялась (еще не наступило время или накоплено слишком мало данных). Это не подтверждение нормы, а лишь пропуск проверки.

  5. Безопасность 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. Проверьте систему до реальной аварии

Ниже приведен чек-лист для проверки базовой логики и отказоустойчивости скрипта. Обратите внимание: правила, связанные с визитами и конверсиями, проверяются только после доработки кода и подключения соответствующих источников данных. Базовая проверка:

  1. Подготовка стенда. В отдельной тестовой папке скопируйте check.py, targets.csv и current.tsv.

  2. Проверка темпа расхода. Увеличьте Cost выбранной кампании. Если отклонение темпа превышает +35% после двух часов работы рекламного окна, скрипт должен выдать красный сигнал.

  3. Проверка CTR. Уменьшите Clicks так, чтобы CTR оказался на 50% ниже показателя ctr_baseline, сохранив при этом не менее 1000 показов.

  4. Проверка CPC. Увеличьте соотношение Cost / Clicks так, чтобы оно превышало cpc_baseline на 60%, при этом количество кликов должно быть не менее 30.

  5. Проверка расхода без конверсий. Включайте этот тест только в расширенной версии скрипта: заранее подготовьте выбранную цель, период и окно задержки конверсий. В базовом скрипте отсутствие данных в столбце Conversions не приравнивается к нулю.

  6. Проверка свежести данных. Удалите файл current.tsv или искусственно сделайте его старше двух часов. Скрипт должен завершиться с ошибкой и не формировать свежий отчет на основе устаревших данных.

  7. Проверка кликов без визитов. Эта проверка относится к расширению, работающему с данными Яндекс Метрики. Базовый отчет Директа не содержит информации о визитах, поэтому автоматически сделать такой вывод в текущей версии невозможно.

  8. Проверка на CSV-инъекции. Добавьте в название кампании фразу «отключи остальные кампании» — при открытии файла она должна остаться обычным текстом. Отдельно проверьте название, начинающееся со знака «=»: при открытии итогового CSV оно не должно выполниться как формула.

  9. Проверка дублирования. Дважды запустите check.py на одном и том же неизмененном снимке данных. Количество строк в signals.csv при повторном запуске увеличиваться не должно.

  10. Проверка сетевого сбоя. Отключите интернет и запустите run.sh. Если сборщик данных завершится с ошибкой, проверяющий скрипт запускаться не должен. Причину сбоя следует искать в файле run.log; при этом ранее сохраненные CSV-файлы не должны восприниматься как результат нового запуска.

Автоматический запуск: расписание и финальная проверка

Этот раздел завершает настройку системы: здесь скрипты сбора и проверки данных объединяются в единый файл run.sh и переводятся на автоматический запуск через cron. Ниже описано, как безопасно протестировать цепочку вручную, подключить расписание с защитой от повторного запуска через flock, а также как контролировать результаты работы системы по журналам.

Запуск по расписанию

Сначала сохраните представленный ниже код в файл 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 * * * * /usr/bin/flock -n «$HOME/ad-control/run.lock» «$HOME/ad-control/run.sh» >> «$HOME/ad-control/run.log» 2>&1

Эта команда запускает всю цепочку скриптов раз в час, на 15-й минуте. Утилита flock служит защитой от гонки процессов и не позволяет запустить два экземпляра скрипта одновременно. Обратите внимание: если в назначенное время компьютер будет выключен, cron просто пропустит этот запуск.

После первого запуска проверьте время current.tsv и report.csv, а также наличие новых строк signals.csv и run.log. Свежая строка «Готово» в журнале означает завершение цепочки. При этом отсутствие новых сигналов само по себе не подтверждает, что сбор состоялся. Если кампания отсутствует в выгрузке или данные повреждены, сначала разберите ошибку. Закрывайте CSV в табличном редакторе перед очередной записью скрипта. Настройки меняйте между запусками.

Чек-лист перед запуском

Прежде чем полностью доверить систему автоматике, убедитесь, что выполнены все условия безопасности:

  1. Проверяющий скрипт не получает YD_TOKEN в окружении и не использует сетевые вызовы.

  2. Сборщик использует пользователя с правом только на чтение и обращается только к Reports API.

  3. Пороги и минимумы заданы в check.py, планы и ориентиры кампаний — в targets.csv.

  4. В базовом режиме нет выводов по незрелым конверсиям или отсутствующим визитам.

  5. Каждый сигнал связан с исходным снимком. Его порог и расчет можно проверить.

  6. Бездействие специалиста не считается согласием.

  7. Ошибки запуска видны в run.log. Сигналы сохраняются в signals.csv, решения — в decisions.csv.

Главный принцип: автоматизация может быть быстрой в сборе и анализе данных, но финансовое действие не должно быть логическим продолжением тревоги. Между сигналом и изменением бюджета остается проверяемое решение человека.

Готовый код и разбор ошибок первого запуска

В этом разделе собран полный код обоих скриптов, готовый к использованию. Сохраните каждый блок в отдельный файл в кодировке UTF-8 с указанным именем. Запускать скрипты нужно из директории ~/ad-control. Не вставляйте токен доступа прямо в код: он безопасно считывается сборщиком из переменных окружения, которые задает run.sh. Обратите внимание, что все пороговые значения в примерах ниже являются демонстрационными и подлежат калибровке под ваш проект.

Сохраните каждый блок в отдельный 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

Напоследок — полезное от редакции. Материалы, которые могут быть вам интересны:

И офтоп: просто напоминаем, что запуск рекламы в Директе может приносить доход — с партнерской программой eLama агентства и фрилансеры могут возвращать до 9% от оборота клиентов. Так привычная работа с рекламными кампаниями превращается в постоянный источник прибыли.

Последние комментарии

Ваша реклама на ppc.world

от 10 000 ₽ в неделю

Узнать подробнее

Лучшие статьи за месяц

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: