Multi-Bidder - Satu Akun

Ikon bidding

Mengoptimalkan bid kata kunci adalah salah satu aktivitas terpenting saat mengelola akun Google Ads. Bid kata kunci memiliki dampak langsung pada penempatan iklan (peringkatnya di Google Penelusuran) serta biayanya. Anda sering kali perlu mengembangkan strategi bidding sendiri untuk mencapai sasaran.

Aturan Otomatis adalah fitur Google Ads yang memungkinkan Anda mengubah bid kata kunci berdasarkan kriteria yang ditetapkan, dan melakukannya sesuai jadwal. Jika Anda memiliki Aturan Otomatis dalam jumlah besar, aturan tersebut dapat menjadi sulit untuk dikelola.

Multi-Bidder menawarkan fungsi yang mirip dengan Aturan Otomatis berdasarkan spreadsheet. Setiap baris dalam spreadsheet sebenarnya setara dengan seluruh Aturan Otomatis. Mengelola aturan tersebut dalam spreadsheet lebih mudah daripada mengelolanya di Google Ads.

Screenshot spreadsheet multi-bid

Spreadsheet di atas menunjukkan satu aturan yang melakukan hal berikut:

  • Melihat statistik untuk THIS_WEEK_SUN_TODAY.
  • Menemukan semua kata kunci di Kampanye #1 yang menerima lebih dari 1 tayangan dan yang CTR-nya lebih besar dari 25%.
  • Meningkatkan bid sebesar 10%, tetapi tidak melebihi Rp14.000.

Cara kerjanya

Kolom berikut harus ada di spreadsheet:

  • Tindakan
  • Argumen
  • Batas penghentian

Kolom lain dapat ditambahkan atau dihapus sesuai kebutuhan. Berikut adalah semua contoh nama kolom yang valid dan nilai sel yang sesuai:

ColumnNilai sel
CampaignName STARTS_WITH '?'Indonesia_
Conversions >= ?4
Status IN [?]'ENABLED', 'PAUSED'

Simbol ? dalam nama kolom akan diganti dengan nilai dalam baris yang sesuai. Periksa dokumentasi KeywordSelector untuk mengetahui kumpulan lengkap kolom yang didukung.

Tindakan

Action adalah salah satu dari berikut ini:

  • Kalikan dengan: Mengalikan bid kata kunci dengan Argumen. 1.1 meningkatkan bid sebesar 10%. 0.8 menurunkannya sebesar 20%.
  • Tambahkan: Menambahkan Argument ke bid kata kunci. 0.3 meningkatkan bid sebesar $0,30 (dengan asumsi akun tersebut dalam USD). -0.14 menurunkannya sebesar $0,14.
  • Tetapkan ke CPC Halaman Pertama: Menetapkan bid kata kunci ke CPC halaman pertama. Argumen diabaikan.
  • Tetapkan ke CPC Halaman Bagian Atas: Menetapkan bid kata kunci ke CPC halaman bagian atas. Argumen diabaikan.

Batas perhentian

Batas Perhentian digunakan untuk membatasi perubahan bid yang dibuat skrip. Jika perubahan bid positif, bid tidak akan menjadi lebih besar dari Batas Perhentian. Jika perubahannya negatif, bid tidak akan menjadi lebih kecil dari Batas Perhentian. Misalnya, jika bid kata kunci saat ini sebesar Rp20.000, dan Anda meningkatkannya sebesar 25% dengan Batas Perhentian sebesar Rp30.000, bid akan menjadi Rp25.000. Namun, jika Batas Perhentian ditetapkan sebesar Rp23.000, bid juga akan menjadi Rp23.000.

Hasil

Hasil dibuat secara otomatis oleh skrip. Objek ini berisi error yang ditemukan saat eksekusi, atau indikasi jumlah kata kunci yang diambil dan coba diubah oleh skrip.

Perhatikan bahwa "upaya perubahan" mungkin tidak berarti perubahan yang sebenarnya terjadi, misalnya, memberikan bid negatif pada kata kunci tidak akan berhasil. Pastikan Anda memeriksa log eksekusi untuk melihat persis apa yang dilakukan skrip tersebut.

Kolom Hasil adalah kolom terakhir dalam spreadsheet yang dibaca. Jika Anda menambahkan kondisi apa pun setelah kolom Hasil, kondisi tersebut tidak akan berlaku.

Penjadwalan

Opsi penjadwalan yang paling umum digunakan untuk aturan bidding adalah Harian dan Mingguan.

Perhatikan pengaruh frekuensi jadwal terhadap rentang tanggal statistik yang Anda gunakan. Karena statistik Google Ads dapat tertunda selama hingga 3 jam, hindari menjadwalkan skrip Anda Per Jam.

Anda juga tidak dapat menjadwalkan skrip sama sekali jika itu masuk akal.

Penyiapan

  • Siapkan skrip berbasis {i>spreadsheet<i} dengan kode sumber di bawah ini. Gunakan spreadsheet template Multi Bidder.
  • Jangan lupa untuk memperbarui SPREADSHEET_URL dalam kode.
  • Pastikan untuk melihat pratinjau skrip sebelum menjalankannya.
  • Pertimbangkan apakah akan menjadwalkan skrip.

Kode sumber

// 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 Multi Bidder
 *
 * @overview The Multi Bidder script offers functionality similar to that of
 *     Automated Rules based on a spreadsheet. See
 *     https://developers.google.com/google-ads/scripts/docs/solutions/multi-bidder
 *     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.getMaxCpc() and keyword.setMaxCpc().
 * - version 1.0.2
 *   - Added validation of user settings.
 * - version 1.0.1
 *   - Improvements to time zone handling.
 * - version 1.0
 *   - Released initial version.
 */

const SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';

const spreadsheetAccess = new SpreadsheetAccess(SPREADSHEET_URL, 'Rules');

let totalColumns;

/**
 * The bids of keywords are modified based on the criterion mentioned
 * in the spreadsheet.
 */
function main() {
  // Make sure the spreadsheet is using the account's timezone.
  spreadsheetAccess.spreadsheet.setSpreadsheetTimeZone(
      AdsApp.currentAccount().getTimeZone());
  spreadsheetAccess.spreadsheet.getRangeByName('account_id').setValue(
      AdsApp.currentAccount().getCustomerId());
  const columns = spreadsheetAccess.sheet.getRange(5, 2, 5, 100).getValues()[0];
  for (let i = 0; i < columns.length; i++) {
    if (columns[i].length == 0 || columns[i] == 'Results') {
      totalColumns = i;
      break;
    }
  }
  if (columns[totalColumns] != 'Results') {
    spreadsheetAccess.sheet.getRange(5, totalColumns + 2, 1, 1).
        setValue('Results');
  }
  // clear the results column
  spreadsheetAccess.sheet.getRange(6, totalColumns + 2, 1000, 1).clear();

  let row = spreadsheetAccess.nextRow();

  while (row != null) {
    let argument;
    let stopLimit;
    try {
      argument = parseArgument(row);
      stopLimit = parseStopLimit(row);
    } catch (ex) {
      logError(ex);
      row = spreadsheetAccess.nextRow();
      continue;
    }
    let selector = AdsApp.keywords();
    for (let i = 3; i < totalColumns; i++) {
      let header = columns[i];
      let value = row[i];
      if (!isNaN(parseFloat(value)) || value.length > 0) {
        if (header.indexOf("'") > 0) {
          value = value.replace(/\'/g, "\\'");
        } else if (header.indexOf('\"') > 0) {
          value = value.replace(/"/g, '\\\"');
        }
        const condition = header.replace('?', value);
        selector.withCondition(condition);
      }
    }
    selector.forDateRange(spreadsheetAccess.spreadsheet.
        getRangeByName('date_range').getValue());

    const keywords = selector.get();

    try {
      keywords.hasNext();
    } catch (ex) {
      logError(ex);
      row = spreadsheetAccess.nextRow();
      continue;
    }

    let fetched = 0;
    let changed = 0;

    for (const keyword of keywords) {
      let oldBid = keyword.bidding().getCpc();
      let action = row[0];
      let newBid;

      fetched++;
      if (action == 'Add') {
        newBid = addToBid(oldBid, argument, stopLimit);
      } else if (action == 'Multiply by') {
        newBid = multiplyBid(oldBid, argument, stopLimit);
      } else if (action == 'Set to First Page Cpc' ||
        action == 'Set to Top of Page Cpc') {
        let newBid = action == 'Set to First Page Cpc' ?
            keyword.getFirstPageCpc() : keyword.getTopOfPageCpc();
        let isPositive = newBid > oldBid;
        newBid = applyStopLimit(newBid, stopLimit, isPositive);
      }
      if (newBid < 0) {
        newBid = 0.01;
      }
      newBid = newBid.toFixed(2);
      if (newBid != oldBid) {
        changed++;
      }
      keyword.bidding().setCpc(parseFloat(newBid));
    }
    logResult('Fetched ' + fetched + '\nChanged ' + changed);

    row = spreadsheetAccess.nextRow();
  }

  spreadsheetAccess.spreadsheet.getRangeByName('last_execution')
      .setValue(new Date());
}

/**
 * Performs addition on oldbid and argument.
 *
 * @param {string} oldBid The old bid value.
 * @param {string} argument The arugument value in the spreadsheet.
 * @param {string} stopLimit The changes made to the bids.
 * @return {string} Applies stop limit to the bid.
 */
function addToBid(oldBid, argument, stopLimit) {
  return applyStopLimit(oldBid + argument, stopLimit, argument > 0);
}

/**
 * Performs multiplication on oldbid and argument.
 *
 * @param {string} oldBid The old bid value.
 * @param {string} argument The arugument value in the spreadsheet.
 * @param {string} stopLimit The changes made to the bids.
 * @return {string} Applies the stop limit to the bid.
 */
function multiplyBid(oldBid, argument, stopLimit) {
  return applyStopLimit(oldBid * argument, stopLimit, argument > 1);
}

/**
 * Applies stop limit to the bid depending on the conditions.
 *
 * @param {string} newBid The calculated bid value as per the action
 *   in the spreadsheet.
 * @param {string} stopLimit The changes made to the bids.
 * @param {boolean} isPositive checks for the value sign.
 * @return {string} assigns stop limit to the bids and returns.
 */
function applyStopLimit(newBid, stopLimit, isPositive) {
  if (stopLimit) {
    if (isPositive && newBid > stopLimit) {
      newBid = stopLimit;
    } else if (!isPositive && newBid < stopLimit) {
      newBid = stopLimit;
    }
  }
  return newBid;
}

/**
 * If the argument is not specified or bad arguments are passed, an error is
 * returned in the result field.
 *
 * @param {!Object} row The row in the spreadsheet.
 * @return {string} Returns error message in the result column.
 */
function parseArgument(row) {
  if (row[1].length == 0 && (row[0] == 'Add' || row[0] == 'Multiply by')) {
    throw ('\"Argument\" must be specified.');
  }
  let argument = parseFloat(row[1]);
  if (isNaN(argument)) {
    throw 'Bad Argument: must be a number.';
  }
  return argument;
}

/**
 * Parses stop limit from the spreadsheet.
 *
 * @param {!Object} row The row in the spreadsheet.
 * @return {string} Returns error message to the Result field in the row
 */
function parseStopLimit(row) {
  if (row[2].length == 0) {
    return null;
  }
  let limit = parseFloat(row[2]);
  if (isNaN(limit)) {
    throw 'Bad Argument: must be a number.';
  }
  return limit;
}

/**
 * Format the error messages in the spreadsheet
 *
 * @param {string} error The error messages.
 */
function logError(error) {
  spreadsheetAccess.sheet.getRange(spreadsheetAccess.currentRow(),
      totalColumns + 2, 1, 1)
  .setValue(error)
  .setFontColor('#c00')
  .setFontSize(8)
  .setFontWeight('bold');
}

/**
 * Formats the result messages in the spreadsheet
 *
 * @param {string} result The result values.
 */
function logResult(result) {
  spreadsheetAccess.sheet.getRange(spreadsheetAccess.currentRow(),
      totalColumns + 2, 1, 1)
  .setValue(result)
  .setFontColor('#444')
  .setFontSize(8)
  .setFontWeight('normal');
}

/**
 * Provides access to the spreadsheet using spreadsheetUrl, sheetName
 * and validate the spreadsheet.
 *
 * @param {string} spreadsheetUrl The spreadsheet url.
 * @param {string} sheetName The spreadsheet name.
 * @return {string} Returns spreadsheet along with rows.
 */
function SpreadsheetAccess(spreadsheetUrl, sheetName) {
  Logger.log('Using spreadsheet - %s.', spreadsheetUrl);
  this.spreadsheet = validateAndGetSpreadsheet(spreadsheetUrl);

  this.sheet = this.spreadsheet.getSheetByName(sheetName);
  this.cells = this.sheet.getRange(6, 2, this.sheet.getMaxRows(),
      this.sheet.getMaxColumns()).getValues();
  this.rowIndex = 0;

  this.nextRow = function() {
    for (; this.rowIndex < this.cells.length; this.rowIndex++) {
      if (this.cells[this.rowIndex][0]) {
        return this.cells[this.rowIndex++];
      }
    }
    return null;
  };
  this.currentRow = function() {
    return this.rowIndex + 5;
  };
}

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