Собираем данные по рекламным кампаниям ВКонтакте

Время чтения текста – 14 минут

В пятничном лонгриде проделаем большую работу: возьмём информацию по рекламным кампаниям ВКонтакте и сопоставим их с данными Google Analytics в Redash. Чтобы снова не поднимать сервер, будем передавать данные через Google Docs, используя Spreadsheet API.

Получение access token
Для получение пользовательского ключа ВКонтакте нужно создать приложение. Идём в раздел «Разработчики» по https://vk.com/apps?act=manage, жмём на кнопку «Создать приложение». В поле «Тип приложения» выбираем «Standalone-приложение» и даём любое название. После этого в меню слева идём в настройки и сохраняем себе ID приложения.

Актуальную информацию о ключах можно посмотреть в статье «Получение ключа доступа»

Теперь копируем себе эту ссылку:

https://oauth.vk.com/authorize?client_id=YourClientID&scope=ads&response_type=token

Но вместо YourClientID вставляем ID своего созданного приложения. В scope у этой ссылки только ads, так что с этим ключом можно будет получать только информацию о рекламном кабинете. Вставляем её в браузер и нас скидывает на другую страницу — в адресе этой странице будет указан ваш сгенерированный access token.

Срок жизни токена — 86400 секунд: ровно сутки. Чтобы получить токен без временных ограничений можно добавить в scope параметр offline. Если токен понадобилось отозвать — смените пароль от страницы или в настройках безопасности завершите активные сессии.

Ещё для запросов к API нам пригодится ID рекламного кабинета — проходим по https://vk.com/ads?act=settings и копируем «номер кабинета».

Сбор данных через запросы к API
Напишем скрипт, который обращается к серверу ВКонтакте с нашим access token и номером рекламного кабинета и берёт информацию о всех кампаниях пользователя: количество просмотров на рекламах, кликов и затрат. Затем скрипт будет формировать из него DataFrame и отправлять в Google Docs.

from oauth2client.service_account import ServiceAccountCredentials
from pandas import DataFrame
import requests
import gspread
import time

Зададим несколько константных значений: access token, ID рекламного кабинета и версию API ВКонтакте, которую будем использовать. Актуальной является версия 5.103.

token = 'fa258683fd418fafcab1fb1d41da4ec6cc62f60e152a63140c130a730829b1e0bc'
version = 5.103
id_rk = 123456789

За получение статистики по рекламе отвечает метод ads.getStatistics, но один из обязательных параметров при его вызове — ’ids’, ID рекламного объявления, статистику по которому мы хотим получить. Так как ID у нас пока нет, придётся сначала воспользоваться методов ads.getAds, который возвращает ID объявлений и кампаний.

Подробнее со всеми методами ВКонтакте API можно ознакомиться в документации

Библиотекой requests отправляем запрос к серверу и передаём свои параметры. Полученный ответ сразу переведём в формат json


campaign_ids = []
ads_ids = []
r = requests.get('https://api.vk.com/method/ads.getAds', params={
    'access_token': token,
    'v': version,
    'account_id': id_rk
})
data = r.json()['response']

Вот, как выглядит объект data: нам вернулся обычный список словарей, с которым мы уже имели дело в материале “Передаём и анализируем собранные данные по рекламным капманиям в Redash”.

Заполняем словарь ad_campaign_dict. Ключом будет ID объявления, а значением — ID кампании, к которой принадлежит объявление. Так будет удобнее присваивать к объявлению ID кампании, к которой оно принадлежало.

ad_campaign_dict = {}
for i in range(len(data)):
    ad_campaign_dict[data[i]['id']] = data[i]['campaign_id']

Теперь, имея ID каждого нужного объявления, можно обратиться к методу ads.getStatistics. Мы будем собирать количество просмотров, кликов, затрат и даты начала и конца объявления, поэтому заблаговременно заведём пустые списки.

ads_campaign_list = []
ads_id_list = []
ads_impressions_list = []
ads_clicks_list = []
ads_spent_list = []
ads_day_start_list = []
ads_day_end_list = []

Вызывать getStatistics нужно отдельно для каждого объявления — будем делать это в итераторе по ad_campaign_dict. Отправляем запрос, передавая в ‘period’ значение ‘overall’ — берём данные за всё время. У некоторых объявлений могут отсутствовать данные по полю «Просмотры» или «Клики» если они не были запущены, и, потребовав их, мы словим KeyError — во избежание этого добавим обработчик try — except, который заставит скрипт не обращать внимания на эту ошибку.

for ad_id in ad_campaign_dict:
        r = requests.get('https://api.vk.com/method/ads.getStatistics', params={
            'access_token': token,
            'v': version,
            'account_id': id_rk,
            'ids_type': 'ad',
            'ids': ad_id,
            'period': 'overall',
            'date_from': '0',
            'date_to': '0'
        })
        try:
            data_stats = r.json()['response']
            for i in range(len(data_stats)):
                for j in range(len(data_stats[i]['stats'])):
                    ads_impressions_list.append(data_stats[i]['stats'][j]['impressions'])
                    ads_clicks_list.append(data_stats[i]['stats'][j]['clicks'])
                    ads_spent_list.append(data_stats[i]['stats'][j]['spent'])
                    ads_day_start_list.append(data_stats[i]['stats'][j]['day_from'])
                    ads_day_end_list.append(data_stats[i]['stats'][j]['day_to'])
                    ads_id_list.append(data_stats[i]['id'])
                    ads_campaign_list.append(ad_campaign_dict[ad_id])
        except KeyError:
            continue

Теперь сформируем из списков DataFrame и выведем первые 5 элементов:

df = DataFrame()
df['campaign_id'] = ads_campaign_list
df['ad_id'] = ads_id_list
df['impressions'] = ads_impressions_list
df['clicks'] = ads_clicks_list
df['spent'] = ads_spent_list
df['day_start'] = ads_day_start_list
df['day_end'] = ads_day_end_list
print(df.head())

Экспорт данных в Google Docs
Для экспорта DataFrame в таблицу Google Sheets необходим ключ доступа Google API. Пройдём по https://console.developers.google.com и создадим новый проект. Даём ему любое имя и в Dashboard жмём на кнопку “Подключить API и сервисы”. Нужно включить два API — Google Drive API и Google Sheets API. Ищем первый в поиске, нажимаем на “Включить API”, затем ищем второй и проделываем то же самое.

После включения нас отправят на панель управления API. Жмём на «Создать учётные данные» — по ним будем проводить авторизацию в скрипте. Отмечаем, что используем Google Sheets API из веб-сервера и обращаемся к данным пользователя. Нажимаем на «Выбрать тип учётных данных» и создаем сервисный аккаунт. В поле «Роль» выбираем Проект — Редактор, а тип ключа оставим JSON.

После этого нам отправят файл в формате JSON с нашими учетными данными — назовём его «credentials.json» — и перенаправят на страницу с сервисными аккаунтами. Ниже будет поле с почтой — копируем её себе.

Переходим по https://docs.google.com/spreadsheets и создаем пустой файл с названием data, в который будут отправляться данные из DataFrame. В настройках доступа даём доступ по почте, скопированной ранее из сервисных аккаунтов — от неё будут приходить данные из скрипта.

Закинем файл credentials.json в директорию со скриптом и продолжим писать код. Перечисляем область видимости в виде ссылок:

scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']

И при помощи библиотек oauth2client и gspread проводим авторизацию методами ServiceAccountCredentials.from_json_keyfile_name и gspread.authorize, указывая в параметрах первого наш файл и переменную scope. Через переменную sheet будем обращаться к нашему файлу в Google Docs.

creds = ServiceAccountCredentials.from_json_keyfile_name('credentials.json', scope)
client = gspread.authorize(creds)
sheet = client.open('data').sheet1

Для ввода значений в ячейку таблички есть метод update_cell. Важно: нумерация индексов ячеек при обращении начинается не с нуля, а с единицы. Первым циклом пройдём по первой строке и перенесем туда заголовки нашего DataFrame. Во втором будем идти по каждой ячейке и вставлять соответствующие значения DataFrame. По умолчанию стоит ограничение — 100 запросов в 100 секунд. Это ограничение может остановить наш скрипт на полпути: чтобы избежать ошибки пропишем time.sleep, чтобы после каждой вставки скрипт секунду выжидал.

count_of_rows = len(df)
count_of_columns = len(df.columns)
for i in range(count_of_columns):
    sheet.update_cell(1, i + 1, list(df.columns)[i])
for i in range(1, count_of_rows + 1):
    for j in range(count_of_columns):
        sheet.update_cell(i + 1, j + 1, str(df.iloc[i, j]))
        time.sleep(1)

Если всё сделаем правильно — получим таблицу такого вида:

Экспорт данных в Redash

Подключение Google Analytics к Redash описано в статье «Как подключить Google Analytics как Redash?».

Имея в Redash таблицу с Google Analytics и рекламным кампаниям ВКонтакте, можем сопоставить их друг другу. Напишем такой запрос:

SELECT
    query_50.day_start,
    CASE WHEN ga_source LIKE '%vk%' THEN 'vk.com' END AS source,
    query_50.spent,
    query_50.impressions,
    query_50.clicks,
    SUM(query_49.ga_sessions) AS sessions,
    SUM(query_49.ga_newUsers) AS users
FROM query_49
JOIN query_50
ON query_49.ga_date = query_50.day_start
WHERE query_49.ga_source LIKE '%vk%' AND DATE(query_49.ga_date) BETWEEN '2020-05-16' AND '2020-05-20'
GROUP BY query_49.ga_date, source

ga_source — источник, с которого человек пришел на сайт. Всё, что похоже на vk оператором CASE объединяем в столбец «vk.com». Оператором JOIN добавляем таблицу с данными из ВКонтакте, объединяя по полю даты. Отсеиваем данные — возьмём день последней рекламной кампании и посмотрим на несколько дней после него. На выходе получим таблицу такого вида:

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

Поделиться
Отправить
Запинить
9 комментариев
Сергей Грушевский 2021

Привет, при запросе ads.getAds для списка рекламных компаний, столкнулся с проблемой того где взять client_id, так как нас обслуживает агенство.
r = requests.get(’https://api.vk.com/method/ads.getAds', params={
’access_token’: token,
’v’: version,
’client_id’ : client_id,  — Вот этот дополнительный параметр для передачи
’account_id’: id_rk
})
data = r.json()[’response’]

Чтобы его узнать не нужно никуда писать/звонить. Просто зайдите в агентский кабинет РК ВК, далее выберете вашу рекламную компанию (столбец «Клиенты»), и вот когда будет список уже рекламных компаний из адресной строки возьмите ID, это и есть client_id

Николай Валиотти 2021

Спасибо за уточнение!

Александр 2021

Доброй ночи)
Сделал всё, как вы написали, в результате получил ответ:
{’error’: {’error_code’: 600,
’error_msg’: ’Permission denied. You have no access to operations specified with given object(s): account_id is invalid’,
На стороне рекламного кабинета нужно что-то сделать?

Николай Валиотти 2021

Сложно сказать, судя по ошибке неверно указан account_id. Вы точно его корректно задали?

Александр 2021

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

Александр 2021

Причём на такой запрос всё работает зашибись: rs = requests.get(’https://api.vk.com/method/users.get?user_ids=210700285&fields=bdate&access_token=token&v=5.131') при том же токене

Николай Валиотти 2021

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

Сергей Макогон 2022

Доброго дня, спасибо за ваш труд, очень познавательно!
У меня возникла проблема при использовании описанного метода на реальном кейсе:
из 58 объявлений в дф попадают только 12, остальные — мимо.
Причина: try — except, хотя без него вообще никак...
Много объявлений не содержат всех значений stats, что исключает возможность их выборки.
Для примера, мои объявления бывают такими:
{’response’: [{’id’: 114211837,
’type’: ’ad’,
’stats’: [{’overall’: 1,
’day_from’: ’2022-04-04’,
’day_to’: ’2022-04-11’,
’ctr’: ’0.000’}]}]}
Полагаю, если бы разрабы предусмотрели пустые значения по умолчанию, то можно было бы собрать дф хотя бы с пропущенными значениями.
Может, подскажете вариант решения?

Николай Валиотти 2022

Думаю, что лучше этот вопрос обратить в саппорт VK, контекст не понятен и не очевиден.

Николай Копылов 2022

Столкнулся с проблемой, схожей с той, о которой пишет Сергей Макогон. Опытным путем удалось выяснить, что в цикле, который делает запрос методом ads.getStatistics при большом количестве кампаний приходят ошибки 6 и 9, которые говорят о том, что запросы отправляются слишком часто. Помогло установить в цикл функцию time.sleep().

Егор Лукьянов 1 год

Всем привет, всё работает супер, но ровно до того момента, где мы выводим датафрейм — посмотрел причины ошибок, и понял, что в моих РК видимо есть креативы, которые используются в нескольких кампаниях.

Как я к этому пришёл?

Задал параметры для запроса:

’period’: ’month’,
’date_from’: ’2023-02-01’,
’date_to’: ’2023-02-28’

И посмотрел на длину списков, которые возвращаются после цикла for:

print(len(ads_campaign_list)) ## 0
print(len(ads_id_list)) ## 0
print(len(ads_impressions_list)) ## 34
print(len(ads_clicks_list)) ## 33
print(len(ads_spent_list)) ## 33
print(len(ads_day_start_list)) ## 0
print(len(ads_day_end_list)) ## 0

Если выставить overall, и, само собой, нули в даты, то получится 249,249,299,282,282,249,249 — видно, что показы, клики и траты при любых выборках в датах показываю разные данные, как такое может происходить, подскажите, пожалуйста)

Антон Миславский | Программист 5 мес

Если нужно запустить скрипт не с локального компьютера, а передать токен разработчикам, то для того чтобы токен от ВК был рабочим и с доступом необходимо добавить
scope=ads%20offine
redirect_uri=https://oauth.vk.com/blank.html

иначе будет ’User authorization failed: access_token was given to another ip address’

вот моя строка
https://oauth.vk.com/authorize?client_id=___ваш_id____&display=page&response_type=token&scope=offline ads&v=5.131&redirect_uri=https://oauth.vk.com/blank.html

Вася Пупкин 5 мес

У меня произошла такая ошибка: ValueError: Length of values (41) does not match length of index (24). Подскажите, в чём может быть причина?

Популярное