Как автоматизировать работу PPC-специалиста?

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

Главными помощниками в этом деле станут два скрипта Google Ads. Первый скрипт позволяет подставлять стоимость товара в объявление без последующей модерации. Второй — останавливает показ объявлений товаров, которых нет в наличии. Согласитесь, ведь это здорово!

С помощью скриптов мы не только экономим свое время, но еще и избавляемся от простоя рекламы, после внесенных руками изменений в объявления.

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

Основные функции скриптов:

  • парсинг цены товара на сайте и подстановка ее в объявление;
  • парсинг любого другого текста на страничке сайта и подстановка его в объявление;
  • парсинг наличия товара и отключение объявлений, когда товары отсутствуют в продаже.

Сайты, для которых подойдет данный пакет скриптов:

  • на странице товара указана его цена
  • на странице товара указано наличие товара

Что необходимо сделать?

1. Копируем скрипт подмены цены:

var URL_LEVEL = 'Ad'; // Ad or Keyword
var ONLY_ACTIVE = false; // set to false for all ads or keywords
var CAMPAIGN_LABEL = ''; // set this if you want to only check campaigns with this label
var STRIP_QUERY_STRING = true; // set this to false if the stuff that comes after the question mark is important
var WRAPPED_URLS = true; // set this to true if you use a 3rd party like Marin or Kenshoo for managing you account
// This is the specific text to search for 
// on the page that indicates the item 
// is out of stock.
var LABEL_NAMES = ['"Price_Change"'];
var OUT_OF_STOCK_TEXT = 'Осталось 0 товаров';
var PRICE_TEXT_BEGIN = '';
var PRICE_TEXT_END = '';
var keywords = {};
 
function setKeywordPrice(keyword, price) {
  var keywordId = keyword.getId();
  if (keywords[keywordId]) {} else {
    Logger.log('Keyword: '+keyword+', Price: '+price);
    keyword.setAdParam(1, price);
    keywords[keywordId] = true;
  }
}

function setAdPrice(ad, price) {
  var keywords = ad.getAdGroup().keywords().get();
  while(keywords.hasNext()) {
    var keyword = keywords.next();
    setKeywordPrice(keyword, price);
    //Logger.log('Ad: '+ad+'; Price: '+price+'; Keyword: '+keyword);
  }
}

function main() {
  var alreadyCheckedUrls = {};
  var prices = {};
  var campIter = AdWordsApp.campaigns().get();
  while (campIter.hasNext()) {
    var camp = campIter.next();
  var adIter = buildSelector(camp, 'Ad');
  adIter = adIter.withCondition('LabelNames CONTAINS_ANY [' + LABEL_NAMES.join(',') + ']');
  adIter = adIter.get();
  //Logger.log(iter.totalNumEntities());
  while(adIter.hasNext()) {
      var entity = adIter.next();
      var url = entity.urls().getFinalUrl();
      if (url === null)
          continue;
      url = cleanUrl(url);
      if (prices[url]) {
        setAdPrice(entity, prices[url]);
        //Logger.log('Url: '+url+'; Price: '+prices[url]+'; Entity: '+entity);
      } else {
        var htmlCode;
        try {
          htmlCode = UrlFetchApp.fetch(url).getContentText();
        } catch(e) {
          Logger.log('There was an issue checking:'+url+', Skipping.');
          continue;
        }
        var priceStart = htmlCode.indexOf(PRICE_TEXT_BEGIN) + PRICE_TEXT_BEGIN.length;
        if(priceStart >= 0) {
          var priceEnd = htmlCode.indexOf(PRICE_TEXT_END, priceStart);
          prices[url] = htmlCode.substr(priceStart, priceEnd - priceStart).replace(/\D/, '');
          //setKeywordPrice(keyword, prices[url]);
          setAdPrice(entity, prices[url]);
          //Logger.log('Url: '+url+'; Price: '+prices[url]+'; Entity: '+entity);
        }
      }
      //Logger.log('Url: '+url+' price is '+prices[url]);
      
      if(alreadyCheckedUrls[url]) {
        if(alreadyCheckedUrls[url] === 'out of stock') {
          entity.pause();
          //keyword.pause();
        } else {
          entity.enable();
          //keyword.enable();
        }
      } else {
        var htmlCode;
        try {
          htmlCode = UrlFetchApp.fetch(url).getContentText();
        } catch(e) {
          Logger.log('There was an issue checking:'+url+', Skipping.');
          continue;
        }
        if(htmlCode.indexOf(OUT_OF_STOCK_TEXT) >= 0) {
          alreadyCheckedUrls[url] = 'out of stock';
          entity.pause();
        } else {
          alreadyCheckedUrls[url] = 'in stock';
          entity.enable();
        }
        Logger.log('Url: '+url+' is '+alreadyCheckedUrls[url]+'; price: '+prices[url]);
      }
  }
  }
}
 
function cleanUrl(url) {
  if(WRAPPED_URLS) {
    url = url.substr(url.lastIndexOf('http'));
    if(decodeURIComponent(url) !== url) {
      url = decodeURIComponent(url);
    }
  }
  if(STRIP_QUERY_STRING) {
    if(url.indexOf('?')>=0) {
      url = url.split('?')[0];
    }
  }
  if(url.indexOf('{') >= 0) {
    //Let's remove the value track parameters
    url = url.replace(/\{[0-9a-zA-Z]+\}/g,'');
  }
  return url;
}
 
function buildSelector(camp, url_level) {
  var selector = (url_level === 'Ad') ? camp.ads() : camp.keywords();
  return selector;
}

В объявление добавляется динамический параметр: {param1:230} — где 230 любое число, если используется 4х значное, то указать 4х значное.

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

2. Копируем скрипт проверки наличия товаров:

/************************************
* Item Out Of Stock Checker
* Version 1.1
* ChangeLog v1.1 - Filtered out deleted Campaigns and AdGroups
* Created By: Russ Savage
* FreeAdWordsScripts.com
***********************************/
var URL_LEVEL = 'Ad'; // or Keyword
var ONLY_ACTIVE = false; // set to false for all ads or keywords
var CAMPAIGN_LABEL = 'CampaignLabel'; // set this if you want to only check campaigns with this label
var STRIP_QUERY_STRING = true; // set this to false if the stuff that comes after the question mark is important
var WRAPPED_URLS = true; // set this to true if you use a 3rd party like Marin or Kenshoo for managing you account
// This is the specific text to search for 
// on the page that indicates the item 
// is out of stock.
var OUT_OF_STOCK_TEXT = 'availability gray';
 
function main() {
  var alreadyCheckedUrls = {};
  var iter = buildSelector().get();
  while(iter.hasNext()) {
    var entity = iter.next();
	if(entity.urls().getFinalUrl()){
		var url = cleanUrl(entity.urls().getFinalUrl(), entity);
		if(alreadyCheckedUrls[url]) {
		  if(alreadyCheckedUrls[url] === 'out of stock') {
			entity.pause();
		  } else {
			entity.enable();
		  }
		} else {
		  var htmlCode;
		  try {
			htmlCode = UrlFetchApp.fetch(url).getContentText();
		  } catch(e) {
			Logger.log('There was an issue checking:'+url+', Skipping.');
			continue;
		  }
		  if(htmlCode.indexOf(OUT_OF_STOCK_TEXT) >= 0) {
			alreadyCheckedUrls[url] = 'out of stock';
			entity.pause();
		  } else {
			alreadyCheckedUrls[url] = 'in stock';
			entity.enable();
		  }
		}
		Logger.log('Url: '+url+' is '+alreadyCheckedUrls[url]);
	}
  }
}
 
function cleanUrl(url, entity) {
  if (url) {
	  if(WRAPPED_URLS) {
		url = url.substr(url.lastIndexOf('http'));
		if(decodeURIComponent(url) !== url) {
		  url = decodeURIComponent(url);
		}
	  }
	  if(STRIP_QUERY_STRING) {
		if(url.indexOf('?')>=0) {
		  url = url.split('?')[0];
		}
	  }
	  if(url.indexOf('{') >= 0) {
		//Let's remove the value track parameters
		url = url.replace(/\{[0-9a-zA-Z]+\}/g,'');
	  }
	}
	else Logger.log('Что-то пошло не так, в урле нулл: '+url+' '+ entity.getCampaign.getName+ ' -> ' +entity.getAdGroup.getName);
  return url;
}
 
function buildSelector() {
  //var selector = (URL_LEVEL === 'Keyword') ? AdWordsApp.ads() : AdWordsApp.keywords();
  var selector = (URL_LEVEL === 'Keyword') ? AdWordsApp.keywords() : AdWordsApp.ads();
  selector = selector.withCondition('CampaignStatus != DELETED').withCondition('AdGroupStatus != DELETED');
  if(ONLY_ACTIVE) {
    selector = selector.withCondition('CampaignStatus = ENABLED').withCondition('Status = ENABLED');
    if(URL_LEVEL !== 'Keyword') {
      selector = selector.withCondition('AdGroupStatus = ENABLED');
    }
  }
  if(CAMPAIGN_LABEL) {
    var label = AdWordsApp.labels().withCondition("Name = '"+CAMPAIGN_LABEL+"'").get().next();
    var campIter = label.campaigns().get();
    var campaignNames = [];
    while(campIter.hasNext()) {
      campaignNames.push(campIter.next().getName());
    }
    selector = selector.withCondition("CampaignName IN ['"+campaignNames.join("','")+"']");
  }
  return selector;
}

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

Название ярлыка можно увидеть на следующем скриншоте. Его можно заменить на свое.

4. Меняем заголовки или описания в объявлениях, которые мы пометили ранее ярлыком, путем добавления динамической вставки в текст {param1:230}. Это будет наша переменная, которая будет записываться в массив данных Google Ads, а далее —  подставляться из массива в текст.

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

Важно! В объявлениях этого не видно, так как цена будет подтягиваться из массива данных внутри Google Ads и увидеть ее можно будет только в рекламной выдаче.

5. Добавляем ранее скачанный скрипт в библиотеку Google Ads. Просто создаете новый скрипт и полностью вставляете скопированный код в поле.

6. Определяем код, который включает в себя цену на сайте. Для этого нам необходимо открыть страничку товара на сайте, с которой мы будем парсить цену. Найти на странице цену и щелкнуть по ней правой кнопкой мыши, где выбрать «Просмотреть код элемента».

7. Находим этот код и указываем для скрипта обе его части, между которыми и находится наша цена. В нашем случае код выглядит так:

<span data-el_name=»default_price»>3215 </span>.

Определяем начало кода — <span datael_namedefault_price«> И конец — </span>.

Важно! Внутри кода могут находиться не только цифры, а и текст вроде «всего», «от», «грн», «usd» и др. Различные символы и пробелы, которые находятся до и после цены, учитываются при ее определении. Поэтому обязательно указывайте их в скрипте, чтобы ничего лишнего, кроме цены товара, не вытянулось в наш параметр. После того, как мы определили Начало и Конец цены, добавляем эти части кода в скрипт.

8. Нажимаем сохранить скрипт. Затем нажимем кнопку «Просмотр» в правой нижней части экрана.

Если все сделано верно, то на вкладке «Журнал» можно будет наблюдать записи по полученным ценам для различных объявлений и ключевых слов.

После того, как все предыдущие операции были сделаны и отработали без ошибок, нажимаем кнопку «Сохранить».

Результаты работы подмены цены можно увидеть, если ввести целевой запрос товара в поиске. С помощью таких объявлений, перед кликом на рекламу мы даем представление о цене потенциальному клиенту. Таким образом мы получаем более горячие переходы по рекламе и исключаем в клики пользователей, для которых данные товары слишком дорогие.

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

Для это переходим в список скриптов аккаунта, для которого мы устанавливали наш скрипт, находим его из списка (если до этого у нас уже есть в аккаунте установленные ранее скрипты) и нажимаем на появившуюся кнопку редактирования в столбце «Частота». В появившемся окошке выбираем частоту и время срабатывания для нашего скрипта.

Далее настроим скрипт, который будет отключать объявления, естли товара нет в наличии на сайте.

Установка практически идентичная предудущему скрипту. Первым делом, помечаем ярлыком кампании, в которых мы рекламируем наши товары и будем проверять их наличие:

В нашем случае ярлык будет с названием «Test_In_Stock». По желанию название этого ярлыка можно изменить. В этом случае не забудьте поменять его и в самом скрипте.

Далее переходим на страничку с нашим товаром, которого нет в наличии. Находим на странице код элемента, который отвечает за наличие товара. В нашем случае товары, которые отсутствуют были без пометки «Нет в наличии».  Вместо него на странице был кусок кода, который отвечал за количество оставшегося товара «Осталось 0 товаров». Этот код, в итоге, мы использовали для определения таких продуктов.

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

После этого, даем название своему скрипту и запускаем его предпросмотр:

Если все сделано верно, то на вкладке «Журнал» можно будет наблюдать записи по проверенным объявлениям:

После успешной установки, нажимаем на кнопку «Сохранить».

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

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

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

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