Скрипты в Google AdWords: полезное решение для автоматизация рутинных операций
Часто при ведении большого количества рекламных компаний (РК), которые имеют множество групп объявлений, ключевых слов и «горы» отчетов, сталкиваешься с некоторыми рутинными действиями, которые отнимают довольно много сил и времени. Особенно эта проблема актуальна для Центра клиентов MMC имеющего «на борту» множество аккаунтов.
Казалось бы простые и однообразные ручные операции со временем можно выполнять по «накатанной», однако это в разы замедляет эффективность работы. Тем не менее используя скрипты AdWords можно в несколько раз повысить продуктивность работы и упростить себе жизнь.
Основные плюсы скриптов AdWords:
- Автоматическая настройка запуска определенных действий по заданному времени (например, каждый час, день или месяц).
- Легкость интеграции с сервисами Google (например, Gmail, Google Docs)
- Использование внешних ресурсов для приостановки или возобновления действия объектов. Увеличение ставок или добавление новых ключевых слов по мере расширения ресурса.
- Выполнение действий над несколькими объектами. Например, если какое-либо ключевое слово забирает почти весь ваш дневной бюджет, вы можете приостановить его, одновременно увеличив бюджет для других кейвордов.
- Использование одного скрипта для нескольких дочерних аккаунтов ММС, чтобы оптимизировать ставки, создавать отчеты.
- Отображение статистики кампании в виде таблицы, с помощью которой можно создавать отчеты и представлять данные в графической форме.
О всех «прелестях» скриптов AdWords можно говорить долго, однако советуем ознакомится c официальной справкой Google – «скрипты Adwords», там есть вся необходимая информация. Тем не менее для более наглядной картины, представляем небольшой обзор-инструкцию подключения скрипта «Битые ссылки», который позволяет отслеживать рекламные объявления на наличие ошибок 404, 500. Итак, приступим...
Чтобы подключить нужный скрипт для нескольких аккаунтов в ММС, в основном интерфейсе Центра клиентов находим основной блок с левой стороны, вкладка «Скрипты». Подключения скрипта для отдельного аккаунта осуществляется немного глубже «Массовые операции» – «Скрипты»:

Создаем для нашего аккаунта скрипт в котором будет размещаться программный код скрипта:

Пишем название, как будет именоваться наш скрипт (пример: Отчет по битым ссылкам (404,500) и вносим в текстовый редактор программный код (заменив e-mail – почта, куда будут отправляться отчеты в случае обнаружение ошибок ):

Разрешаем доступ скрипту к нашему аккаунту («Авторизация»), для обработки данных. 
Тестируем («Просмотр») и сохраняем созданный скрипт. Далее задаем частоту запуска скрипта («Расписание») и сохраняем:


Вот и все, скрипт установлен и настроен. Теперь запуск скрипта будет осуществляться ежедневно
в 11:00 по Киевскому времени, создавая отчет, который будет заносится в «Журналы» и в случае обнаружения ошибки отправлять оповещение на почту, которая была указана в скрипте:

Подключение остальных скриптов осуществляется по похожему принципу, так что проблем не должно возникнуть.
Некоторые готовые решения, которые вы можете использовать:
Битые ссылки – скрипт парсит ссылки объявлений на наличие ошибок сервера 404, 500 (часто бывает, что сайт не доступен или страница по ошибке была уделена в это время объявления транслируются и бюджеты улетают в «трубу»).
/****************************
* Find Broken Urls In Your Account
* Version 1.1
* ChangeLog v1.1
* - Updated to only see Text Ads
* Created By: Russ Savage
* FreeAdWordsScripts.com
****************************/
function main() {
// You can add more if you want: http://goo.gl/VhIX
var BAD_CODES = [404,500];
var TO = ['ваш-email@example.com'];
var SUBJECT = 'Broken Url Report - ' + _getDateString();
var HTTP_OPTIONS = {
muteHttpExceptions:true
} ;
//Let's look at ads and keywords for urls
var iters = [
//For Ad Level Urls
AdWordsApp.ads()
.withCondition("Status = 'ENABLED'")
.withCondition("AdGroupStatus = 'ENABLED'")
.withCondition("CampaignStatus = 'ENABLED'")
.withCondition("Type = 'TEXT_AD'")
.get(),
//For Keyword Level Urls
AdWordsApp.keywords()
.withCondition("Status = 'ENABLED'")
.withCondition("DestinationUrl != ''")
.withCondition("AdGroupStatus = 'ENABLED'")
.withCondition("CampaignStatus = 'ENABLED'")
.get()
];
var already_checked = { } ;
var bad_entities = [];
for(var x in iters) {
var iter = iters[x];
while(iter.hasNext()) {
var entity = iter.next();
if(entity.getDestinationUrl() == null) { continue; }
var url = entity.getDestinationUrl();
if(url.indexOf(' { ') >= 0) {
//Let's remove the value track parameters
url = url.replace(/ { [0-9a-zA-Z]+ } /g,'');
}
if(already_checked[url]) { continue; }
var response_code;
try {
Logger.log("Testing url: "+url);
response_code = UrlFetchApp.fetch(url, HTTP_OPTIONS).getResponseCode();
} catch(e) {
//Something is wrong here, we should know about it.
bad_entities.push( { e : entity, code : -1 } );
}
if(BAD_CODES.indexOf(response_code) >= 0) {
//This entity has an issue. Save it for later.
bad_entities.push( { e : entity, code : response_code } );
}
already_checked[url] = true;
}
}
var column_names = ['Type','CampaignName','AdGroupName','Id','Headline/KeywordText','ResponseCode','DestUrl'];
var attachment = column_names.join(",")+"n";
for(var i in bad_entities) {
attachment += _formatResults(bad_entities[i],",");
}
if(bad_entities.length > 0) {
var options = { attachments: [Utilities.newBlob(attachment, 'text/csv', 'bad_urls_'+_getDateString()+'.csv')] } ;
var email_body = "There are " + bad_entities.length + " urls that are broken. See attachment for details.";
for(var i in TO) {
MailApp.sendEmail(TO[i], SUBJECT, email_body, options);
}
}
}
//Formats a row of results separated by SEP
function _formatResults(entity,SEP) {
var e = entity.e;
if(typeof(e['getHeadline']) != "undefined") {
//this is an ad entity
return ["Ad",
e.getCampaign().getName(),
e.getAdGroup().getName(),
e.getId(),
e.getHeadline(),
entity.code,
e.getDestinationUrl()
].join(SEP)+"n";
} else {
// and this is a keyword
return ["Keyword",
e.getCampaign().getName(),
e.getAdGroup().getName(),
e.getId(),
e.getText(),
entity.code,
e.getDestinationUrl()
].join(SEP)+"n";
}
}
//Helper function to format todays date
function _getDateString() {
return Utilities.formatDate((new Date()), AdWordsApp.currentAccount().getTimeZone(), "yyyy-MM-dd");
}
Показатель качества аккаунта – скрипт ежедневно проверяет аккаунт и выгружает в отдельный файл показатель качества аккаунта (увеличение показателя качества аккаунта способствует уменьшению расходов и улучшению позиции объявления).
/***************************************
* Store Account Level Quality Score in Google Spreadsheet.
* Version 1.1
* ChangeLog v1.1
* - Changed ACCOUNT_NAME to SHEET_NAME and updated the default value.
* - Removed getSpreadsheet function
*
* Created By: Russ Savage
* Based on script originally found at: http://goo.gl/rTHbF
* FreeAdWordsScripts.com
*********************************/
function main() {
var SPREADSHEET_URL = "Url адрес таблицы Google Docs";
var SHEET_NAME = 'Название файла';
var today = new Date();
var date_str = [today.getFullYear(),(today.getMonth() + 1),today.getDate()].join("-");
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var qs_sheet = spreadsheet.getSheetByName(SHEET_NAME);
var kw_iter = AdWordsApp.keywords()
.withCondition("Status = ENABLED")
.forDateRange("LAST_30_DAYS")
.withCondition("Impressions > 0")
.orderBy("Impressions DESC")
.withLimit(50000)
.get();
var tot_imps_weighted_qs = 0;
var tot_imps = 0;
while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor("LAST_30_DAYS");
var imps = kw_stats.getImpressions();
var qs = kw.getQualityScore();
tot_imps_weighted_qs += (qs * imps);
tot_imps += imps;
}
var acct_qs = tot_imps_weighted_qs / tot_imps;
qs_sheet.appendRow([date_str,acct_qs]);
}
Товара нет в наличии – скрипт сравнивает наличие товара с рекламным объявлением и в случае отсутствия товара приостанавливает рекламу.
/************************************
* 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 = true; // 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 OUT_OF_STOCK_TEXT = 'The Text That Identifies An Out Of Stock Item Goes Here';
function main() {
var alreadyCheckedUrls = { } ;
var iter = buildSelector().get();
while(iter.hasNext()) {
var entity = iter.next();
var url = cleanUrl(entity.getDestinationUrl());
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]);
}
}
nfunction 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() {
var selector = (URL_LEVEL === 'Ad') ? AdWordsApp.ads() : AdWordsApp.keywords();
selector = selector.withCondition('CampaignStatus != DELETED').withCondition('AdGroupStatus != DELETED');
if(ONLY_ACTIVE) {
selector = selector.withCondition('CampaignStatus = ENABLED').withCondition('Status = ENABLED');
if(URL_LEVEL !== 'Ad') {
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;
}
Боитесь превысить месячный бюджет? – Тогда этот скрипт для Вас!
Он автоматически отключает кампании клиента, если был превышен месячный бюджет. И одна из самых простых оптимизаций – сопоставить сумму расходов по аккаунту с месячным бюджетом и при превышении заданной суммы приостановить все активные кампании до следующего месяца:
var CUTOFF_COST = 10000;
var CUTOFF_LABEL = "Total Spend cutoff";
function main() {
var label = AdWordsApp.labels().withCondition("Name='" + CUTOFF_LABEL + "'").get().next();
if (AdWordsApp.currentAccount().getStatsFor("THIS_MONTH").getCost() > CUTOFF_COST) {
var campaignIterator = label.campaigns().get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.pause();
}
}
}
Конечно, вместо 10000 Вам необходимо установить собственную допустимую сумму.
Внимание, Ahtung! Этот скрипт не поможет в отключении Видео кампаний, Торговых и экспериментальных, и вряд ли подойдет для продвинутых пользователей
Мониторим дневные расходы
В связи с тем что Гугл иногда сам решает сколько сегодня Вы должны потратить, а иногда данная сумма может превышать 100% от вашего дневного бюджета... тогда ловите следующий скрипт:
function main() {
var allowedOverdeliveryPercentage = 0.2; // set percentage as decimal, i.e. 20% should be set as 0.2
var labelName = "paused by overdelivery checker script";
AdWordsApp.createLabel(labelName, "automatic label needed to reenable campaigns");
var campaigns = AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.withCondition("Cost > 0")
.forDateRange("TODAY");
var campaignIterator = campaigns.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var campaignName = campaign.getName();
var budgetAmount = campaign.getBudget().getAmount();
var costToday = campaign.getStatsFor("TODAY").getCost();
if(costToday > budgetAmount * (1 + allowedOverdeliveryPercentage)) {
Logger.log(campaignName + " has spent " + costToday + " which is more than allowed.");
campaign.applyLabel(labelName);
campaign.pause();
} else {
Logger.log(campaignName + " has spent " + costToday + " and can continue to run.");
}
}
}
Этот скрипт мониторит дневные расходы и сопоставляет их с дневным бюджетом. Как только они превысят допустимый уровень на определённый процент, например, 20%, кампания будет автоматически приостановлена до следующего дня.
Внимание, Ahtung! Как мы знаем скрипты могут запускаться максимум каждый час, то нам следует иметь в виду, что перерасход бюджета может продолжаться в течение следующих 60 минут, поэтому указанный в скрипте лимит должен быть немного меньшим, чем нужно.
Управление объявлениями
На наше счастья написание объявления все еще требует участие ppc специалиста и мы все еще кому-то нужны ) Поэтому создание объявлений – это обычно не та задача, которую стоит автоматизировать.
При этом уже созданные объявления предполагают довольно большой объём работы по их поддержанию, включая удаление неэффективных объявлений, обновление специальных предложений и проверку на предмет наличия нерабочих целевых страниц. Все эти задачи отлично подходят для автоматизации с помощью скриптов.
Ниже – некоторые из наших любимых скриптов для поддержания хорошо оптимизированного набора объявлений.
Все мы сталкивались с большими кампаниями где бывали случаи массового отклонения объявлений. И со временем эти отклонённые объявления могут стать раздражающими, поскольку они генерируют предупреждения и загромождают интерфейс.
Чтобы нормализовать свою нервную систему от вылетающих предупреждений существует скрипт который помогает от них избавиться:
function main() {
// Let's start by getting all of the ad that are disapproved
var ad_iter = AdWordsApp.ads()
.withCondition("ApprovalStatus != APPROVED")
.get();
// Then we will go through each one
while (ad_iter.hasNext()) {
var ad = ad_iter.next();
// now we delete the ad
Logger.log("Deleteing ad: " + ad.getHeadline());
ad.remove();
}
}
Скрипт для устранения избыточного количества заглавных букв в объявлениях
Вместо удаления отклонённых объявлений вы также можете использовать скрипт, который позволяет устранить одну из частых причин их отклонения – злоупотребление заглавными буквами. Этот скрипт ищет отклонённые объявления и повторно отправляет их на проверку, уменьшив количество используемых заглавных букв.
Весьма удобно при работе с огромным массивом данных:
function main() {
var find_caps = /[A-Z] { 3, } /g;
var SEP = '~~@~~'; // this needs to be something you would never put in your ads.
var ad_iter = AdWordsApp.ads().withCondition("ApprovalStatus = 'DISAPPROVED'").get();
while(ad_iter.hasNext()) {
var ad = ad_iter.next();
var old_ad_cnt = get_ad_count(ad.getAdGroup());
var old_ad_str = [ad.getHeadline(),ad.getDescription1(),ad.getDescription2(),ad.getDisplayUrl()].join(SEP);
var new_ad_str = old_ad_str;
Logger.log("Before:"+old_ad_str);
var m = "";
while((m = find_caps.exec(new_ad_str)) != null) {
new_ad_str = replace_all(new_ad_str,m[0],init_cap(m[0]),false);
}
Logger.log("After:"+new_ad_str);
if(old_ad_str != new_ad_str) {
var [new_headline,new_desc1,new_desc2,new_disp_url] = new_ad_str.split(SEP);
ad.getAdGroup().createTextAd(new_headline, new_desc1, new_desc2, new_disp_url, ad.getDestinationUrl());
var new_ad_cnt = get_ad_count(ad.getAdGroup());
if(new_ad_cnt == (old_ad_cnt+1)) {
ad.remove();
}
} else {
Logger.log("Skipping because no changes were made.");
}
}
function init_cap(s) {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}
// This function was adapted from: http://dumpsite.com/forum/index.php?topic=4.msg8#msg8
function replace_all(original,str1, str2, ignore) {
return original.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\ { \ } \[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2);
}
function get_ad_count(ad_group) {
var ad_iter = ad_group.ads().get();
var new_ad_cnt = 0;
while(ad_iter.hasNext()) {
ad_iter.next();
new_ad_cnt++;
}
return new_ad_cnt;
}
}
Управление ставками
Управление ставками тесно связано с ежедневными рутинными задачами, статистикой и прогнозами. Поэтому эта задача как нельзя подходит для автоматизации.
Однако, иногда произвести автоматизацию не получается из-за слишком сложных настроек. Поскольку в Скриптах AdWords отсутствует дружественный к пользователям интерфейс, не все рекламодатели могут справиться с этой весьма сложной задачей.
Но умные люди придумали, а мы пользуемся: представляю вашему внимаю ряд не сложных, но эффективных скриптов для автоматизации ставок.
Скрипт управления ставками на уровне полученых показов, а также скрипт удержания
позиции
// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @name Bid To Impression Share
*
* @overview The Bid To Impression Share script adjusts your bids and allows you
* to steer ads in an advertiser account into a desired impression share in
* the search results. See
* https://developers.google.com/adwords/scripts/docs/solutions/bid-to-impression-share
* for more details.
*
* @author AdWords Scripts Team [adwords-scripts@googlegroups.com]
*
* @version 2.0.1
*
* @changelog
* - version 2.0.1
* - Fixed logic when determining which keywords to raise and lower.
* - version 2.0.0
* - Refactored to target impression share rather than position.
* - version 1.0.1
* - Refactored to improve readability. Added documentation.
* - version 1.0
* - Released initial version.
*/
// Impression share you are trying to achieve.
var TARGET_IMPRESSION_SHARE = 50;
// Once the keywords fall within TOLERANCE of TARGET_IMPRESSION_SHARE,
// their bids will no longer be adjusted.
var TOLERANCE = 5;
// How much to adjust the bids.
var BID_ADJUSTMENT_COEFFICIENT = 1.05;
/**
* Main function that lowers and raises keywords' CPC to move closer to
* target impression share.
*/
function main() {
raiseKeywordBids();
lowerKeywordBids();
}
/**
* Increases the CPC of keywords that are below the target impression share.
*/
function raiseKeywordBids() {
var keywordsToRaise = getKeywordsToRaise();
while (keywordsToRaise.hasNext()) {
var keyword = keywordsToRaise.next();
keyword.bidding().setCpc(getIncreasedCpc(keyword.bidding().getCpc()));
}
}
/**
* Decreases the CPC of keywords that are above the target impression share.
*/
function lowerKeywordBids() {
var keywordsToLower = getKeywordsToLower();
while (keywordsToLower.hasNext()) {
var keyword = keywordsToLower.next();
keyword.bidding().setCpc(getDecreasedCpc(keyword.bidding().getCpc()));
}
}
/**
* Increases a given CPC using the bid adjustment coefficient.
* @param { number } cpc - the CPC to increase
* @return { number } the new CPC
*/
function getIncreasedCpc(cpc) {
return cpc * BID_ADJUSTMENT_COEFFICIENT;
}
/**
* Decreases a given CPC using the bid adjustment coefficient.
* @param { number } cpc - the CPC to decrease
* @return { number } the new CPC
*/
function getDecreasedCpc(cpc) {
return cpc / BID_ADJUSTMENT_COEFFICIENT;
}
/**
* Gets an iterator of the keywords that need to have their CPC raised.
* @return { Iterator } an iterator of the keywords
*/
function getKeywordsToRaise() {
// Condition to raise bid: Average impression share is worse (less) than
// target - tolerance
return AdWordsApp.keywords()
.withCondition('Status = ENABLED')
.withCondition(
'SearchImpressionShare < ' + (TARGET_IMPRESSION_SHARE - TOLERANCE))
.orderBy('SearchImpressionShare ASC')
.forDateRange('LAST_7_DAYS')
.get();
}
/**
* Gets an iterator of the keywords that need to have their CPC lowered.
* @return { Iterator } an iterator of the keywords
*/
function getKeywordsToLower() {
// Conditions to lower bid: Ctr greater than 1% AND
// average impression share better (greater) than target + tolerance
return AdWordsApp.keywords()
.withCondition('Ctr > 0.01')
.withCondition(
'SearchImpressionShare > ' + (TARGET_IMPRESSION_SHARE + TOLERANCE))
.withCondition('Status = ENABLED')
.orderBy('SearchImpressionShare DESC')
.forDateRange('LAST_7_DAYS')
.get();
}
Это самый простой скрипт, который требует небольшого количества данных и времени
скрипт удержания позиции: можно скачать здесь
Скрипт для почасовой корректировки ставок здесь
Установить почасовую разбивку можно лишь с помощью автоматизации, поскольку Google ограничивает рекламодателей шестью активными временными периодами в сутки. Благодаря данному скрипту можно иметь разные ставки каждый час.
Скрипт для ставок на позицию при нахождении в заданном диапазоне CPС можно скачать здесь.
Одним из основных преимуществ скриптов является то, что всегда можно начать с какой-то простой задачи, например, с настройки правила для повышения ставок в том случае, если объявления не попадают на целевую позицию.
Помните при должной практике вы всегда можете улучшить или переделать этот так и любой другой скрипт под свои нужды.
Погодное таргетирование
Ну и для особых мисье знающих толк в извращениях и которые желают вые..выделиться есть ноухау скрипт по погодному таргетированию.
Скрипты для назначения ставок на основе погодных условий – это классический пример широких и практически безграничных возможностей . Даже если для вашего бизнеса не важна погода, этот пример иллюстрирует тот факт, что в работе скриптов могут использоваться как собственные данные компаний, так и внешние данные, взятые со сторонних источников.
Если же погода для вас важна(например служба доставки) по исследований показывают что люди предпочитают заказывать еду на дом при плохой погоде. Поэтому попробуйте поэкспериментировать с предложенным скриптом.
Скрипт, который сообщает о перерасходе бюджета по группам товаров и ключевым словам можно скачать здесь.
Этот скрипт ищет группы товаров или ключевые слова, расходы по которым превышают заданную сумму, а количество конверсий меньше ожидаемого в течение заданного периода времени.
Эту задачу также можно выполнить с помощью автоматических правил, но они могут срабатывать лишь один раз в день, что очень редко для аккаунтов с высокими расходами. Данный скрипт, в свою очередь, способен выполнять поиск по заданным критериям 24 раза в день.
Подведем итоги
Мы не можем полностью избавиться от рутинной и скучной работы, которая присуща каждому ppc-специалисту, но наша с вами задача стараться и обучаться каждый день. И помните как было написано выше, оптимизации с помощью скриптов ограничивается исключительно Вашей фантазией
Удачной настройки и высокой конверсии!






