Przy ustalaniu najlepszej stawki za słowa kluczowe warto wypróbować różne poziomy stawek i sprawdzić, które stawki będą najskuteczniejsze do osiągnięcia założonych celów. Tutaj pokażemy, jak systematycznie dostosowywać stawki, by znaleźć tę optymalną.
Skrypt ten dostosowuje stawki za słowa kluczowe na podstawie serii mnożników i rejestruje wyniki każdej zmiany.
Jak to działa
Skrypt ten korzysta z arkusza kalkulacyjnego Google do przechowywania stanu (np. mnożników stawek i stawek początkowych), jak również skuteczności katalogu (dla każdego przedziału czasu testowania stawek rejestruje stawkę za słowo kluczowe, CTR, kliknięcia i wyświetlenia).
Następnie mnożniki stawek są stosowane kolejno do każdego wystąpienia skryptu. Przy każdym uruchomieniu do określania stawek za słowa kluczowe stosowany jest kolejny niewykorzystany mnożnik stawki. Na przykład przy stawce początkowej 1 zł i mnożnikach 0,8 i 1,2 stawki będą wynosić 0,80 zł i 1,20 zł.
Aktualizacje stawek
Funkcja updateBids()
stosuje mnożnik dla tej iteracji do wszystkich słów kluczowych w wybranej kampanii:
const keywordIter = campaign.keywords().get();
for (const keyword of keywordIter) {
let oldBid = startingBids[keyword.getText()];
if (!oldBid) {
// If we don't have a starting bid, keyword has been added since we
// started testing.
oldBid = keyword.bidding().getCpc() || keyword.getAdGroup().bidding().getCpc();
startingBids[keyword.getText()] = oldBid;
}
const newBid = oldBid * multiplier;
keyword.bidding().setCpc(newBid);
}
Ten kod wykorzystuje maks. stawkę CPC słowa kluczowego (lub domyślną maks. stawkę CPC grupy reklam, jeśli słowo kluczowe nie ma określonej stawki) i stosuje mnożnik. Wykrywa też, czy pomiędzy wykonaniem skryptu a konkretnym słowem kluczowym zostało dodane słowo kluczowe, i zapisuje bieżącą maksymalną stawkę CPC, aby móc ją wykorzystać w przyszłości.
Raport skuteczności stawek
Za każdym razem, gdy skrypt jest uruchomiony i stosowane są stawki przez co najmniej 1 okres (tydzień, tydzień itd.), jest wykonywana funkcja outputReport
. Data oznaczona na karcie Mnożniki i dzisiejsza data są wykorzystywane jako zakres dat przy uzyskiwaniu danych o każdym słowie kluczowym. Dane wraz ze stawką za słowo kluczowe z okresu wyznaczonego przez zakres dat są przechowywane w osobnym arkuszu na potrzeby późniejszej analizy.
// Create a new sheet to output keywords to.
const reportSheet = spreadsheet.insertSheet(start + ' - ' + end);
const campaign = getCampaign();
const rows = [['Keyword', 'Max CPC', 'Clicks', 'Impressions', 'Ctr']];
const keywordIter = campaign.keywords().get();
for (const keyword of keywordIter) {
const stats = keyword.getStatsFor(start, end);
rows.push([keyword.getText(), keyword.bidding().getCpc(), stats.getClicks(),
stats.getImpressions(), stats.getCtr()]);
}
reportSheet.getRange(1, 1, rows.length, 5).setValues(rows);
Każde słowo kluczowe w kampanii jest powtarzane i dodawane są nowe wiersze zawierające zestaw słów kluczowych, maks. CPC, kliknięć, wyświetleń i CTR. Ta tablica jest następnie zapisywana w nowym arkuszu (którym nazwa jest zgodną z datami rozpoczęcia i zakończenia raportu).
Jeśli używasz innych kluczowych wskaźników wydajności niż te wymienione w skrypcie, możesz zmienić tę zasadę, aby je uwzględnić. Aby to zrobić, dodaj kolejny wpis w pierwszym wierszu (wierszu nagłówka) w następujący sposób:
const rows = [['Keyword', 'Max CPC', 'Clicks', 'Impressions', 'Ctr']];
Zmodyfikuj też skrypt tak, aby zawierał również te dane:
rows.push([keyword.getText(), keyword.bidding().getCpc(), stats.getClicks(),
stats.getImpressions(), stats.getCtr()]);
Planuję
Zalecamy, by wybrać kampanię do przetestowania skryptu i zaplanować jego uruchamianie Co tydzień (lub według innego, odpowiedniego dla Ciebie harmonogramu). Po zaplanowanym interwale skrypt raportuje skuteczność z poprzedniego okresu (jeśli ma to zastosowanie) i stosuje nowy mnożnik stawki.
Główna funkcja obejmuje funkcje logiczne zarządzające poszczególnymi etapami testowania stawek. Po zakończeniu testowania stawek skrypt rejestruje tę informację, a przyszłe uruchomienia nie będą powodować żadnych dodatkowych zmian:
if (finishedReporting) {
console.log('Script complete, all bid modifiers tested and reporting. ' +
'Please remove this script\'s schedule.');
}
Wyniki testów
Gdy skrypt zastosuje tablicę modyfikatorów stawek do słów kluczowych i zarejestruje skuteczność tych wartości dla wszystkich słów kluczowych, trzeba zdecydować, co zrobić z tymi danymi. Pamiętaj, że ten sam modyfikator stawek może nie zapewniać takiej samej poprawy skuteczności w przypadku wszystkich słów kluczowych.
Przy określaniu najważniejszych danych dostępne są zarówno maks. stawka CPC słowa kluczowego, jak i różne wskaźniki skuteczności. Możesz potem sprawdzać każde słowo kluczowe w wielu interwałach i własnie wybrać najlepszą stawkę dla każdego z nich.
Konfiguracja
- Zrób kopię tego arkusza kalkulacyjnego.
- Zapisz adres URL kopii.
- Utwórz nowy skrypt Google Ads na podstawie podanego niżej kodu źródłowego.
- Zmień wartość zmiennej
SPREADSHEET_URL
na adres URL swojej kopii arkusza kalkulacyjnego. - Wybierz kampanię, w której chcesz testować stawki, i zmień odpowiednio wartość zmiennej
CAMPAIGN_NAME
.
Kod źródłowy
// 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 Testing
*
* @overview The Bid Testing script allows you to try different levels of
* bidding for keywords in your advertiser account to determine what bids
* work best to achieve your goals.
* See https://developers.google.com/google-ads/scripts/docs/solutions/bid-testing
* for more details.
*
* @author Google Ads Scripts Team [adwords-scripts@googlegroups.com]
*
* @version 2.0
*
* @changelog
* - version 2.0
* - Updated to use new Google Ads scripts features.
* - version 1.0.3
* - Replaced deprecated keyword.setMaxCpc() and keyword.getMaxCpc().
* - version 1.0.2
* - Added validation for user settings.
* - version 1.0.1
* - Improvements to time zone handling.
* - version 1.0
* - Released initial version.
*/
const SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';
const CAMPAIGN_NAME = 'YOUR_CAMPAIGN_NAME';
const FIELDS = ['ad_group_criterion.keyword.text',
'ad_group.cpc_bid_micros',
'metrics.impressions',
'metrics.clicks',
'metrics.ctr',
'campaign.id'];
function main() {
validateCampaignName();
console.log(`Using spreadsheet - ${SPREADSHEET_URL}`);
const spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
spreadsheet.setSpreadsheetTimeZone(AdsApp.currentAccount().getTimeZone());
const multipliersSheet = spreadsheet.getSheetByName('Multipliers');
const multipliers = multipliersSheet.getDataRange().getValues();
// Find if we have a multiplier left to apply.
let multiplierRow = 1;
for (; multiplierRow < multipliers.length; multiplierRow++) {
// if we haven't marked a multiplier as applied, use it.
if (!multipliers[multiplierRow][1]) {
break;
}
}
const today = Utilities.formatDate(new Date(),
AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd');
let shouldReport = multiplierRow > 1;
let shouldIncreaseBids = multiplierRow < multipliers.length;
const finishedReporting = multipliersSheet.getSheetProtection().isProtected();
if (shouldReport && !finishedReporting) {
// If we have at least one multiplier marked as applied,
// let's record performance since the last time we ran.
const lastRun = multipliers[multiplierRow - 1][1];
if (lastRun == today) {
console.log('Already ran today, skipping');
return;
}
outputReport(spreadsheet, lastRun, today);
if (!shouldIncreaseBids) {
// We've reported one iteration after we finished bids, so mark the sheet
// protected.
const permissions = multipliersSheet.getSheetProtection();
permissions.setProtected(true);
multipliersSheet.setSheetProtection(permissions);
console.log(`View bid testing results here: ${SPREADSHEET_URL}`);
}
}
if (shouldIncreaseBids) {
// If we have a multiplier left to apply, let's do so.
updateBids(spreadsheet, multipliers[multiplierRow][0]);
multipliers[multiplierRow][1] = today;
// Mark multiplier as applied.
multipliersSheet.getDataRange().setValues(multipliers);
}
if (finishedReporting) {
console.log(`Script complete, all bid modifiers tested and reporting. ` +
`Please remove this script's schedule.`);
}
}
function updateBids(spreadsheet, multiplier) {
console.log(`Applying bid multiplier of ${multiplier}`);
let startingBids = getStartingBids(spreadsheet);
if (!startingBids) {
startingBids = recordStartingBids(spreadsheet);
}
const campaign = getCampaign();
const keywordIter = campaign.keywords().get();
for (const keyword of keywordIter) {
let oldBid = startingBids[keyword.getText()];
if (!oldBid) {
// If we don't have a starting bid, keyword has been added since we
// started testing.
oldBid = keyword.bidding().getCpc() ||
keyword.getAdGroup().bidding().getCpc();
startingBids[keyword.getText()] = oldBid;
}
const newBid = oldBid * multiplier;
keyword.bidding().setCpc(newBid);
}
saveStartingBids(spreadsheet, startingBids);
}
function outputReport(spreadsheet, start, end) {
console.log(`Reporting on ${start} -> ${end}`);
// Create a new sheet to output keywords to.
const reportSheet = spreadsheet.insertSheet(`${start} - ${end}`);
const rows = [['Keyword', 'Max CPC', 'Clicks', 'Impressions', 'Ctr']];
const fields = FIELDS.join(",");
const keywordIter =
AdsApp.search(`SELECT ${fields} FROM keyword_view ` +
`WHERE campaign.name = '${CAMPAIGN_NAME}' ` +
`AND segments.date BETWEEN '${start}' AND '${end}'`);
for (const keyword of keywordIter) {
if (keyword.metrics.impressions == 0)
keyword.metrics.ctr = 0;
rows.push([keyword.adGroupCriterion.keyword.text,
keyword.adGroup.cpcBidMicros/1000000,
keyword.metrics.clicks,
keyword.metrics.impressions,
parseFloat(keyword.metrics.ctr).toFixed(5)]);
}
reportSheet.getRange(1, 1, rows.length, 5).setValues(rows);
}
function recordStartingBids(spreadsheet) {
const startingBids = {};
const keywords = getCampaign().keywords().get();
for (const keyword of keywords) {
const bid = keyword.bidding().getCpc() ||
keyword.getAdGroup().bidding().getCpc();
startingBids[keyword.getText()] = bid;
}
saveStartingBids(spreadsheet, startingBids);
return startingBids;
}
function getStartingBids(spreadsheet) {
const sheet = spreadsheet.getSheetByName('Starting Bids');
if (!sheet) {
return;
}
const rawData = sheet.getDataRange().getValues();
const startingBids = {};
for (const i of rawData) {
startingBids[i[0]] = i[1];
}
return startingBids;
}
function saveStartingBids(spreadsheet, startingBids) {
let sheet = spreadsheet.getSheetByName('Starting Bids');
if (!sheet) {
sheet = spreadsheet.insertSheet('Starting Bids');
}
const rows = [];
for (const keyword in startingBids) {
rows.push([keyword, startingBids[keyword]]);
}
sheet.getRange(1, 1, rows.length, 2).setValues(rows);
}
function dateToString(date) {
return date.getFullYear() + zeroPad(date.getMonth() + 1) +
zeroPad(date.getDate());
}
function zeroPad(n) {
if (n < 10) {
return '0' + n;
} else {
return '' + n;
}
}
function getCampaign() {
return AdsApp.campaigns().withCondition(`Name = '` +
`${CAMPAIGN_NAME}'`).get().next();
}
/**
* Validates the provided campaign name and throws a descriptive error
* if the user has not changed the email from the default fake name.
*
* @throws {Error} If the name is the default fake name.
*/
function validateCampaignName(){
if (CAMPAIGN_NAME == "YOUR_CAMPAIGN_NAME") {
throw new Error('Please use a valid campaign name.');
}
}
/**
* Validates the provided spreadsheet URL
* to make sure that it's set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
throw new Error('Please specify a valid Spreadsheet URL. You can find' +
' a link to a template in the associated guide for this script.');
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}