Système d'enchères multiples - Compte unique

Icône d'enchères

L'optimisation des enchères de mots clés est l'une des activités les plus importantes de la gestion de comptes Google Ads. Les enchères de mots clés ont un impact direct sur l'emplacement de l'annonce (son classement dans la recherche Google), ainsi que sur son coût. Vous devez souvent développer vos propres stratégies d'enchères pour atteindre vos objectifs.

Les règles automatiques sont une fonctionnalité Google Ads qui vous permet de modifier les enchères de mots clés en fonction de critères définis, et ce de manière planifiée. Si vous disposez d'un grand nombre de règles automatiques, leur gestion peut s'avérer difficile.

Le système d'enchères multiples offre des fonctionnalités semblables à celles des règles automatiques, qui reposent sur une feuille de calcul. Chaque ligne d'une feuille de calcul équivaut à une règle automatique complète. Il est plus facile de gérer ces règles dans une feuille de calcul que dans Google Ads.

Capture d'écran de la feuille de calcul multi-enchérisseur

La feuille de calcul ci-dessus illustre une règle unique qui effectue les opérations suivantes:

  • Elle examine les statistiques pour THIS_WEEK_SUN_TODAY.
  • recherche tous les mots clés de la campagne 1 qui ont reçu plusieurs impressions et dont le CTR est supérieur à 25 % ;
  • accroît de 10 % les enchères de ces mots clés, sans dépasser 1,40 €.

Comment ça marche ?

Les colonnes suivantes doivent être présentes dans la feuille de calcul:

  • Action
  • Argument
  • Limite d'arrêts

Vous pouvez ajouter ou supprimer d'autres colonnes, selon les besoins. Voici tous des exemples valides de noms de colonne et de valeurs de cellule correspondantes:

ColonneValeur de la cellule
CampaignName STARTS_WITH '?'Indonesia_
Conversions >= ?4
Status IN [?]'ENABLED', 'PAUSED'

Le symbole ? dans le nom de la colonne est remplacé par la valeur de la ligne correspondante. Consultez la documentation sur KeywordSelector pour découvrir un ensemble complet de colonnes compatibles.

Action

L'action peut être l'une des suivantes :

  • Multiply by (Multiplier par) : multiplie l'enchère de mot clé par l'argument. 1.1 augmente l'enchère de 10%. 0.8 réduit cette valeur de 20%.
  • Add (Ajouter) : ajoute l'argument à l'enchère de mot clé. 0.3 augmente l'enchère de 0,30 € (en supposant que la devise du compte est l'euro). -0.14 réduit cette valeur de 0,14 $.
  • Set to First Page Cpc (Utiliser le CPC de première page) : définit le CPC de première page comme enchère de mot clé. L'argument est ignoré.
  • Set to Top of Page Cpc (Utiliser le CPC de haut de page) : définit le CPC de haut de page comme enchère de mot clé. L'argument est ignoré.

Limite d'arrêts

Le seuil d'arrêt (Stop Limit) définit le plafond des modifications apportées aux enchères par le script. En cas de modification positive, l'enchère ne dépassera pas le seuil d'arrêt. En cas de modification négative, l'enchère ne descendra pas au-dessous du seuil d'arrêt. Par exemple, si l'enchère de mot clé est actuellement de 2 € et que vous l'augmentez de 25% avec un seuil d'arrêt de 3 €, l'enchère sera de 2, 50 €. En revanche, si le seuil d'arrêt est de 2,30 €, l'enchère s'élèvera également à 2,30 €.

Résultats

L'onglet Résultats est généré automatiquement par le script. Il contient toutes les erreurs rencontrées par une exécution, ou une indication du nombre de mots clés que le script a récupérés et tentés de modifier.

Notez qu'une "tentative de modification" ne signifie pas nécessairement qu'une modification a réellement eu lieu. Par exemple, attribuer une enchère négative à un mot clé ne fonctionnera pas. Vérifiez les journaux d'exécution pour voir exactement ce que le script a fait.

La colonne Résultats est la dernière colonne lue de la feuille de calcul. Si vous ajoutez des conditions après la colonne Résultats, elles ne s'appliqueront pas.

Planification

Les options Daily (Quotidienne) et Weekly (Hebdomadaire) sont les plus couramment utilisées pour planifier les règles d'enchères.

Tenez compte de l'impact de la fréquence de planification sur les plages de dates des statistiques que vous utilisez. Les statistiques Google Ads pouvant présenter jusqu'à trois heures de latence, évitez de planifier l'exécution du script toutes les heures (Hourly).

Vous ne pourriez pas non plus planifier le script du tout si c'est le plus logique.

Préparation

  • Configurez un script basé sur une feuille de calcul avec le code source ci-après. Utilisez le modèle de feuille de calcul du système d'enchères multiples.
  • N'oubliez pas de mettre à jour SPREADSHEET_URL dans le code.
  • Prévisualisez le script avant de l'exécuter.
  • Réfléchissez à l'intérêt de planifier ou non le script.

Code source

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