Pull to refresh

Получение рекламных кампаний Яндекс Директ с помощью API в DataFrame (Python)

Reading time 7 min
Views 12K
Работая сразу с несколькими клиентами, появляется необходимость оперативно анализировать много информации в разных аккаунтах и отчетах. Когда клиентов становится больше 10, маркетолог больше не успевает постоянно следить за статистикой. Но выход есть.

В данной статье я расскажу про то, как следить за рекламными аккаунтами с помощью API и Python.

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

Для этого нам нужно:

  1. Получить токен API Яндекс Директ
  2. Написать запрос к серверу
  3. Импортировать данные в DataFrame

Импорт библиотек


Необходимо импортировать те библиотеки, которые используются в запросе, а также «pandas» и «DataFrame».

Весь импорт будет выглядеть так:

import requests
from requests.exceptions import ConnectionError
from time import sleep
import json
import pandas as pd
import numpy as np
from pandas import Series,DataFrame

Получение токена


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

(Инструкция по получению токена)

Пишем запрос к серверу API Яндекс Директ


Копируем запрос из документации API

Изменим запрос.

  • Прописываем свои токен и логин

Токен.

token = ‘blaBlaBLAblaBLABLABLAblabla’

Логин.

clientLogin = 'e-66666666'

  • Подстраиваем тело запроса под себя.

Из этого

body = {
    "params": {
        "SelectionCriteria": {
            "DateFrom": "НАЧАЛЬНАЯ_ДАТА",
            "DateTo": "КОНЕЧНАЯ_ДАТА"
        },
        "FieldNames": [
            "Date",
            "CampaignName",
            "LocationOfPresenceName",
            "Impressions",
            "Clicks",
            "Cost"
        ],
        "ReportName": u("НАЗВАНИЕ_ОТЧЕТА"),
        "ReportType": "CAMPAIGN_PERFORMANCE_REPORT",
        "DateRangeType": "CUSTOM_DATE",
        "Format": "TSV",
        "IncludeVAT": "NO",
        "IncludeDiscount": "NO"

Делаем это

 body = {
    "params": {
        "SelectionCriteria": {
            "Filter": [
                {
                    "Field": "Clicks",
                    "Operator": "GREATER_THAN",
                    "Values": [
                        "0"
                    ]
                },

            ]
        },
        "FieldNames": [
            "CampaignName",
            "Impressions",
            "Clicks",
            "Ctr",
            "Cost",
            "AvgCpc",
            "BounceRate",
            "AvgPageviews",
            "ConversionRate",
            "CostPerConversion",
            "Conversions"
        ],
        "ReportName": u("Report4"),
        "ReportType": « ",
        "DateRangeType": "LAST_5_DAYS",
        "Format": "TSV",
        "IncludeVAT": "NO",
        "IncludeDiscount": "NO"
    }
} 

В SelectionCriteria пишем, как будем отбирать данные. Стандартно там пишется 2 даты, но, чтобы не нужно было их постоянно менять, заменим отрезок времени на «Последние 5 дней».

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

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

ReportType. В данном поле пишется тип отчета, для кампаний нужен именно этот отчет.

У вас должно получиться что-то подобное.

image

5. Импортируем данные в DataFrame.

(DataFrame, вероятно, самый подходящий способ для работы с этими данными.)

Я смог реализовать эту функцию с помощью записи и чтения csv файла.
Находим в запросе кусок, который отвечает за вывод статистики — это «req.text».

Удаляем стандартный вывод программы на запись в файл. Для этого меняем все выводы в коде 200.

 print("Отчет создан успешно")
print("RequestId: {}".format(req.headers.get("RequestId", False)))
print("Содержание отчета: \n{}».format(u(req.text))) 

На:

 format(u(req.text)) 

Теперь импортируем ответ сервера в DataFrame.

 file = open("cashe.csv", "w")
file.write(req.text)
file.close()
f = DataFrame.from_csv("cashe.csv",header=1, sep='	', index_col=0,) 

Пошагово:

  • Открываем (и автоматически создаем) файл cashe.csv для записи
  • Записываем в него ответ сервера
  • Закрываем файл
  • Открываем файл как DataFrame (указываем, название файла, в какой строке находятся заголовки таблицы, какой делитель между данными, в каком столбце индекс)

Получилось следующее:

image

Убираем ограничение на вывод столбцов:

 pd.set_option('display.max_columns', None)
pd.set_option('display.expand_frame_repr', False)
pd.set_option('max_colwidth', -1) 

Теперь показывается все:

image

Единственная проблема — денежные значение показываются не так, как хотелось. Это особенности реализации API Яндекс Директ. Нам просто нужно разделить денежные значения на 1000000.

f['Cost'] = f['Cost']/1000000
f['AvgCpc'] = f['AvgCpc']/1000000
f['CostPerConversion'] = f['CostPerConversion']/1000000 

Также предлагаю сразу сделать сортировку по количеству кликов

f=f.sort_values(by=['Clicks'], ascending=False) 

Вот у нас и получился готовый к анализу DataFrame

image

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

Спасибо за внимание.

Конечный код:
import requests
from requests.exceptions import ConnectionError
from time import sleep
import json
import pandas as pd
import numpy as np
from pandas import Series,DataFrame

pd.set_option('display.max_columns', None)
pd.set_option('display.expand_frame_repr', False)
pd.set_option('max_colwidth', -1)

# Метод для корректной обработки строк в кодировке 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

# --- Входные данные ---
# Адрес сервиса Reports для отправки JSON-запросов (регистрозависимый)
ReportsURL = 'https://api.direct.yandex.com/json/v5/reports'

# OAuth-токен пользователя, от имени которого будут выполняться запросы
token = 'тут токен'

# Логин клиента рекламного агентства
# Обязательный параметр, если запросы выполняются от имени рекламного агентства
clientLogin = 'тут логин'

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

# Создание тела запроса
body = {
        "params": {
            "SelectionCriteria": {
                "Filter": [
                    {
                        "Field": "Clicks",
                        "Operator": "GREATER_THAN",
                        "Values": [
                            "0"
                        ]
                    },

                ]
            },
            "FieldNames": [
                "CampaignName",
                "Impressions",
                "Clicks",
                "Ctr",
                "Cost",
                "AvgCpc",
                "BounceRate",
                "AvgPageviews",
                "ConversionRate",
                "CostPerConversion",
                "Conversions"
            ],
            "ReportName": u("Report4"),
            "ReportType": "CAMPAIGN_PERFORMANCE_REPORT",
            "DateRangeType": "LAST_5_DAYS",
            "Format": "TSV",
            "IncludeVAT": "NO",
            "IncludeDiscount": "NO"
        }
    }

# Кодирование тела запроса в JSON
body = json.dumps(body, indent=4)

# --- Запуск цикла для выполнения запросов ---
# Если получен 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:
            format(u(req.text))
            break
        elif req.status_code == 201:
            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 == 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

file = open("cashe.csv", "w")
file.write(req.text)
file.close()
f = DataFrame.from_csv("cashe.csv",header=1, sep='	', index_col=0,)

f['Cost'] = f['Cost']/1000000
f['AvgCpc'] = f['AvgCpc']/1000000
f['CostPerConversion'] = f['CostPerConversion']/1000000
f=f.sort_values(by=['Clicks'], ascending=False)

print(f)

Tags:
Hubs:
+9
Comments 1
Comments Comments 1

Articles