{"id":14274,"url":"\/distributions\/14274\/click?bit=1&hash=fadd1ae2f2e07e0dfe00a9cff0f1f56eecf48fb8ab0df0b0bfa4004b70b3f9e6","title":"\u0427\u0435\u043c \u043c\u0443\u0440\u0430\u0432\u044c\u0438\u043d\u044b\u0435 \u0434\u043e\u0440\u043e\u0436\u043a\u0438 \u043f\u043e\u043c\u043e\u0433\u0430\u044e\u0442 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430\u043c?","buttonText":"\u0423\u0437\u043d\u0430\u0442\u044c","imageUuid":"6fbf3884-3bcf-55d2-978b-295966d75ee2"}

Автоматизация отчетности. API Яндекс.Директа

Привет! Меня зовут Антон Перепечаев. Я специалист по контекстной рекламе, BI аналитик. Подписывайтесь на мой новый Телеграмм-канал по рекламной аналитике "Пытаюсь посчитать". В статье я объясняю как автоматизировать отчетность с помощью API Директа.

UPD. Я выпустил бесплатный видео курс по теме API Яндекс.Директа, можете посмотреть его после регистрации на платформе. Ссылка в посте:

👉🏻 Курсы по автоматизации отчетности и работе с данными.

Бесплатный курс по отчетности Директа в Datalens:

https://perepechaev-course.emdesell.ru/buy/datalens

Python для маркетологов:

https://t.me/poschitai/102

Клиентская отчетность:

https://t.me/poschitai/17

Клиентская отчетность +...

👉🏻 Курсы по автоматизации отчетности и работе с данными.

Бесплатный курс по отчетности Директа в Datalens:

https://perepechaev-course.emdesell.ru/buy/datalens

Python для маркетологов:

https://t.me/poschitai/102

Клиентская отчетность:

https://t.me/poschitai/17

Клиентская отчетность + глубокая аналитика:

https://t.me/poschitai/29

Теперь подробно.

Разница между курсами в способе автоматизации, месте хранения и количестве данных.

Первый курс нужен для создания отчетности для клиента, руководства.

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

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

Начнем с теоретической части.

Что такое API в Яндекс.Директе?

API Директа - это набор команд для работы с данными от Яндекса.

Принцип работы следующий:
1. Мы по правилам Яндекса составляем запрос
2. Отправляем на сервер
3. Получаем ответ

Какие ответы мы можем получить:

- Готовый отчет по рекламным кампаниям
- Статусы кампаний
- Размер ставок
- Процент прогнозируемого трафика

Полный список намного длинней, но суть такова - мы можем управлять нашими кампаниями и получать статистику по скрипту на Python. Скрипт можно сделать полностью автоматическим(расписание) или полуавтоматическим(используя его каждый раз, когда он нам необходим).

Данная статья рассчитана на новичков, которые не знают этот язык и как на нем составить API запрос. Все необходимые данные, которые от вас потребуются находятся в разделе "Как составлять запросы к API?". Я детально описал последовательность действий шаг за шагом.

Двигаемся дальше.

Зачем выгружать данные через API?

UPD. На следующий день после публикации я понял, что было ошибкой не показать какую пользу мы получаем от скрипта.
Поэтому перед тем как читать дальше посмотрите 3-х минутное видео с примером работы API скрипта :

Дополнительные примеры использования скрипта:

- Автоматизация клиентской отчетности

Допустим, у вас агентство и вы ведете 25 клиентов. Все ваши клиентские дашборды привязаны к данным из Google Sheets. С помощью API выгрузка данных в таблицы происходит одним кликом.

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

- Создание уникальных отчетов для себя

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

- Создание собственного сервиса управления кампаниями

Я могу сделать биддер.
Могу - оптимизатор кампаний, который на основе статистики выключает кампании, ключи.

Можно сделать скрипт, который каждый день будет обновлять даты в объявлениях, например "Акция до dd.mm!", подставляя вместо dd и mm дату.

В общем, как вы воспользуетесь инструментом - ваше дело, я зарезюмирую информацию выше:

API Директа - это получение данных от Яндекса с помощью скрипта.

Скрипт можно автоматизировать.

Далее я буду разбирать по частям код API запроса к директу. Ссылку на полную версию кода выложу в конце статьи.

Как составлять запросы к API?

У Яндекса есть примеры запросов, которые могут использоваться как основа. На этой странице вы можете ознакомиться с примером кода запроса для Python.
Я взял изначальный код, усовершенствовал его и дополнил комментариями.
Моя личная рекомендация - сразу попробуйте сделать то, что я описываю. Это не сложно.

Перед началом вам необходимо установить Python на компьютер.
Лучше всего об этом рассказано в видео:

В этом видео есть информация об установке библиотек, которые нам понадобятся.

Работать с кодом вам не потребуется. В п.2 введите данные и ваш отчет сформируется.

И так, из чего состоит код API запроса:

  1. Подключение библиотек и проверка версии Python
  2. Ввод пользовательских данных
  3. Создание запроса
  4. Отправка запроса
  5. Получение данных и первичная обработка

Теперь подробно.

Подключение библиотек и проверка версии Python

За данный этап отвечает следующий код:

# -*- coding: utf-8 -*- import requests from requests.exceptions import ConnectionError from time import sleep import json import pandas as pd # Метод для корректной обработки строк в кодировке UTF-8 как в Python 3, так и в Python 2 import sys #проверка версии пайтона if sys.version_info < (3,): def u(x): try: return x.encode("utf8") except UnicodeDecodeError: return x else: def u(x): if type(x) == type(b''): return x.decode('utf8') else: return x

Requests, Time, Pandas, Sys - это библиотеки с полезными для нас инструментами.
Если проводить аналогию - это плоскогубцы, молоток, ключ на 8 и домкрат.

Установить библиотеки можно через:

1) Команду "!pip install название_библиотеки" в Python

2) Анакоду, как в видео выше.
Выбирайте этот вариант, он проще. В списке найдите библиотеки Requests, Time, Pandas, Sys и нажмите установить.

Код "sys.version_info < (3,):" проверяет версию пайтона и если она ниже 3, то меняет кодировку.

Ввод пользовательских данных

# --- Входные данные --- # Адрес сервиса Reports для отправки JSON-запросов (регистрозависимый) ReportsURL = 'https://api.direct.yandex.com/json/v5/reports' # OAuth-токен пользователя, от имени которого будут выполняться запросы token = 'token834fdsf89diofefs9fesj9fj' # Логин клиента рекламного агентства # Обязательный параметр, если запросы выполняются от имени рекламного агентства clientLogin = 'client' #какие столбцы подгрузить stolbec = ['Date','CampaignName','Clicks','Cost','Conversions','Criteria','AdGroupName','TargetingLocationName','Age','Gender'] #ID цели goalID = ['43693399'] #даты отчета firstDate = "2022-05-01" secondDate = "2022-08-15"

ReportsURL - адрес для запроса данных. Не меняем.

token - в кавычках подставляем токен для нашего аккаунта.
Получить его можно на этом сайте. Достаточно авторизоваться под своим логином.
Если вы работаете на управляющем аккаунте - указывайте его при авторизации.

clientLogin - вписываем мейл на яндексе без @yandex.ru.
Сюда вписываем конечный логин клиента. Если у вас управляющий аккаунт - вписывайте логин клиента.

stolbec - список столбцов для выгрузки.
Я оставил столбцы:

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

Полный список столбцов:

Учтите, что не все столбцы можно сочитать друг с другом.
Чтобы отчет создался - поле в FieldNames и Criteria_perfomance_report не должны быть со знаком минуса. Например поле AdGroupName отвечает требованиям, а AdFormat - нет.

goalID - ID нашей цели. Найти ID можно в настройках целей в метрике.
Подставьте номер цели в кавычки. Если целей несколько - добавьте запятую и новый айдишник в таком виде: ['123','324']

firstDate и secondDate - выставляем первую и последнюю дату отчета. Формат yyyy-mm-dd.

Создание запроса

Наша основная часть запроса состоит из двух частей: headers и body.

# --- Подготовка запроса --- # Создание HTTP-заголовков запроса headers = { # OAuth-токен. Использование слова Bearer обязательно "Authorization": "Bearer " + token, # Логин клиента рекламного агентства "Client-Login": clientLogin, # Язык ответных сообщений "Accept-Language": "ru", # Режим формирования отчета "processingMode": "auto", # Формат денежных значений в отчете "returnMoneyInMicros": "true", #Не выводить в отчете строку с названием отчета и диапазоном дат #"skipReportHeader": "true", # Не выводить в отчете строку с названиями полей # "skipColumnHeader": "true", # Не выводить в отчете строку с количеством строк статистики "skipReportSummary": "true" }

В этой части кода в скрипт добавляются наш токен и логин клиента, которые мы указали ранее. Все вместе это называется headers (заголовки).

"returnMoneyInMicros": "false" - важная часть кода, которая требует пояснений. По умолчанию Яндекс импортирует расходы в API рублях, они в МЛН раз больше реальных рублей, т.е. 1 рубль расхода это 1000000 api рублей.
Данная функция позволяет импортировать сразу правильный вид расходов.

Помимо этого skipReportSummary убирает лишнюю строку с итогами.

Теперь приступим к основной части запроса.

body = { "params": { "SelectionCriteria": { "DateFrom": firstDate, "DateTo": secondDate }, "Goals": goalID, "FieldNames": stolbec, "ReportName":f"Отчет №1", "ReportType": "CRITERIA_PERFORMANCE_REPORT", "DateRangeType": "CUSTOM_DATE", "Format": "TSV", "IncludeVAT": "YES", "IncludeDiscount": "NO" } } # Кодирование тела запроса в JSON body = json.dumps(body, indent=4)

В body содержатся основные параметры отчета, его "тело".

SelectionCriteria - фильтр. В нашем случае он содержит только фильтр по датам.
DateFrom и DateTo - наши даты, которые мы описали заранее.

Goals - список целей из метрики, по которым будут учитываться конверсии в отчете.

FieldNames - список наших столбцов, которые мы указали заранее в переменной stolbec.

ReportName - нужен исключительно для создания имени запроса, ни на что не влияет.

ReportType - тип нашего отчета.

В нашем случае выбран "CRITERIA_PERFORMANCE_REPORT", что буквально означает "Отчет по ключевым словам(условиям показа)".

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

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

Как я писал в начале - вы можете хоть каждый день выгружать данные по новым уникальным поисковым запросам. Делается это с помощью другого типа отчетов "SEARCH_QUERY_PERFOMANCE_REPORT".

IncludeVAT - включать ли НДС. Если "YES" то сумма будет с учетом НДС указана +20%.

Остальные поля пока что не важны, но ознакомиться со всем списком можно по ссылке:

Отправка запроса

Сначала разберем часть кода, отвечающую за запрос к яндексу. Нужные данные для запроса мы добавляем в переменную req.

req = requests.post(ReportsURL, body, headers=headers) req.encoding = 'utf-8' # Принудительная обработка ответа в кодировке UTF-8

ReportsURL - адрес запроса к Яндексу
body - суть нашего запроса, что мы хотим получить
headers - заголовки из предыдущего пункта

req.encoding - оставляем как есть, это декодинг в нужный формат.

Дальше скрипт отправляет запрос и получает ответ с определенным кодом.

if req.status_code == 400: print("Параметры запроса указаны неверно или достигнут лимит отчетов в очереди") print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код запроса: {}".format(u(body))) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break elif req.status_code == 200: print("Отчет создан успешно") print("RequestId: {}".format(req.headers.get("RequestId", False))) break elif req.status_code == 201: print("Отчет успешно поставлен в очередь в режиме офлайн") retryIn = int(20) print("Повторная отправка запроса через {} секунд".format(retryIn)) print("RequestId: {}".format(req.headers.get("RequestId", False))) sleep(retryIn) elif req.status_code == 202: print("Отчет формируется в режиме офлайн") retryIn = int(req.headers.get("retryIn", 60)) print("Повторная отправка запроса через {} секунд".format(retryIn)) print("RequestId: {}".format(req.headers.get("RequestId", False))) sleep(retryIn) elif req.status_code == 500: print("При формировании отчета произошла ошибка. Пожалуйста, попробуйте повторить запрос позднее") print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break elif req.status_code == 502: print("Время формирования отчета превысило серверное ограничение.") print("Пожалуйста, попробуйте изменить параметры запроса - уменьшить период и количество запрашиваемых данных.") print("JSON-код запроса: {}".format(body)) print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break else: print("Произошла непредвиденная ошибка") print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код запроса: {}".format(body)) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break

Если ответ 200 - все прошло успешно и мы получили данные.

Код 201 и 202 - значит, что нужно подождать и не отключать скрипт до получения данных. Позже он сам сменится на 200.

Для каждого из этих ответов мы получим такой ответ:

В случае получения ошибки скрипт сам напишет в чем проблема. Пример ошибки и подробное объяснение:

Теперь мы соединяем наш запрос и ответ на него:

# --- Запуск цикла для выполнения запросов --- # Если получен HTTP-код 200, то выводится содержание отчета # Если получен HTTP-код 201 или 202, выполняются повторные запросы while True: try: req = requests.post(ReportsURL, body, headers=headers) req.encoding = 'utf-8' # Принудительная обработка ответа в кодировке UTF-8 if req.status_code == 400: print("Параметры запроса указаны неверно или достигнут лимит отчетов в очереди") print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код запроса: {}".format(u(body))) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break elif req.status_code == 200: print("Отчет создан успешно") print("RequestId: {}".format(req.headers.get("RequestId", False))) break elif req.status_code == 201: print("Отчет успешно поставлен в очередь в режиме офлайн") retryIn = int(20) print("Повторная отправка запроса через {} секунд".format(retryIn)) print("RequestId: {}".format(req.headers.get("RequestId", False))) sleep(retryIn) elif req.status_code == 202: print("Отчет формируется в режиме офлайн") retryIn = int(req.headers.get("retryIn", 60)) print("Повторная отправка запроса через {} секунд".format(retryIn)) print("RequestId: {}".format(req.headers.get("RequestId", False))) sleep(retryIn) elif req.status_code == 500: print("При формировании отчета произошла ошибка. Пожалуйста, попробуйте повторить запрос позднее") print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break elif req.status_code == 502: print("Время формирования отчета превысило серверное ограничение.") print("Пожалуйста, попробуйте изменить параметры запроса - уменьшить период и количество запрашиваемых данных.") print("JSON-код запроса: {}".format(body)) print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break else: print("Произошла непредвиденная ошибка") print("RequestId: {}".format(req.headers.get("RequestId", False))) print("JSON-код запроса: {}".format(body)) print("JSON-код ответа сервера: \n{}".format(u(req.json()))) break # Обработка ошибки, если не удалось соединиться с сервером API Директа except ConnectionError: # В данном случае мы рекомендуем повторить запрос позднее print("Произошла ошибка соединения с сервером API") # Принудительный выход из цикла break # Если возникла какая-либо другая ошибка except: # В данном случае мы рекомендуем проанализировать действия приложения print("Произошла непредвиденная ошибка") # Принудительный выход из цикла break

Полный код отвечает за цикл, который заканчивается когда:

а) Отчет готов
б) Ошибка в запросе

Получение данных и первичная обработка

Ответ на наш запрос мы получили в нечитабельном виде:

Поэтому теперь надо обработать запрос. За обработку отвечает следующий код:

#создаем csv файл и записываем в него ответ file = open("cashe.csv", "w") file.write(req.text) file.close() #превращаем ответ в таблицу df = pd.read_csv("cashe.csv",header=1, sep='\t', index_col=0, encoding="cp1251") #меняем значения в конверсиях - на 0, переводит в int for col in df.columns: if 'Conversions' in col: df[col] = df[col].replace('--','0').astype(int) conv = col #проверка таблицы df.groupby('CampaignName',as_index=False).agg({'Clicks':'sum','Cost':'sum',conv:'sum'}).sort_values(conv)

Что делает этот код:

  1. Создает в папке со скриптом файл cashe.csv (file = open("cashe.csv", "w"))
  2. Записывает в него ответ (file.write(req.text))
  3. Закрывает файлик, чтобы не повредить данные
  4. Переводит нечитабельный ответ в табличные данные:
    (df = pd.read_csv()))

5. Меняет знак "-" в конверсиях на 0. Приводит столбец к числовому типу данных.

6. Сохранить новую таблицу в Excel можно с помощью команды:
df.to_excel('test.xlsx')

В директории со скриптом появится файлик test.xlsx, в котором будет отчет.

Чек-лист настройки скрипта:
1. Python установлен
2. Библиотеки установлены
3. Токен аккаунта получен и добавлен в скрипт
4. Конечный мейл рекламного кабинета добавлен в скрипт
5. ID цели выставлен
6. Даты выставлены

7. Если с данными все ок - в конце скрипта добавить строчку df.to_excel('test.xlsx') и выгрузить все в Excel файл

Если вы сделали все по инструкции - поздравляю, вы умеете отправлять запросы и частично автоматизировать вашу отчетность.
Полученные данные вы можете отправить в Google Sheets или SQL и построить дашборд.

На этом все, спасибо, что дочитали! :)

Подписывайтесь на мой тг-канал, ссылка в шапке.

Если вам нужна помощь с аналитическими задачами, пишите мне в ТГ: @tonyornot

Полная версия кода по ссылке:

0
5 комментариев
Konstantin Chelovechkov

Антон, спасибо за статью. Попробовал по вашему мануалу создать скрипт.
Столкнулся с тем что в анаконде нет библиотек Time и Sys. Они как-то отдельно ставятся?

Ответить
Развернуть ветку
Александр Белов

import sys
import time

Ответить
Развернуть ветку
Антон Перепечаев
Автор

Они входят в стандартный функционал пайтона, достаточно их просто импортировать.

Если у вас возникнут трудности, то у меня есть готовое решение- полный пошаговый курс по автоматизации отчетности из директа.

Подробно описал в посте:
https://t.me/poschitai/13

Ответить
Развернуть ветку
Ярослав Добрынин

Добрый вечер, соорудил себе скрипт по вашей схеме, чтобы передать данные из директа в гугл аналитикс. Но есть проблема, что в запросе к директу не могу найти нигде utm-меток, по которым собственном данные и должны совмещаться с гугл аналитикс. Может вы подскажете, есть ли в принципе у яндекса в API запрос, который бы возвратил одновременно все utm-метки объявления и id рекламной кампании.

Ответить
Развернуть ветку
Антон Перепечаев
Автор

Из директа к сожалению нельзя передавать UTM, только из метрики.

Ответить
Развернуть ветку
2 комментария
Раскрывать всегда