Was dieses Skript macht:
Wenn "Auto Exclude" in den "Skript-Einstellungen" auf "YES" gesetzt ist:
Werden für AdGroups mit dem Label "Automate Negatives" alle Suchbegriffe der letzten 7 Tage mit den Keywords in der Adgroup abgeglichen. Wenn der Suchbegriff nicht exakt mit einem Keyword in der Adgroup übereinstimmt, wird er als negatives Keyword hinzugefügt.
Den Reporting Zeitraum kannst Du in den Einstellungen festlegen. Das Skript schließt nur enge Varianten von exact matches aus (Du kannst also, Phrase- und Broad Matches weiterhin in derselben Ad-Group verwenden).
Wenn "Auto Exclude" auf "NO" gesetzt ist: Das Skript nimmt keine Änderungen am Konto vor, sondern gibt nur Berichte darüber aus, was "getan worden wäre".
Implementierung des Skripts
1. Labele die Adgroups, die du in deinem Konto anpassen möchtest, mit dem Label "Automate negatives", achte auf Groß- und Kleinschreibung!
2. Erstelle eine Kopie dieses Google Sheets und füge die URL Deines Sheets in Zeile 2 des Skripts ein:
Kopiere dieses Sheet.
3. Klicke auf "Ausführen" und autorisiere das Skript
4. In den ersten beiden Grafiken unter "Übersichtsdiagramme" kannst du die Kosten und Konversionen nach Treffertyp sehen. In den beiden darauf folgenden Grafiken kannst du sehen, welche Suchbegriffe ausgeschlossen und welche nicht ausgeschlossen sind (alle nicht exakten Treffer werden ausgeschlossen)
5. In "Performance Deepdive" kannst du auch die Leistung der Ad-Groups auf CPL- und ROAS-Ebene überprüfen.
6. Wenn die Daten und Ausschlüsse für dich sinnvoll sind, kannst du die Einstellung in den "Skript-Einstellungen" ändern, um die Search Terms künftig automatisch auszuschließen
Datenanalyseinformationen: Es kann vorkommen, dass der Prozentsatz der exakten Übereinstimmung (enge Variante) höher ist als die tatsächlich ausgeschlossenen Kosten, wenn das Skript ausgeführt wird. Dies geschieht, wenn ein Exact Match kürzlich hinzugefügt wurde. Im Abfragebericht wird er immer noch als enge Variante für vergangene Daten angezeigt. Tatsächlich handelt es sich jedoch bereits um einen exact Match.
Code für das Skript (Copy & Paste in Google Ads)
var dataToWrite = [];
function main() {
// Provide the Google Sheets URL here
var spreadsheetURL = "YOUR_SHEET_URL_HERE";
//no changes below here please
if (spreadsheetURL.indexOf('docs.google.com') == -1) {
Logger.log("Please make sure to provide a valid SpreadsheetURL")
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetURL);
var sheet = spreadsheet.getSheetByName("Search Term Data"); // Negatives are in the first sheet
var settingsSheet = spreadsheet.getSheetByName("Script Settings");
clearSheetExceptHeadline(spreadsheetURL, "Search Term Data")
//Script settings
var automateNegatives = settingsSheet.getRange("D8").getValue();
var timeframe = settingsSheet.getRange("D4").getValue()
var reportingTimeframe = getDateRangeForTimeframe(timeframe);
// Iterate through all ad groups in the account
var adGroupsIterator = AdsApp.adGroups().get();
while (adGroupsIterator.hasNext()) {
var adGroup = adGroupsIterator.next();
var adGroupId = adGroup.getId();
var adGroupName = adGroup.getName();
// Check if the ad group has a label named "Automate negatives"
if (hasLabel(adGroup, "Automate negatives")) {
// Fetch all keywords in the ad group
var keywordsIterator = adGroup.keywords().get();
var adGroupKeywords = [];
while (keywordsIterator.hasNext()) {
var keyword = keywordsIterator.next();
adGroupKeywords.push(keyword.getText());
}
// Get the search terms for the current ad group ordered by clicks in the last 30 days
var searchTermsQuery = "SELECT Query, Cost, Clicks, Conversions, ConversionValue, QueryMatchTypeWithVariant FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
"WHERE AdGroupId = " + adGroupId +
" AND Clicks > 0 " +
"DURING " + reportingTimeframe + " " +
"ORDER BY Clicks DESC ";
var searchTermsIterator = AdsApp.report(searchTermsQuery).rows();
while (searchTermsIterator.hasNext()) {
var searchTerm = searchTermsIterator.next();
var searchTermText = searchTerm["Query"].trim();
var searchTermCost = searchTerm["Cost"]
var searchTermClicks = searchTerm["Clicks"]
var searchTermConversions = searchTerm["Conversions"]
var searchTermConversionValue = searchTerm["ConversionValue"]
var matchTypeVariant = searchTerm["QueryMatchTypeWithVariant"]
var currentDate = new Date();
// Check if the search term is not in the ad group's keywords
if (adGroupKeywords.indexOf(searchTermText) === -1 && matchTypeVariant == "exact (close variant)") {
if(automateNegatives == "Yes"){
adGroup.createNegativeKeyword("[" + searchTermText + "]");
Logger.log("Excluded search term in ad group '" + adGroupName + "': " + searchTermText);
}
// Log to Google Sheets
var rowData = [currentDate, adGroupName, "[" + searchTermText + "]", searchTermCost, searchTermClicks, searchTermConversions, searchTermConversionValue, matchTypeVariant, "Search Term Excluded"];
dataToWrite.push(rowData);
}
else {var rowData = [currentDate, adGroupName, "[" + searchTermText + "]", searchTermCost, searchTermClicks, searchTermConversions, searchTermConversionValue, matchTypeVariant, "Search Term Not Excluded"];
dataToWrite.push(rowData);}
}
}
}
if (dataToWrite.length > 0) {
var sheet = SpreadsheetApp.openByUrl(spreadsheetURL).getSheetByName("Search Term Data");
sheet.getRange(sheet.getLastRow() + 1, 1, dataToWrite.length, dataToWrite[0].length).setValues(dataToWrite);
}
else {Logger.log("No data to log. Please make sure at least one adgroup contains the label 'Automate negatives' ")};
}
// Helper function to check if an ad group has a specific label
function hasLabel(adGroup, labelName) {
// Convert the labelName parameter to lowercase for case-insensitive comparison
var normalizedLabelName = labelName.toLowerCase();
// Get all labels for the adGroup
var labels = adGroup.labels().get();
// Iterate through all labels
while (labels.hasNext()) {
var label = labels.next();
// Compare the label name in lowercase to the normalized label name
if (label.getName().toLowerCase() === normalizedLabelName) {
return true; // Return true if a match is found
}
}
return false; // Return false if no matching label is found
}
function clearSheetExceptHeadline(spreadsheetURL, sheetName) {
// Open the spreadsheet by URL
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetURL);
// Access the specified sheet by name
var sheet = spreadsheet.getSheetByName(sheetName);
// Check if the sheet exists
if (!sheet) {
Logger.log("Sheet not found: " + sheetName);
return; // Exit the function if the sheet does not exist
}
//remove filters first
var filter = sheet.getFilter();
if (filter) {
filter.remove();}
// Get the number of rows and columns to clear
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
// Ensure there are rows to clear beyond the headline
if (lastRow > 1) {
// Clear all content starting from the second row
sheet.getRange(2, 1, lastRow - 1, lastColumn).clearContent();
// After clearing the content, delete the empty rows to avoid having a large gap.
// This action is not immediate, hence checking if the sheet still has more than one row before attempting to delete.
var rowsAfterClear = sheet.getLastRow();
if (rowsAfterClear > 1) {
// Delete rows from the second row to the end of the sheet, adjusting for the deletion offset.
sheet.deleteRows(2, rowsAfterClear - 1);
}
}
}
function getDateRangeForTimeframe(timeframe) {
var today = new Date();
var endDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1); // Set to yesterday
var startDate = new Date(); // Initialized but will be overwritten
switch (timeframe) {
case 'LAST_7_DAYS':
startDate.setDate(endDate.getDate() - 7);
break;
case 'LAST_14_DAYS':
startDate.setDate(endDate.getDate() - 14);
break;
case 'LAST_30_DAYS':
startDate.setDate(endDate.getDate() - 30);
break;
case 'LAST_90_DAYS':
startDate.setDate(endDate.getDate() - 90);
break;
case 'LAST_180_DAYS':
startDate.setDate(endDate.getDate() - 180);
break;
default:
throw new Error('Invalid timeframe specified');
}
// Format dates as YYYYMMDD
function formatAsYYYYMMDD(date) {
var dd = date.getDate();
var mm = date.getMonth() + 1; // January is 0!
var yyyy = date.getFullYear();
if (dd < 10) dd = '0' + dd;
if (mm < 10) mm = '0' + mm;
return '' + yyyy + mm + dd;
}
var startDateStr = formatAsYYYYMMDD(startDate);
var endDateStr = formatAsYYYYMMDD(endDate);
// Return formatted date range
return startDateStr + ',' + endDateStr;
}