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

Хочу поделиться best-practices и дать несколько готовых наработок, которые пригодятся и маркетологам, и состоявшимся маркетинговым аналитикам.

Этот лонгрид целиком и полностью посвящён вопросам, которыми занимается web-маркетинг и основан на моем личном опыте работы с ребятами из маркетинга Лиги Ставок. (Про мобильную рекламу, mobile-marketing, как мы строили Buying Dashboard и клиентскую аналитику еще в mobio.ru я могу говорить бесконечно и, может быть, напишу отдельно.)

1) Важность разметки

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

Интернет-маркетинг работает по разным моделям (CPM, CPC для медийки, комиссия, RevShare и CPA для перформанс-маркетинга). Выделяю два основных момента: источник трафика и трекер размещения. Дьявол, как известно, кроется в деталях - очень много маленьких и важных моментов, которые бы хотелось обсудить. Но обо всем по порядку:

  1. «Стоимость» рекламного размещения.

    В качестве источника трафика могут быть как рекламные площадки (MyTarget, Google Ads, Google DV360, Facebook, разные DSPшки), так и рекламные серверы, которые трекают размещения баннерной рекламы у партнеров (показы и клики). В данной статье мы рассмотрим сбор костов с рекламного сервера Adfox — side проекта Яндекса. Можно выстроить аналогичные решения для абсолютно любого источника, но для некоторых, например, MyTarget и Facebook, вам не обойтись без вашей разработки. Из источников трафика вы сможете вытаскивать на ежедневной основе данные по показам / кликам, а также по стоимости размещения.

  2. «Выручка» с рекламы.

    В парадигме 2021 года рынок ушел от бездумного размещения рекламы, где мы просто тратим маркетинговый бюджет направо и налево, придя к прямому (CPA, RevShare) или косвенному (различные hard- и soft- KPI) оцениванию рекламных размещений. В качестве основного мерила в вебе используется Google Analytics. Там можно достаточно просто настраивать цели как на различные события, происходящие на веб-сайте (регистрация, добавление в корзину, совершение целевого события), так и на различные бихевиористические характеристики (длительность сеанса, показатель отказа, время пребывания на отдельной странице продукта). Основной задачей в построении отчетов здесь является выгрузка достижения одной или нескольких целей, на которые таргетируется закупка рекламы в разбивке по источникам трафика.

  3. Нейминг — наше все.

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

    Для получения данных о выполнении целей в рекламной кампании в Google Analytics можно использовать несколько параметров, записываемых после знака ? в ссылке.

    Это параметры:

    • Источник рекламного размещения (utm_source)

    • Название кампании (utm_campaign)

    • Тип трафика (utm_medium)

    • Размещаемый баннер (utm_content)

    • Ключевое слово (utm_term)

    Последний не особо используется в Лиге из-за запрета на рекламу букмекерских организаций в сети Гугла и Яндекса, так что в нашей компании мы остановились на первых четырех.

    Основные ошибки, которые совершаются при заведении рекламных кампаний:

    • Название источника / рекламной кампании не должно содержать пробелов, иначе URL-ссылка обрезается, и все параметры справа от нее пропадают в неизвестность.

    • Название рекламной кампании указывается по-разному для разных ссылок (например, в нашей крутейшей акции с кэшбеком) при создании кампаний можно было встретить различное написание слова кэшбек, использование или нет цифр: (cashback, kashback, cashback10, cash10)

    • Тип баннера указывался по-разному, а его размер указывался не всегда, и иногда ставился перед / после названия типа баннера (mobile_640x480, 640x480_mobile).

    • Если баннер делался под конкретное событие (финал Лиги Чемпионов УЕФА), то название события писалось в разных местах тега utm_content, а иногда кочевало в utm_campaign.

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

    Utm_source — источник трафика/партнер/паблишер, который должен выбираться из выпадающего списка для единообразности написания

    Utm_campaign — название кампании, которое мы выбираем перед ее началом и далее оно выбирается из выпадающего списка

    Utm_medium — тут все просто, мы обычно используем форматы cpm/cpc

    Utm_content — тут мы решили, что самым простым будет использовать нейминг типа

    {тип баннера}_{размер баннера}|{используемый макет}. Так мы довольно просто смогли разбивать трафик и смотреть конверсии по каналам в разбивке до баннера. 

    Следующий немаловажный шаг — настроить запись меток из трекинговой ссылки так, чтобы она соответствовала параметрам создаваемой рекламной кампании. Здесь для рекламного сервера Adfox мы можем управлять только двумя сущностями: название рекламной кампании и название баннера. Упросить задачу в некоторых случаях может создание в рекламном сервере баннеров разного размера, но ввиду особенностей работы с партнерами в Лиге Ставок и передачи им трек-ссылки в виде пикселя мы обычно делаем баннеры стандартного типа, так что пришлось креативить. Оптимально в имени кампании указывать название партнера, а в названии баннера указывать {utm_campaign} - {тип баннера из utm_content} - {размер баннера} | {спортивное событие}.

    Использование правильной разметки позволило сильно сократить время создания отчетов (до 15 минут), а маркетологам быстро и эффективно оценивать размещения на партнерских сайтах с самой максимальной разбивкой и оперативно тестировать и отключать неработающие каналы.

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

2)    Автоматизация сбора костов

Рекламный сервер Яндекса Adfox является удобным способом работы с партнерами. Он позволяет собирать данные по показам и кликам по рекламным объявлениям на основе генерируемых ссылок. При работе с партнерами и рассчёте эффективности наших медийных размещений в вебе мы обычно ориентируемся именно на данные нашего Adfox в разбивке по кампании, баннеру и дате. Сначала с аккаунта с уровнем «рекламодатель» необходимо авторизоваться в кабинете разработчика Яндекса. Далее нажать «подключить» API», выбрать из выпадающего списка Adfox. В результате вы получите токен, по которому можно обращаться к API адфокса и выгружать статистику.

            Дальнейшая логика предельно простая. Вся ее работа описана и реализована в документе.

1.     Вытаскиваем список кампаний для рекламного кабинета: 

function getCampaignList(key) {

var key = '' // INSERT YOUR API TOKEN HERE

var url =  'https://adfox.yandex.ru/api/v1?object=account&action=list&actionObject=campaign&show=common&limit=200'  

var table = []

var option = { headers : {'X-Yandex-API-Key' : key}

var request = UrlFetchApp.fetch(url, option);

//decode data    

var text = request.getContentText("windows-1251");

var cdata = text.split('<ID>')

 //parse data

// Adfox отдает нам данные, которые можно распарсить по открывающим и закрывающим

//тегам <ID>

  for(var i in cdata)if(i!=0){

  var id = cdatat[i].split('</ID>')[0]

  var cname = test[i].split('<name>')[1].split('</name>')[0]

  var row = [id,cname]

  table.push(row)

    }

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

function getBannersList(){

//get campaign info

var key = '' // INSERT YOUR API TOKEN HERE

var campdata = getCampaignList(key)

var table = [];

table.push(['Campaign ID','Campaign Name','Banner ID','Banner Name'])

 //get paginated list of banners. Пагинация тут добавлена, потому что нередко по кампании //может быть больше 100 баннеров и URL будет слишком большим

 for(var a in campdata){

 var campbanners = []

  var campid = campdata[a][0];

  var cname = campdata[a][1];

var url =

 'https://adfox.yandex.ru/api/v1?object=campaign&action=list&actionObject=banner&show=common&limit=1000&objectID='+campid

 var option = {

 headers : {  'X-Yandex-API-Key' : key  }  }

     var request = UrlFetchApp.fetch(url, option);

  //decode

     var text = request.getContentText("windows-1251");

    var test = text.split('<ID>')

  //  parse

    for(var i in test)if(i!=0){

      var bid = test[i].split('</ID>')[0];

      var bname = test[i].split('<name>')[1].split('</name>');

      campbanners.push(bid);

      table.push([campid,cname,bid,bname])

 }

//pagination    

   var a = campbanners.length

    for(var x=0; x<a; x = x + 100){

  var start = x;

  var end = x + 100;

  if(a>end)var banners = campbanners.slice(start, end);

  else{banners = campbanners.slice(x,a)};

  var banners = campbanners.slice(start, end)

  banners = '['+banners.toString()+']'

  //get taskid for the data batch

  var taskid = orderData(banners,key) //эта функция будет изложена дальше

 // Logger.log(taskid); //For testing Purposes

  Utilities.sleep(5000) //its better to have a 5 sec delay

  var data = getData(taskid,cname,bname,key); // SEE the code in utilities.gs

    }

  }

SpreadsheetApp.getActive().getSheetByName('banner list').getRange(1,1,table.length,table[0].length).setValues(table);

 

    return;

  

  addCorectBname()

  return test;

}

3.     Сама функция получения статистики по баннерам выглядит примерно так:

function orderData(banners,key,date){ //gets taskId for the batch

// По умолчанию вытаскивает данные за "вчера"

// Можно также прописать конкретные даты заменив ниже date на конкретные даты.

  var date = new Date();

  date.setHours(-20);

date = date.toISOString().substr(0,10)

var datefrom = date  //'2020-08-01';

var dateto = date   //'2020-09-27'

var url = 

"https://adfox.yandex.ru/api/report/banner?name=days&bannerId="+banners+"&dateFrom="+datefrom+"&dateTo="+dateto+"&precision=high";

var option = { headers : {  'X-Yandex-API-Key' : key  }  }

var request = UrlFetchApp.fetch(url, option);

var text = request.getContentText();

var json = JSON.parse(text);

var taskid = json['result']['taskId'];

return taskid;

}

 

// Дальше по каждому таск id получаем данные  

function getData(taskId,campname,banname,key){ //gets Report via task ID

  var url = 'https://adfox.yandex.ru/api/report/result?taskId='+taskId;

var option = { headers : { 'X-Yandex-API-Key' : key  }   }

var request = UrlFetchApp.fetch(url, option);

var text = request.getContentText();

var json = JSON.parse(text);

var table = []

var title = json['result']['fields'];

var tab = json['result']['table'];

for(var i in tab){

            var row = tab[i]

row.push(campname,banname);

            table.push(row)

             }

if(table.length > 0){var t = 

SpreadsheetApp.getActive().getSheetByName('data').getDataRange().getValues();

SpreadsheetApp.getActive().getSheetByName('data').getRange(t.length+1,1,table.length,table[0].length).setValues(table);

                      }

  

  return json;

}

Данный скрипт позволяет вытащить данные по всем рекламным размещениям Adfox. Для получения дополнительных кастомных данных можно обратиться к API документации Яндекса.

3) Автоматизация расчета прибыли

Для автоматизации расчета прибыли нужно создать на нужное событие цель в Google Analytics и назначить ему ценность (а также можно прокидывать в содержание события реальную цену события). После этого довольно удобно через API Google Analytics получить данные в нужной разбивке. Рабочий скрипт также указан в файле во вкладке GA.

Выглядит он примерно так:

function nextDay(date,loop){

 // var loop = 0 // 0 - сегодня, положительное число - дни +, отрицательное - дни-

 // var date = new Date()

  var day = 1000 * 60 * 60 * 24;

  var b = new Date(date.getTime()+loop*day)

  b = b.toISOString().substr(0,10); // - если надо выводить в формате для отчета

  return b;

}

 

function runDemo() {

  var date = new Date();

  date.setHours(12);

  for(var i=1;i<=1;i++){

  var datefrom = nextDay(date,-1*i)

  var dateto=datefrom;

  try {

      

        var firstProfile = getFirstProfile();

        var results = getReportDataForProfile(firstProfile,datefrom,dateto);

      outputToSpreadsheet(results);

  

    } 

    catch(error) {

        Browser.msgBox(error.message);

    }

  }

}
  

function getFirstProfile() {

    var accounts = Analytics.Management.Accounts.list();

    if (accounts.getItems()) {

        var firstAccountId = accounts.getItems()[0].getId();

      Logger.log(firstAccountId)

        var webProperties = Analytics.Management.Webproperties.list(firstAccountId);

        if (webProperties.getItems()) {

            var firstWebPropertyId = webProperties.getItems()[0].getId();

           Logger.log(firstWebPropertyId)

            var profiles = Analytics.Management.Profiles.list(firstAccountId, firstWebPropertyId);

            if (profiles.getItems()) {

                var firstProfile = profiles.getItems()[0];

              var tolog = firstProfile.getId()

              Logger.log(tolog)

                return firstProfile;

            } 

            else {

                throw new Error('No views (profiles) found.');

            }

        } 

        else {

            throw new Error('No webproperties found.');

        }

    } 

    else {

        throw new Error('No accounts found.');

    }

  return;

}

function getReportDataForProfile(firstProfile,datefrom,dateto) {

    var profileId = firstProfile.getId();

    var tableId = 'ga:' + profileId;

    var startDate = datefrom 

    var endDate = dateto

    var optArgs = {

      'dimensions':'ga:source,ga:campaign,ga:medium,ga:adContent,ga:date', // Comma separated list of dimensions.

      'metrics': 'ga:users,ga:sessions,ga:newUsers,ga:bounces,ga:goal10Completions',

        'segment': 'gaid::-1',  //

        'samplingLevel': 'HIGHER_PRECISION',

        'start-index': '1',

        'max-results': '1000000'

    };

  

    // Make a request to the API.

    var results = Analytics.Data.Ga.get(

        tableId,                    // Table id (format ga:xxxxxx).

        startDate,                  // Start-date (format yyyy-MM-dd).

        endDate,                    // End-date (format yyyy-MM-dd).

        'ga:sessions',//,ga:pageviews', // Comma seperated list of metrics.

        optArgs);

  

    if (results.getRows()) {

        return results;

    } 

    else {

        throw new Error('No views (profiles) found');

    }

}

function outputToSpreadsheet(results) {

  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("GA");

  var indexFrom = sheet.getDataRange().getValues().length 

  var headerNames = ['ga:source','ga:campaign','ga:medium','ga:adContent','date','ga:users','ga:sessions','ga:newUsers','ga:bounces','1 цель']

    sheet.getRange(1, 1, 1, headerNames.length).setValues([headerNames]);

    sheet.getRange(indexFrom+1, 1, results.getRows().length, headerNames.length).setValues(results.getRows());

}

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