Ofertante múltiple: una sola cuenta

Ícono de ofertas

Optimizar las ofertas de palabra clave es una de las actividades más importantes a la hora de administrar cuentas de Google Ads. Las ofertas de palabra clave tienen un impacto directo en la posición de un anuncio (su clasificación en la Búsqueda de Google) y en su costo. A menudo necesitas desarrollar tus propias estrategias de ofertas para alcanzar tus objetivos.

Las reglas automáticas son una función de Google Ads que te permite modificar las ofertas de palabra clave según criterios establecidos, de manera programada. Si tienes muchas reglas automáticas, pueden ser difíciles de administrar.

El ofertante de varios ofertas ofrece una funcionalidad similar a la de las reglas automáticas basadas en una hoja de cálculo. En efecto, cada fila de una hoja de cálculo equivale a toda una regla automática. Administrar estas reglas en una hoja de cálculo es más fácil que administrarlas en Google Ads.

Captura de pantalla de una hoja de cálculo de múltiples ofertantes

La hoja de cálculo anterior muestra una sola regla que hace lo siguiente:

  • Revisa las estadísticas del THIS_WEEK_SUN_TODAY.
  • Busca todas las palabras clave de la Campaña n.° 1 que recibieron más de 1 impresión y cuya CTR es superior al 25%.
  • Aumenta sus ofertas en un 10%, sin exceder los USD 1.40.

Cómo funciona

Las siguientes columnas en la hoja de cálculo deben estar presentes:

  • Acción
  • Argumento
  • Detener límite

Se pueden agregar o quitar otras columnas según sea necesario. Los siguientes son todos ejemplos válidos de nombres de columna y valores de celda correspondientes:

ColumnaValor de celda
CampaignName STARTS_WITH '?'Indonesia_
Conversions >= ?4
Status IN [?]'ENABLED', 'PAUSED'

El símbolo ? en el nombre de la columna se reemplaza por el valor de la fila correspondiente. Consulta la documentación de KeywordSelector para obtener un conjunto completo de columnas compatibles.

Acción

Action es uno de los siguientes:

  • Multiplicar por: Multiplica la oferta de palabra clave por el Argumento. 1.1 aumenta la oferta en un 10%. 0.8 lo reduce un 20%.
  • Add: Agrega el argumento a la oferta de palabra clave. 0.3 aumenta la oferta en USD 0.30 (suponiendo que la cuenta está en USD). -0.14 lo reduce en USD 0.14.
  • Establecer como CPC de la primera página: Establece la oferta de palabra clave en el CPC de la primera página. Argument.
  • Establecer en CPC de la parte superior de la página: Establece la oferta de palabra clave en el CPC de la parte superior de la página. Argument.

Límite de paradas

Detener límite se utiliza para limitar los cambios de oferta que realiza la secuencia de comandos. Si los cambios en la oferta son positivos, esta no superará el valor de Detener límite. Si el cambio es negativo, la oferta no será inferior a Detener límite. Por ejemplo, si la oferta de la palabra clave es actualmente de USD 2.00 (o el equivalente en la moneda local) y la aumentas en un 25% con un límite de detención de USD 3.00, la oferta pasará a ser de USD 2.50. Sin embargo, si el límite se establece en USD 2.30, la oferta también será de USD 2.30.

Resultados

La secuencia de comandos genera automáticamente Resultados. Contiene los errores que se encontraron en una ejecución o una indicación de la cantidad de palabras clave que la secuencia de comandos recuperó y que intentó cambiar.

Ten en cuenta que "intento de cambio" podría no significar que se produjo un cambio real. Por ejemplo, no funcionará proporcionar una oferta negativa a una palabra clave. Asegúrate de revisar los registros de ejecución para ver exactamente qué hizo la secuencia de comandos.

La columna Resultados es la última columna de la hoja de cálculo que se lee. Si agregas alguna condición después de la columna Resultados, no se aplicará.

Programación

Las opciones de programación que más se utilizan para las reglas de oferta son Diaria y Semanalmente.

Ten en cuenta cómo la frecuencia de la programación podría afectar los períodos de las estadísticas que utilices. Dado que las estadísticas de Google Ads pueden tener un retraso de hasta 3 horas, evita programar la secuencia de comandos por horas.

Tampoco podrías programar la secuencia de comandos si te conviene.

Configuración

  • Configura una secuencia de comandos basada en hojas de cálculo con el código fuente que aparece a continuación. Usa la hoja de cálculo de la plantilla de ofertantes múltiples.
  • No olvides actualizar SPREADSHEET_URL en el código.
  • Asegúrate de obtener una vista previa de la secuencia de comandos antes de ejecutarla.
  • Considera si es necesario programar el guion.

Código fuente

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