Собираем данные по рекламным кампаниям ВКонтакте
⏱ Время чтения текста – 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 добавляем таблицу с данными из ВКонтакте, объединяя по полю даты. Отсеиваем данные — возьмём день последней рекламной кампании и посмотрим на несколько дней после него. На выходе получим таблицу такого вида:

Итоги
Получилась таблица, сообщающая, сколько всего было затрачено на объявления в этот день, сколько человек его посмотрели, зашли к нам на сайт и стали нашими новыми пользователями.
Привет, при запросе 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
Спасибо за уточнение!
Доброй ночи)
Сделал всё, как вы написали, в результате получил ответ:
{’error’: {’error_code’: 600,
’error_msg’: ’Permission denied. You have no access to operations specified with given object(s): account_id is invalid’,
На стороне рекламного кабинета нужно что-то сделать?
Сложно сказать, судя по ошибке неверно указан account_id. Вы точно его корректно задали?
Да нет, я перепроверял. ID-шник рекламного кабинета точно тот. А если мой личный id под которым я захожу на страницу разработчиков и регистрирую приложение, не совпадает с id пользователя, создавшего рекламный кабинет, не надо ничего дополнительно делать?
Причём на такой запрос всё работает зашибись: rs = requests.get(’https://api.vk.com/method/users.get?user_ids=210700285&fields=bdate&access_token=token&v=5.131') при том же токене
Все протестировали на нескольких аккаунтах, код работает. Нужно добавить пользователя, который запрашивает данные в администраторы того рекламного аккаунта, к которому обращаетесь.
Доброго дня, спасибо за ваш труд, очень познавательно!
У меня возникла проблема при использовании описанного метода на реальном кейсе:
из 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’}]}]}
Полагаю, если бы разрабы предусмотрели пустые значения по умолчанию, то можно было бы собрать дф хотя бы с пропущенными значениями.
Может, подскажете вариант решения?
Думаю, что лучше этот вопрос обратить в саппорт VK, контекст не понятен и не очевиден.
Столкнулся с проблемой, схожей с той, о которой пишет Сергей Макогон. Опытным путем удалось выяснить, что в цикле, который делает запрос методом ads.getStatistics при большом количестве кампаний приходят ошибки 6 и 9, которые говорят о том, что запросы отправляются слишком часто. Помогло установить в цикл функцию time.sleep().
Всем привет, всё работает супер, но ровно до того момента, где мы выводим датафрейм — посмотрел причины ошибок, и понял, что в моих РК видимо есть креативы, которые используются в нескольких кампаниях.
Как я к этому пришёл?
Задал параметры для запроса:
’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 — видно, что показы, клики и траты при любых выборках в датах показываю разные данные, как такое может происходить, подскажите, пожалуйста)
Если нужно запустить скрипт не с локального компьютера, а передать токен разработчикам, то для того чтобы токен от ВК был рабочим и с доступом необходимо добавить
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
У меня произошла такая ошибка: ValueError: Length of values (41) does not match length of index (24). Подскажите, в чём может быть причина?