Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Identifizieren von schlecht performenden Keywords und Produkten in AdWords
/**************************************************************/
/****** "Gurkenfinder"-Script für Google AdWords *******/
/**************************************************************/
/* v1.3 2017 Markus Baersch (@mbaersch)
Reduzierte Non-MCC-Fassung
gandke marketing & software - www.gandke.de */
/*********** Start Setup **********************/
var emailAddress = "mailadresse@hier.eintragen";
var emailName = "" ;
var chkLabel = "PC:DoCheck" ; //Nur Kampagnen mit diesem Label werden berücksichtigt
var chkPauseLabel = "PC:Paused" ; //Optionales Label für pausierte Keywords
//Standard- / Schwellwerte & Einstellungen
var chkType = 'REL'; //REL = Umsatz/Kosten, CNV = Anzahl Conversions; CPA = Kosten/Conversion
var trshClicks = 200; //Schwellwert für erzielte Klicks zur Untersuchung
var chkDateRange = 'ALL_TIME'; //Mögl. Werte: TODAY, YESTERDAY, LAST_7_DAYS, THIS_WEEK_SUN_TODAY,
//LAST_WEEK, LAST_14_DAYS, LAST_30_DAYS, LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT,
//THIS_MONTH, LAST_MONTH, ALL_TIME
//REL
var trshCnvCostRelReport = 2; //Melden ab CVal/Cost unter 2
var trshCnvCostRelPause = 0.5; //Pausieren, wenn CVal/Cost unter 0,5
//CNV
var trshCnvReport = 2; //Melden bei weniger als 2 Conversions
var trshCnvPause = 0.5; //Pausieren unter 0,5 Conversions
//CPA
var trshCpaReport = 30; //Melden bei CPA über 30,--
var trshCpaPause = 50; //Pausieren bei CPA > 40,--
var setupDays = [1,3,5] ; //Wochentage; Sonntag = 0. Hier: nur Montags, Mittwochs und Freitags ausführen
var debug = false ;
/*********** Ende Setup ***********************/
var Wochentage = new Array("Sonntag", "Montag", "Dienstag", "Mittwoch",
"Donnerstag", "Freitag", "Samstag");
function main() {
var chkShResults = new Array();
var chkKwResults = new Array();
//Start nur an ausgewählten Wochentagen
var wTag = getAccountCurrentDateTime().getDay();
if (debug || (setupDays.indexOf(wTag) >= 0)) {
w2log('Vorgang gestartet.') ;
var mandantId = AdWordsApp.currentAccount().getCustomerId();
var mandantName = AdWordsApp.currentAccount().getName();
//Labels anlegen, wenn nicht vorhanden
needsLabel(chkLabel) ;
if (chkPauseLabel) needsLabel(chkPauseLabel) ;
//Ergebnisse für Keyword- und Shopping-Kampagnen ermitteln
chkKwResults = checkKeywordPerformance();
chkShResults = checkProductPerformance();
if (debug) {
w2log(formatResults(chkShResults, 'S', true)) ;
w2log(formatResults(chkKwResults, 'K', true)) ;
}
w2log('Vorgang abgeschlossen.') ;
if ((!emailAddress) || (emailAddress == "mailadresse@hier.eintragen")) {
w2log("Es wurde keine Mailadresse angegeben, Report wird nicht versendet.");
return "";
} else {
//Mail mit Ergebnissen erstellen
if (!debug) {
if (emailName) emailName = ' ('+emailName+')' ;
var htmlResult = "<br><br><hr><h2>Ergebnisse für \"" + mandantName + "\"</h2>\n" + "\n" +
formatResults(chkShResults, 'S', false) + "\n" +
formatResults(chkKwResults, 'K', false) ;
var mlSubject = 'Gurkenfinder-Report'+ emailName +' für '+ Wochentage[wTag] + ', den '+
Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), "dd.MM.yyyy") ;
var resPreText = '<style type="text/css">body,small,h1,h2,h3,p,ul,li{font-size:11px;font-family:arial,sans-serif;color:#222}'+
'td{padding-right:13px;white-space:nowrap;vertical-align:top}b.r{color:red}h1,h2,h3{margin:20px 0 0 0;'+
'padding:0 0 2px 0;font-size:16px;color:#C44E00}h2 a{color:#C44E00}h1{font-size:18px}h3{font-size:13px;color:#14941D}</style>'+
'<h1>'+mlSubject+'</h1>' ;
var resBody = resPreText + htmlResult + '<p>llap!</p>' ;
if (resBody.length >= 200*1024) {
w2log("Report zu groß für E-Mail.") ;
//nicht senden...
resBody = resPreText + '<p>Der Report ist leider zu groß für den Versand per E-Mail.</p>' ;
w2log(formatResults(chkShResult, 'S', true)) ;
w2log(formatResults(chkKwResult, 'K', true)) ;
}
//Mail senden
MailApp.sendEmail({
to: emailAddress,
subject: mlSubject,
htmlBody: resBody,
});
w2log('Info wurde per Mail gesendet.');
}
}
} else
w2log('Heute setze ich aus...') ;
}
function checkKeywordPerformance() {
//Aktive Kampagnen abrufen
var campaignIterator = AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_ALL ['"+chkLabel+"']")
.get();
var cmpFound = false;
var cResults = new Array();
//Keyword-Reports für ausgewählte Kampagnen abrufen
while (campaignIterator.hasNext()) {
cmpFound = true;
var campaign = campaignIterator.next();
var repStatement = 'SELECT Id, CampaignName, AdGroupName, Criteria, Clicks, '+
'Ctr, AveragePosition, AverageCpc, Cost, Conversions, CostPerConversion, ConversionValue ' +
'FROM KEYWORDS_PERFORMANCE_REPORT WHERE CampaignName="' + campaign.getName() +
'" AND Status = ENABLED AND Clicks > '+trshClicks ;
if (chkDateRange != "ALL_TIME") repStatement += ' DURING '+chkDateRange ;
var report = AdWordsApp.report(repStatement);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var cst = row['Cost'];
var cstraw = getValue(cst) ;
var rev = row['ConversionValue'];
var revraw = getValue(rev) ;
var rel = formatFloat(revraw / cstraw) ;
var cnvs = row['Conversions'] ;
var cpa = row['CostPerConversion'] ;
var cnvsraw = getValue(cnvs);
var cparaw = getValue(cpa) ;
if ((cnvsraw === 0) && (chkType === 'CPA')) cpa = 999999;
if (((chkType === 'REL') && (rel < trshCnvCostRelReport)) ||
((chkType === 'CPA') && (cparaw > trshCpaReport)) ||
((chkType === 'CNV') && (cnvsraw < trshCnvReport)))
{
var aktRow = new Array();
aktRow.push(row['CampaignName']);
aktRow.push(row['AdGroupName']);
if (((chkType === 'REL') && (rel < trshCnvCostRelPause)) ||
((chkType === 'CPA') && (cpa > trshCpaPause)) ||
((chkType === 'CNV') && (cnvs < trshCnvPause)))
{
var kw = AdWordsApp.keywords().withCondition('Id='+row['Id']).get().next();
if (chkPauseLabel != "") addLbl(kw, chkPauseLabel)
kw.pause();
aktRow.push('<b style="color:red">' + row['Criteria'] + ' [pausiert]</b>');
} else
aktRow.push(row['Criteria']);
aktRow.push(formatInt(row['Clicks']));
aktRow.push(row['Ctr']);
aktRow.push(row['AverageCpc']);
aktRow.push(cst);
aktRow.push(row['AveragePosition']);
aktRow.push(cnvs);
aktRow.push(cpa);
aktRow.push(row['ConversionValue']);
aktRow.push(rel);
cResults.push(aktRow) ;
}
} //Reportiterator
} //Kampagneniterator
if (!cmpFound) cResults = "NIX" ;
return cResults;
}
function checkProductPerformance() {
//Aktive Kampagnen abrufen
var campaignIterator = AdWordsApp.shoppingCampaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_ALL ['"+chkLabel+"']")
.get();
var cmpFound = false;
var cResults = new Array();
//Shopping-Report für ausgewählte Kampagnen abrufen
while (campaignIterator.hasNext()) {
cmpFound = true;
var campaign = campaignIterator.next();
var repStatement = 'SELECT OfferId, Brand, ProductTypeL1, CategoryL1, AdGroupId, CampaignName, AdGroupName, Clicks, Ctr, AverageCpc, Cost, '+
'Conversions, CostPerConversion, ConversionValue ' +
'FROM SHOPPING_PERFORMANCE_REPORT WHERE CampaignName="' + campaign.getName() + '" AND Clicks > '+trshClicks ;
if (chkDateRange != "ALL_TIME") repStatement += ' DURING '+chkDateRange ;
var report = AdWordsApp.report(repStatement);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var adg = row['AdGroupId'];
var prdId = row['OfferId'];
var cst = row['Cost'];
var cstraw = getValue(cst) ;
var rev = row['ConversionValue'];
var revraw = getValue(rev) ;
var rel = formatFloat(revraw / cstraw) ;
var cnvs = row['Conversions'] ;
var cpa = row['CostPerConversion'] ;
var cnvsraw = getValue(cnvs);
var cparaw = getValue(cpa) ;
if ((cnvsraw === 0) && (chkType === 'CPA')) cpa = 999999;
if (((chkType === 'REL') && (rel < trshCnvCostRelReport)) ||
((chkType === 'CPA') && (cparaw > trshCpaReport)) ||
((chkType === 'CNV') && (cnvsraw < trshCnvReport)))
{
//Ist das Produkt noch aktiv oder schon deaktiviert?
var itemInactive = false ;
var adgrp = AdWordsApp.shoppingAdGroups().withIds([adg]).get().next();
var adgrps = adgrp.productGroups().get();
while (adgrps.hasNext()) {
var grp = adgrps.next();
if (grp.getDimension() == 'ITEM_ID')
if (grp.asItemId().getValue() == prdId)
if (grp.asItemId().isExcluded()) {
itemInactive = true ;
w2log('INFO: ' + prdId + ' übergangen, weil bereits deaktiviert');
break;
}
}
if (!itemInactive) {
var aktRow = new Array();
aktRow.push(row['CampaignName']);
aktRow.push(row['AdGroupName']);
if (((chkType === 'REL') && (rel < trshCnvCostRelPause)) ||
((chkType === 'CPA') && (cpa > trshCpaPause)) ||
((chkType === 'CNV') && (cnvs < trshCnvPause)))
{
aktRow.push('<b style="color:red">' + row['OfferId'] + ' - manuell pausieren!</b>');
} else
aktRow.push(row['OfferId']);
aktRow.push(row['Brand']);
aktRow.push(row['ProductTypeL1']);
aktRow.push(row['CategoryL1']);
aktRow.push(formatInt(row['Clicks']));
aktRow.push(row['Ctr']);
aktRow.push(row['AverageCpc']);
aktRow.push(row['Cost']);
aktRow.push(cnvs);
aktRow.push(cpa);
aktRow.push(row['ConversionValue']);
aktRow.push(rel);
cResults.push(aktRow) ;
}
}
} //Reportiterator
} //Kampagneniterator
if (!cmpFound) cResults = "NIX" ;
return cResults;
}
/**************** Helper ******************/
//Ergebnisse für Log oder Mail ausgeben
function formatResults(arr, tp, forLog) {
var res = "" ;
if (arr === 'NIX') return "";
var doHtmlTitle = true ;
if (tp === 'S') {
var nam = "Aktive Produkte";
var hdr = ['Kampagne', 'Anzeigengruppe', 'Produkt', 'Marke', 'Prod-Typ', 'Kategorie', 'Klicks', 'CTR', 'CPC', 'Kosten', 'Conv.', 'Kost./Conv.', 'Umsatz', 'Umsatz/Kosten'] ;
} else {
var nam = "Aktive Keywords";
var hdr = ['Kampagne', 'Anzeigengruppe', 'Keyword', 'Klicks', 'CTR', 'CPC', 'Kosten', 'Position', 'Conv.', 'Kost./Conv.', 'Umsatz', 'Umsatz/Kosten'] ;
}
if (chkType === 'REL')
var ttl = nam + " mit Verhältnis von Umsatz/Kosten unter "+trshCnvCostRelReport + ' und mindestens '+trshClicks+' Klicks' ;
else if (chkType === 'CPA')
var ttl = nam + " mit Kosten/Conversion über "+trshCpaReport + ' und mindestens '+trshClicks+' Klicks' ;
else
var ttl = nam + " mit weniger als "+trshCnvReport + ' Conversions und mindestens '+trshClicks+' Klicks' ;
ttl += " im Zeitraum "+chkDateRange;
if (forLog) {
res = "\n"+ttl + ":\n" ;
if (arr.length == 0) res += "- Keine Einträge -" ; else {
res += hdr.join('; ').replace(/<br>/g,"") + '\n';
for (var i=0;i<arr.length;i++) {
if(typeof arr[i] === 'string')
res += arr[i] + '\n';
else
res += arr[i].join('; ') + '\n';
}
}
} else {
if (doHtmlTitle) {
res = "\n<h3>" + ttl + "</h3>\n";
if (arr.length == 0) res += "<small>Keine Einträge</small>\n" ; else {
res += "<table>\n";
if (hdr.length > 0) res += "<tr><td class=\"first\"><b>"+ hdr.join('</b></td>\t<td><b>') + '</b>\t</td></tr>\n';
for (var i=0;i<arr.length;i++) {
if(typeof arr[i] === 'string')
res += "<tr><td class=\"first\">" + arr[i] + "</td>\t</tr>\n";
else
res += "<tr><td class=\"first\">"+arr[i].join('</td>\t<td>').replace(/\n/gm,"<br>") + '</td>\t</tr>\n';
}
res += "</table>\n";
}
}
}
return res ;
}
//angefordertes Label anlegen, wenn noch nicht vorhanden
function needsLabel(lbl) {
if (!(AdWordsApp.labels().withCondition("Name = '" + lbl + "'").get().hasNext())) {
AdWordsApp.createLabel(lbl, "Markiert Daten für Controlling / Steuerung per Script. " +
"Fragen dazu? Kontakt unter www.gandke.de)", "#C44E00");
//Da das Label hier ggf. schon direkt gebraucht wird, aber nicht zwingend schon genutzt werden kann, lieber noch ein wenig warten...
Utilities.sleep(1000);
}
return true ;
}
function w2log(txt) {
//Zu viele Details im Log bringen nichts - daher hier jeden Eintrag kürzen. Um Überlauf zu vermeiden ggf. hier als Ergänzung
//die Gesamtlänge aller Einträge beobachten und begrenzen
if (txt.length > (4096)) {
Logger.log(txt.substr(0, 4000)+"...\n\nACHTUNG:Text für Log gekürzt!!!") ;
return false;
} else {
try {Logger.log(txt) ;} catch(e) { }
return true;
}
}
//Ausgleich der ggf. bestehenden Zeitdifferenz zwischen Zeitstempel des
//ausf. Systems (PST) und Planungszeit im Konto
function getAccountCurrentDateTime() {
return new Date(Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), "MMM dd,yyyy HH:mm:ss"));
}
//Label ohne Fehlermeldungen in der Vorschau bei fehlendm Label hinzufügen
function addLbl(entity, lblname) {
if (!(entity.labels().withCondition("Name = '" + lblname + "'").get().hasNext()))
try { entity.applyLabel(lblname) ; } catch(e) { } ;
}
function getValue(v) {
return parseFloat(v.toString().replace(/,/g,''));
}
function formatInt(number) {
number = number || 0;
thousand = ",";
var negative = number < 0 ? "-" : "",
i = parseInt(number = Math.abs(+number || 0).toFixed(0), 10) + "",
j = (j = i.length) > 3 ? j % 3 : 0;
return negative + (j ? i.substr(0, j) + thousand : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousand) ;
}
function formatFloat(amount) {
var delimiter = ","; // replace comma if desired
amount = amount.toFixed(2).toString() ;
var a = amount.split('.',2)
var d = a[1];
var i = parseInt(a[0]);
if(isNaN(i)) { return ''; }
var minus = '';
if(i < 0) { minus = '-'; }
i = Math.abs(i);
var n = new String(i);
var a = [];
while(n.length > 3)
{
var nn = n.substr(n.length-3);
a.unshift(nn);
n = n.substr(0,n.length-3);
}
if(n.length > 0) { a.unshift(n); }
n = a.join(delimiter);
if(d.length < 1) { amount = n; }
else { amount = n + '.' + d; }
amount = minus + amount;
return amount;
}
@mbaersch

This comment has been minimized.

Copy link
Owner Author

commented Dec 2, 2017

Google AdWords Script zur Identifizierung schlechter Keywords in Suchkampagnen und Produkten in Shopping-Kampagnen

Das Script sucht Keywords und Produkte, die bei Erreichen eines Schwellwertes an Klicks die definierten Ziele anhand CR, CPL oder ROAS nicht erreicht haben und listet diese auf bzw, pausiert Keywords, die unter einen zweiten Schwellwert fallen.

Voraussetzungen

Conversiontracking muss für die zu untersuchenden Kampagnen stattfinden und für ROAS-Steuerung müssen auch Umsätze messbar sein.

Installation

Nachdem mindestens eine geeignete Kampagne im Konto mit einem entsprechenden Label versehen wurde, kann das Script getestet und anschließend eingesetzt werden, Es können Shopping- oder Suchnetzwerk-Kampagnen berücksichtigt werden.

Der Scriptcode wird im Konto unter “Gemeinsam genutzte Bibliothek -> Bulk-Vorgänge -> Scripts” eingetragen. Dazu auf “+Script” klicken, einen Namen vergeben und den kopierten Scriptcode (siehe unten) über die Zwischenablage einfügen. In der Konfiguration sind für einen ersten Durchlauf keine Einstellungen neben der bei der bzw. den Kampagne(n) verwendeten Label-Bezeichnung erforderlich. Bevor das Script gestartet oder die Vorschau genutzt werden kann, ist aber noch eine Autorisierung erforderlich.

Verwendung

Wird das Script gestartet oder (nach Anlage des erforderlichen Labels) in der Vorschau betrachtet, werden anhand der definierten Schwellwerte und der gewählten Art der Steuerung (siehe Kommentare im Script) Keywords oder Produkte identifiziert, die die Schwellwerte zur Leistung unterschreiten und in einem Report ausgegeben bzw. ggf. direkt pausiert (nur Keywords , keine Produkte). Nach Abschluss wird das Ergebnis entweder (debug=true) im Protokoll oder per Mail (Standardeinstellung) ausgegeben.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.