Gebotstests

Gebotssymbol

Bei der Ermittlung des besten Gebots für Ihre Keywords kann es sinnvoll sein, verschiedene Gebotsstufen auszuprobieren, um zu ermitteln, mit welchen Geboten Sie Ihre Ziele am besten erreichen. In diesem Artikel erfahren Sie, wie Sie Ihre Gebote systematisch anpassen können, um die optimale Balance für Ihre Keyword-Gebote zu finden.

Dieses Skript passt Ihre Keyword-Gebote anhand verschiedener Faktoren an und erfasst das Ergebnis jeder Änderung.

Funktionsweise

Dieses Skript verwendet eine Google-Tabelle sowohl zum Speichern des Status (z. B. Gebotsmultiplikatoren und Startgebote) als auch zur Katalogleistung (in jedem Intervall des Gebotstests werden Keyword-Gebot, CTR, Klicks und Impressionen aufgezeichnet).

Die Gebotsfaktoren werden dann bei jeder Ausführung des Skripts angewendet. Bei jedem Durchlauf wird der nächste ungenutzte Gebotsfaktor auf Ihre Keyword-Gebote angewendet. Beispiel: Bei einem Startgebot von 1 € und den Gebotsfaktoren 0,8 und 1,2 werden Gebote von 0,80 € und 1,20 € angezeigt.

Gebotsaktualisierungen

Mit der Funktion updateBids() wird der Multiplikator des aktuellen Durchlaufs auf alle Keywords in der ausgewählten Kampagne angewendet:

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);
}

Dieser Code verwendet das maximale CPC-Gebot des Keywords (oder das maximale CPC-Standardgebot der Anzeigengruppe, wenn für das Keyword kein Gebot festgelegt ist) und wendet den Multiplikator an. Er erkennt auch, ob ein Keyword zwischen den Skriptausführungen hinzugefügt wurde, und speichert das aktuelle maximale CPC-Gebot zur späteren Verwendung.

Berichte zur Gebotsleistung

Jedes Mal, wenn das Skript ausgeführt wird und Gebote für mindestens einen Zeitraum (Woche, Tag usw.) angewendet wurden, wird die Funktion outputReport ausgeführt. Das auf dem Tab „Multiplikatoren“ angegebene Datum und das aktuelle Datum werden als Zeitraum für den Abruf von Messwerten für jedes Keyword verwendet. Die Messwerte werden zusammen mit dem Keyword-Gebot für den durch den Zeitraum definierten Zeitraum zur späteren Analyse in einer separaten Tabelle gespeichert.

// 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);

Jedes Keyword in der Kampagne wird durchgegangen und es werden neue Zeilen hinzugefügt, die ein Array aus Keyword, maximalem CPC, Klicks, Impressionen und CTR enthalten. Dieses Array wird dann in das neue Tabellenblatt geschrieben, das gemäß dem Start- und Enddatum des Berichts benannt ist.

Wenn Sie andere Leistungskennzahlen als diejenigen im Skript verwenden, können Sie die Abfrage entsprechend anpassen. Fügen Sie dazu einen weiteren Eintrag in der ersten Zeile (Kopfzeilenzeile) hinzu:

const rows = [['Keyword', 'Max CPC', 'Clicks', 'Impressions', 'Ctr']];

Ändern Sie das Skript so, dass auch dieser Messwert enthalten ist:

rows.push([keyword.getText(), keyword.bidding().getCpc(), stats.getClicks(),
    stats.getImpressions(), stats.getCtr()]);

Wird geplant

Wir empfehlen Ihnen, eine Kampagne auszuwählen, um das Skript zu testen, und planen, dass es wöchentlich oder nach einem anderen Zeitplan ausgeführt wird. Zum geplanten Intervall meldet das Skript die Leistung des vorherigen Zeitraums (falls zutreffend) und wendet einen neuen Gebotsfaktor an.

Die Funktion "main()" enthält eine Abfrage zur Steuerung der einzelnen Schritte des Gebotstests. Nach Abschluss der Gebotstests wird dies protokolliert und bei zukünftigen Ausführungen werden keine weiteren Änderungen vorgenommen:

if (finishedReporting) {
  console.log('Script complete, all bid modifiers tested and reporting. ' +
    'Please remove this script\'s schedule.');
}

Testergebnisse

Nachdem das Skript ein Array von Gebotsanpassungen auf Ihre Keywords angewendet und die Leistung dieser Werte für alle Keywords erfasst hat, kann entschieden werden, was mit diesen Daten geschehen soll. Beachten Sie, dass dieselben Gebotsanpassungen nicht bei allen Keywords zu denselben Leistungsverbesserungen führen.

Beim Ermitteln der wichtigsten Messwerte stehen Ihnen sowohl das maximale CPC-Gebot des Keywords als auch verschiedene Leistungsindikatoren zur Verfügung. Anschließend können Sie jedes Keyword über mehrere Intervalle hinweg auswerten und nach eigenem Ermessen das beste Gebot für jedes Keyword festlegen.

Einrichtung

  • Erstellen Sie eine Kopie dieser Tabelle.
  • Notieren Sie sich die URL der Kopie.
  • Erstellen Sie ein neues Google Ads-Skript mit dem unten stehenden Quellcode.
  • Ändern Sie den Wert der Variablen SPREADSHEET_URL in die URL Ihrer Kopie der Tabelle.
  • Wählen Sie eine Kampagne aus, für die Sie Gebotstests durchführen möchten, und ändern Sie den Wert der Variablen CAMPAIGN_NAME entsprechend.

Quellcode

// 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);
}