Google Ads

de en

Verfolgen Sie Ihre AdWords-Änderungen mit Labels und Slack

October 1, 2018

Auch ohne Experimente nach Änderungen auf dem Laufenden bleiben - so geht's

Trotz der Tatsache, dass Google die neue AdWords-Benutzeroberfläche mit der damals mit Spannung erwarteten [Notizen-Funktion] (https://searchengineland.com/adwords-notes-have-arrived-how-to-get-started-299381) veröffentlicht hat, ist es immer noch nicht einfach, all die kleinen Änderungen im Auge zu behalten, die Sie in dem Bündel von Konten vornehmen, die Sie wahrscheinlich verwalten.

Wenn Sie natürlich vorhaben, einen anderen Kampagnenaufbau mit viel Verkehr zu testen, richten Sie einfach ein Experiment ein und los geht's.

Aber was ist mit all den kleinen Dingen, die Sie auf dem Weg dorthin machen? Ich meine, besonders bei gut etablierten Konten, die "nur laufen". Ich spreche davon, hier ein weiteres Schlüsselwort hinzuzufügen, dort einen Gerätemodifikator zu setzen und von Zeit zu Zeit neue Anzeigen einzufügen.

Die neue Notizfunktion ist nicht geeignet, Ihnen bei der Überwachung solcher Änderungen zu helfen. Und manuelles Notieren von Tabellenkalkulationen usw. ist auch nicht die Lösung. Warten Sie. Aber warum?

Weil es je nachdem, welche Entität Sie berühren, d.h. Schlüsselwörter mit hohem oder niedrigem Volumen, einen großen Unterschied macht, wann es sinnvoll ist, einen Blick darauf zu werfen, wie sich die Entität nach Ihren Änderungen verhalten hat. Sie ändern das Schlüsselwort X, und X erzeugt 100 Klicks pro Tag →. Sie kontrollieren das Schlüsselwort besser noch am selben Tag. Sie ändern das Schlüsselwort Y, und Y erzeugt 30 Klicks pro Monat → Entspannen Sie sich eine Weile.

Wer also wirklich in Konten springen und all diese kleinen Änderungen auf täglicher Basis kontrollieren will, muss jedes Mal zuerst prüfen, ob nach den Änderungen für jede einzelne Änderung eine ausreichende Anzahl von Klicks erfolgt ist.

Zur Rettung stelle ich Ihnen die Leistungsfähigkeit der AdWords-Skripte, der Google Firebase Datenbank und von Slack vor, Ihrem bevorzugten Kommunikationstool, das sogar Google [in Kombination mit der AdWords-Berichterstattung] vorschlägt (https://developers.google.com/google-ads/scripts/docs/examples/slack).

Sie erhalten das Skript und eine kurze Einrichtungsanleitung in einer Sekunde, lassen Sie mich nur kurz erklären, wie es funktioniert.

Sie installieren das Skript und lassen es stündlich in Ihrem AdWords-Konto laufen. Sie werden ein Label in Ihrem Konto haben, mit dem Sie alle Entitäten kennzeichnen (standardmäßig werden Schlüsselwörter, Adgroups und Anzeigen unterstützt), die Sie überwachen möchten. Lassen Sie uns dieses Label vorerst CONTROL nennen.

Sie ändern also ein Schlüsselwort in irgendeiner Weise und setzen dann die Bezeichnung CONTROL auf dieses Schlüsselwort. Wenn das Skript das nächste Mal ausgeführt wird, bemerkt es, dass Sie ein neues Schlüsselwort mit CONTROL gekennzeichnet haben, und es schreibt die Uhrzeit und einige andere Details in eine Datenbank. Jetzt wird das Skript bei jeder Ausführung des Skripts einen Blick in Ihre CONTROL-Entitäten werfen, um zu sehen, ob sie eine Schlupfbericht-Bedingung auslösen. Standardmäßig kennt das Skript zwei Arten von Triggern, zeit- und statistikbasierte.

Ein statistikbasierter Trigger ist standardmäßig clicks|30, was bedeutet, dass Sie Leistungsstatistiken zu Ihrem Schlupfkanal erhalten, sobald die Entität 30 Klicks nach Erhalt des CONTROL-Labels durchläuft.

Der zeitbasierte Trigger ist standardmäßig Wochen|1-4, d.h. ab 1 Woche, nachdem die Entität das Label erhalten hat, werden Leistungsstatistiken wöchentlich an Ihren Schlupfkanal gesendet, bis 4 Wochen vergangen sind.

Die Statistiken im Schlupfkanal werden den Zeitraum, der nach dem Setzen des Labels verstrichen ist, mit dem Zeitraum derselben Länge vor dem Setzen des Labels vergleichen. Für den statistikbasierten Trigger kann dies sehr dynamisch sein, wobei ein Tag das kleinste Intervall ist. Der zeitbasierte Standard-Trigger vergleicht immer volle Wochen (7 Tage).

Der folgende Screenshot zeigt eine lockere Ausgabe für ein Schlüsselwort, das neu erstellt wurde und das CONTROL-Label erhalten hat (deshalb zeigen alle Vergleichsstatistiken 0).

Die Schlupf-Ausgabe (standardmäßig sehr einfach)

Wenn Sie die Überwachung einer Entität beenden wollen, entfernen Sie einfach das Etikett.

Setup-Anweisungen

Loggen Sie sich bei Google Firebase ein (es ist kostenlos), erstellen Sie ein Projekt und eine Echtzeit-Datenbank innerhalb dieses Projekts. Bearbeiten Sie die Datenbankregeln, um nur authentifizierten Zugriff zu erlauben:

{
 “rules”: {
 “.read”: “auth != null”,
 “.write”: “auth != null”
 }
}

Bemerkung die Datenbank-URL und das Datenbankgeheimnis:

Erstellen Sie eine Slack-App für Ihren Slack-Arbeitsbereich und aktivieren Sie eingehende Webhooks. Beachten Sie die Hook-URL, die Sie unter Apps Management → Custom Integrations → Incoming WebHooks finden.

Kopieren Sie das Skript am Ende dieser Geschichte und fügen Sie es zu Ihrem AdWords-Konto hinzu, geben Sie die erforderlichen Firebase- und Slack-Konfigurationsdaten ein.

Konfigurieren Sie den statistikbasierten Trigger und den zeitbasierten Trigger nach Ihren Wünschen.

aktivieren Sie die stündliche Ausführung für das Skript.


Sie können jetzt Entitäten in Ihrem Konto kennzeichnen und automatisch Statistiken in Slack erhalten. Für die beste Erfahrung sollten Sie die Slack-Ausgabe ein wenig stylen :) Alle benötigten Informationen werden [von Slack] bereitgestellt (https://api.slack.com/docs/message-formatting).

Code

/**
 * AdWords Performance Monitoring in Slack via Labels
 * @author: Dustin Recko
 *
 */

// Config Section //>

var DB_URL = 'https://...'; // The Firebase Database URL
var DB_AUTH = 'xxx'; // The Firebase Database Secret

var SLACK_HOOK = 'https://...'; // The Slack Hook URL
var SLACK_CHANNEL = '#adwords'; // The Slack Channel
var SLACK_EMOJI = ':smile:'; // The Slack Emoji

var TRIGGER = {
  clicks: 30, //Stats as a trigger with a specified threshold
  weeks: [1,4] //Time as a trigger with a start and end threshold
};

var LABEL_NAME = "CONTROL"; // The name of the label used in AdWords to activate monitoring for Keywords, AdGroups, and Ads

var NOW = new Date();

// End of Config <//

function main() {

  init();

  var ACC = AdWordsApp.currentAccount().getName().split(" ")[0];

  var myDb = new firebase(DB_URL,DB_AUTH);
  var myDbJson = myDb.getJson(LABEL_NAME+'/'+ACC) || nest({},[LABEL_NAME,ACC]);

  var mySlack = new slack(SLACK_HOOK,SLACK_CHANNEL,SLACK_EMOJI);

  var myLabel = AdWordsApp.labels().withCondition("Name = '"+LABEL_NAME+"'").get().next();

  var process = {
    "keywords": myLabel.keywords().get(),
    "adGroups": myLabel.adGroups().get(),
    "ads": myLabel.ads().get()
  };

  /// Main process

  for(var handler in process) {

    /// Check items with the label

    while(process[handler].hasNext()) {

      var h = process[handler].next();

      if(!myDbJson[handler] || !myDbJson[handler][h.getId()]) {

        var obj = {
          "name"    : (h.getText instanceof Function) ? h.getText() : ((h.getDescription1 instanceof Function) ? h.getDescription1() : h.getName()),
          "campaign": h.getCampaign().getName(),
          "qsStart"  : (h.getQualityScore instanceof Function) ? h.getQualityScore() : 0,
          "started" : NOW.getTime(),
          "trigger": initTrigger(TRIGGER)
        };
        myDb.patch(obj,LABEL_NAME+'/'+ACC+'/'+handler+'/'+h.getId());
        myDbJson = nest(myDbJson,[handler,h.getId()]);

      } else {

        for(var i in TRIGGER) {
          switch(typeof(myDbJson[handler][h.getId()].trigger[i])) {
            case "boolean":        
              if(!myDbJson[handler][h.getId()].trigger[i]) {
                if(statsBasedCheck(handler,h,i,myDbJson[handler][h.getId()])) {
                  var status = {};
                  status[i] = true;
                  myDb.patch(status,LABEL_NAME+'/'+ACC+'/'+handler+'/'+h.getId()+'/trigger');
                }
              }
              break;
            case "number":
              var weeksPassed = Math.round((NOW.getTime() - myDbJson[handler][h.getId()].started)/(1000*60*60*24)) / 7;
              if(weeksPassed%1 === 0 && TRIGGER[i][0] <= weeksPassed && weeksPassed <= TRIGGER[i][1] && weeksPassed > myDbJson[handler][h.getId()].trigger[i]) {
                timeBasedCheck(handler,h,i,myDbJson[handler][h.getId()]);
                var status = {};
                status[i] = weeksPassed;
                myDb.patch(status,LABEL_NAME+'/'+ACC+'/'+handler+'/'+h.getId()+'/trigger');
              }
              break;
          }
        }
      }
      /// Flag processed items
      myDbJson[handler][h.getId()].flag = true;
    }
      /// Cleanup no longer labelled items, i.e., non-flagged
      for(var i in myDbJson[handler]) {
      if(myDbJson[handler][i].flag == undefined)
        myDb.purge(LABEL_NAME+'/'+ACC+'/'+handler+'/'+i);
    }
  }


  /// Some functions

  function init() {
    Date.prototype.yyyymmdd = function(days) {
      if(days) {
        this.setDate(this.getDate() + days);
      }
      return Utilities.formatDate(this, AdWordsApp.currentAccount().getTimeZone(),'yyyyMMdd');
    }
  }

  function firebase(db,auth) {
    this.db = db;
    this.auth = auth;

    this.patch = function(payload,path) {
      path = path+'/.json?auth=';
      var options = {
        "method"  : "patch",
        "payload" : JSON.stringify( payload )
      };
      UrlFetchApp.fetch(this.db+path+this.auth,options);
    }

    this.purge = function(path) {
      path = path+'/.json?auth=';
      var options = {
        "method": "delete"
      };
      UrlFetchApp.fetch(this.db+path+this.auth,options);
    }

    this.getJson = function(path) {
      path = path+'/.json?auth=';
      return JSON.parse(
        UrlFetchApp
        .fetch(this.db+path+this.auth)
        .getContentText()
      );
    }
  }

  function slack(hook,channel,emoji) {

    this.hook = hook;
    this.channel = channel;
    this.emoji = emoji;

    this.msg = function(payload) {
      payload.channel = this.channel;
      payload.icon_emoji = this.emoji;
      var options = {
        method: "POST",
        contentType: 'application/json',
        payload: JSON.stringify(payload)
      };
      UrlFetchApp.fetch(this.hook,options);
    }
  }

  function initTrigger() {
    var obj = {};
    for(var i in TRIGGER) {
      if(typeof(TRIGGER[i]) == "number")
        obj[i] = false
      else
        obj[i] = 0
    }
    return obj;
  }

  function statsBasedCheck(type,handler,trigger,dbData) {

    var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd(),NOW.yyyymmdd());

    var stats = {
      avgCpc: sh.getAverageCpc().toFixed(2),
      avgPos: sh.getAveragePosition(),
      clicks: sh.getClicks(),
      conversions: sh.getConversions(),
      cost: sh.getCost(),
      qs: (type == 'keywords') ? handler.getQualityScore() : 0
    };

    if(stats[trigger] >= TRIGGER[trigger])  {

      var daysPassed = Math.round((NOW.getTime() - dbData.started)/(1000*60*60*24));

      var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd(daysPassed*-1),new Date(dbData.started).yyyymmdd());

      var beforeStats = {
        avgCpc: sh.getAverageCpc().toFixed(2),
        avgPos: sh.getAveragePosition(),
        clicks: sh.getClicks(),
        conversions: sh.getConversions(),
        cost: sh.getCost(),
        qs: dbData.qsStart
      };

      var attachments = [];
      for(var s in stats) {
        attachments.push({
          title: s,
          text: 'is '+stats[s]+ (stats[s]>=beforeStats[s] ? ' (up' : '(down') +' from '+beforeStats[s]+')'
        });
      }

      mySlack.msg({
        text: LABEL_NAME+" > "+ACC+" > "+type+" > "+dbData.name+" in "+dbData.campaign+" passed "+stats[trigger]+" "+trigger+" in "+daysPassed+" days (was "+beforeStats[trigger]+" in the previous period)",
        attachments: attachments
      });

      return true;
    }

    return false;
  }

  function timeBasedCheck(type,handler,trigger,dbData) {

    var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd(),NOW.yyyymmdd());

    var stats = {
      avgCpc: sh.getAverageCpc().toFixed(2),
      avgPos: sh.getAveragePosition(),
      clicks: sh.getClicks(),
      conversions: sh.getConversions(),
      cost: sh.getCost(),
      qs: (type == 'keywords') ? handler.getQualityScore() : 0
    };

    var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd((dbData.trigger[trigger]+1)*-7),new Date(dbData.started).yyyymmdd());

    var beforeStats = {
      avgCpc: sh.getAverageCpc().toFixed(2),
      avgPos: sh.getAveragePosition(),
      clicks: sh.getClicks(),
      conversions: sh.getConversions(),
      cost: sh.getCost(),
      qs: dbData.qsStart
    };

    var attachments = [];
    for(var s in stats) {
      attachments.push({
        title: s,
        text: 'is '+stats[s]+ (stats[s]>=beforeStats[s] ? ' (up' : '(down') +' from '+beforeStats[s]+')'
      });
    }

    mySlack.msg({
      text: LABEL_NAME+" > "+ACC+" > "+type+" > "+dbData.name+" in "+dbData.campaign+" passed "+(dbData.trigger[trigger]+1)+" "+trigger,
      attachments: attachments
    });

  }

  // by Jason Knight
  function nest(base,arr) {
    for (var obj = base, ptr = obj, i = 0, j = arr.length; i < j; i++)
      ptr = (ptr[arr[i]] = {});
    return obj;
  }

}
Neugierig geworden?
Skalieren auch Sie mit der richtigen Marketing Technology. Sprechen Sie uns an.