有时您希望完全匹配就是:完全匹配。您知道,这样您就可以向真正热衷于观看您的广告的人展示广告,而不是那种热衷于观看的人。它可以对点击率和每次点击费用产生真正的影响,那么您为什么不希望此选项仍然存在呢?
脚本是如何工作的?
它查看过去 30 天内活动广告系列中的“紧密变体”搜索查询。如果查询与确切关键字不完全匹配,则该查询将添加为广告组否定。您也可以收到一封包含新底片列表的电子邮件。
与旧版本不同,每个广告组可以有多个关键字。如果特定关键字未设置为完全匹配,则脚本不会添加这些否定词。如果有多个确切的关键字,脚本会在排除之前确保查询与所有关键字都不同。
还可以选择安排脚本来分析活动,而无需自动进行更改。然后您可以手动查看建议的底片列表,并在您对它们感到满意时创建它们。
有许多警告:
- 它仅适用于小型帐户(我估计最多 10,000 个关键字)。如果您的帐户较大或有很多帐户,则需要通过 API 执行此操作。如果你自己做不到,你可以寻求合作伙伴。
- 它只是回顾性地添加否定关键字,即必须出现并识别相似的变体才能排除它。因此,在此过程发生时,您将花费一些费用。
- 您可能会在大型帐户中遇到否定关键字限制。
- 如果您想积极应对交叉污染,有一些技术方法可以做到这一点。(例如,我们在 Brainlabs Tech Stack 中有一个用于此目的的模块,但它依赖于在广告系列或广告组级别将精确关键字和广泛关键字分开的清晰结构)。
我如何使用它?
现在我们已经介绍了注意事项,让我们进入脚本。要重新控制您的确切关键字,请在 Google Ads 中创建一个新脚本并复制以下代码。然后,根据您的情况修改以下设置:
- CampaignNameContains 用于过滤脚本查看的活动。如果您只想让脚本查看名称中包含某些单词或短语的营销活动,请将这些单词或短语放在方括号中、双引号中并用逗号分隔。例如,如果 CampaignNameContains 为 [“Brand”, “Generic”],则仅包含名称包含“brand”或“generic”的广告系列。
- CampaignNameDoesNotContain 是相同的,但对于您希望脚本忽略的营销活动名称中的单词或短语。例如,如果 CampaignNameDoesNotContain 是 [“Misspellings”, “Competitor”],则任何名称包含“错误拼写”或“竞争者”的营销活动都将被忽略。
- CampaignNameContains 和 campaignNameDoesNotContain 不区分大小写。
- 将它们留空,[],以涵盖所有广告系列。
- 如果您需要在任一变量的营销活动名称文本中添加双引号,请在其前添加反斜杠。
- 如果 makeChanges 为 true,则脚本将为您创建底片。如果您只想在进行更改之前检查底片,则可以将其设置为 false。
- emailAddresses 是您放置电子邮件地址的地方,如果您想收到新底片的 CSV(或将制作的底片,如果 makeChanges 为 false)。
- 如果您不想要电子邮件,请将其保留为“”。
完成预览运行并感到满意后,您可以将脚本安排在每周或每月运行的时间表上,并完全按照您的需要保留您的帐户。
精确匹配脚本代码
// ID: 5a92f7fa7a689734ad71adb3579baff8
/**
*
* Make Exact Match Exact
*
* Adds negatives for any search query that doesn't actually exactly match an exact
* match keyword.
*
* Version: 2.0
* Google AdWords Script maintained on brainlabsdigital.com
*
*/
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Options
var campaignNameDoesNotContain = [];
// Use this if you want to exclude some campaigns. Case insensitive.
// For example ["Brand"] would ignore any campaigns with 'brand' in the name,
// while ["Brand","Competitor"] would ignore any campaigns with 'brand' or
// 'competitor' in the name.
// Leave as [] to not exclude any campaigns.
var campaignNameContains = [];
// Use this if you only want to look at some campaigns. Case insensitive.
// For example ["Brand"] would only look at campaigns with 'brand' in the name,
// while ["Brand","Generic"] would only look at campaigns with 'brand' or 'generic'
// in the name.
// Leave as [] to include all campaigns.
// Choose whether the negatives are created, or if you just get an email to review
var makeChanges = true;
// These addresses will be emailed when the tool is run, eg "[email protected]"
// If there are multiple addresses then separate them with commas, eg "[email protected], [email protected]"
// Leave as "" to not send any emails
var emailAddresses = '';
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
function main() {
var campaigns = {};
var adGroups = {};
var exactKeywords = [];
var exactGroupIds = {};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Pull a list of all exact match keywords in the account
var campaignIds = getCampaignIds();
var report = AdWordsApp.report(
'SELECT AdGroupId, Id, Criteria '
+ 'FROM KEYWORDS_PERFORMANCE_REPORT '
+ 'WHERE Impressions > 0 AND KeywordMatchType = EXACT '
+ 'AND CampaignId IN [' + campaignIds.join(',') + '] '
+ 'AND AdGroupStatus IN [ENABLED, PAUSED] '
+ 'AND Status IN [ENABLED, PAUSED] '
+ 'DURING LAST_30_DAYS'
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var keywordId = row.Id;
var adGroupId = row.AdGroupId;
exactKeywords.push(adGroupId + '#' + keywordId);
exactGroupIds[adGroupId] = true;
if (!adGroups.hasOwnProperty(adGroupId)) {
adGroups[adGroupId] = [
[],
[],
[]
];
}
adGroups[adGroupId][2].push(row.Criteria.toLowerCase().trim());
}
exactGroupIds = Object.keys(exactGroupIds);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Remove ad groups with non-exact keywords
var nonExactGroupIds = {};
for (var i = 0; i < exactGroupIds.length; i += 10000) {
var exactGroupIdsChunk = exactGroupIds.slice(i, i + 10000);
var report = AdWordsApp.report(
'SELECT AdGroupId, Id '
+ 'FROM KEYWORDS_PERFORMANCE_REPORT '
+ 'WHERE KeywordMatchType != EXACT AND IsNegative = FALSE '
+ 'AND AdGroupId IN [' + exactGroupIdsChunk.join(',') + '] '
+ 'AND Status IN [ENABLED, PAUSED] '
+ 'DURING LAST_30_DAYS'
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var adGroupId = row.AdGroupId;
nonExactGroupIds[adGroupId] = true;
}
}
var onlyExactGroupIds = [];
for (var i = 0; i < exactGroupIds.length; i++) {
if (nonExactGroupIds[exactGroupIds[i]] == undefined) {
onlyExactGroupIds.push(exactGroupIds[i]);
}
}
Logger.log(onlyExactGroupIds.length + ' ad groups (with only exact keywords) were found.');
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Pull a list of all exact (close variant) search queries
for (var i = 0; i < onlyExactGroupIds.length; i += 10000) {
var onlyExactGroupIdsChunk = onlyExactGroupIds.slice(i, i + 10000);
var report = AdWordsApp.report(
'SELECT Query, AdGroupId, CampaignId, KeywordId, KeywordTextMatchingQuery, Impressions, QueryMatchTypeWithVariant, AdGroupName '
+ 'FROM SEARCH_QUERY_PERFORMANCE_REPORT '
+ 'WHERE AdGroupId IN [' + onlyExactGroupIdsChunk.join(',') + '] '
+ 'DURING LAST_30_DAYS'
);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var adGroupId = parseInt(row.AdGroupId);
var campaignId = parseInt(row.CampaignId);
var keywordId = parseInt(row.KeywordId);
var searchQuery = row.Query.toLowerCase().trim();
var keyword = row.KeywordTextMatchingQuery.toLowerCase().trim();
var matchType = row.QueryMatchTypeWithVariant.toLowerCase().trim();
if (keyword !== searchQuery && matchType.indexOf('exact (close variant)') !== -1) {
if (adGroups[adGroupId][2].indexOf(searchQuery) > -1) {
// This query is a positive keyword in the ad group
// so we don't want to add is as a negative
continue;
}
if (!campaigns.hasOwnProperty(campaignId)) {
campaigns[campaignId] = [
[],
[]
];
}
campaigns[campaignId][0].push(searchQuery);
campaigns[campaignId][1].push(adGroupId + '#' + keywordId);
if (!adGroups.hasOwnProperty(adGroupId)) {
adGroups[adGroupId] = [
[],
[]
];
}
adGroups[adGroupId][0].push(searchQuery);
adGroups[adGroupId][1].push(adGroupId + '#' + keywordId);
}
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Parse data correctly
var adGroupIds = [];
var adGroupNegatives = [];
for (var x in adGroups) {
adGroupIds.push(parseInt(x));
adGroupNegatives.push([]);
for (var y = 0; y < adGroups[x][0].length; y++) {
var keywordId = adGroups[x][1][y];
var keywordText = adGroups[x][0][y];
if (exactKeywords.indexOf(keywordId) !== -1) {
adGroupNegatives[adGroupIds.indexOf(parseInt(x))].push(keywordText);
}
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Create the new negative exact keywords
var results = [];
for (var i = 0; i < adGroupIds.length; i += 10000) {
var adGroupIdsChunk = adGroupIds.slice(i, i + 10000);
var adGroupIterator = AdWordsApp.adGroups()
.withIds(adGroupIdsChunk)
.get();
while (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var adGroupId = adGroup.getId();
var adGroupName = adGroup.getName();
var adGroupIndex = adGroupIds.indexOf(adGroupId);
var campaignName = adGroup.getCampaign().getName();
for (var j = 0; j < adGroupNegatives[adGroupIndex].length; j++) {
if (makeChanges) {
adGroup.createNegativeKeyword('[' + adGroupNegatives[adGroupIndex][j] + ']');
}
results.push([campaignName, adGroupName, adGroupNegatives[adGroupIndex][j]]);
}
}
}
if (!makeChanges || AdWordsApp.getExecutionInfo().isPreview()) {
Logger.log(results.length + ' new negatives were found.');
} else {
Logger.log(results.length + ' new negatives were created.');
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Email the results
if (emailAddresses == '') {
Logger.log('No email addresses given - not sending email.');
} else if (results.length == 0) {
Logger.log('No changes to email.');
} else {
var attachments = [];
var headers = ['Campaign', 'Ad Group', 'Negative'];
attachments.push(createEscapedCsv([headers].concat(results), 'Ad-Group-Negatives.csv'));
if (!makeChanges || AdWordsApp.getExecutionInfo().isPreview()) {
var verb = 'would be';
} else {
var verb = 'were';
}
var subject = AdWordsApp.currentAccount().getName() + ' - Making Exact Match Exact - ' + Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd');
var body = 'Please find attached a list of the ' + results.length + ' negative keywords that ' + verb + ' added to your account.';
var options = {
attachments: attachments
};
MailApp.sendEmail(emailAddresses, subject, body, options);
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Prepare an array to be made into a CSV
function createEscapedCsv(array, csvName) {
var cells = [];
for (var i = 0; i < array.length; i++) {
var row = [];
for (var j = 0; j < array[i].length; j++) {
row.push(array[i][j].replace(/"/g, '""'));
}
cells.push('"' + row.join('","') + '"');
}
return Utilities.newBlob('ufeff' + cells.join('n'), 'text/csv', csvName);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Get the IDs of campaigns which match the given options
function getCampaignIds() {
var whereStatement = '';
var whereStatementsArray = [];
var campaignIds = [];
for (var i = 0; i < campaignNameDoesNotContain.length; i++) {
whereStatement += "AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE '" + campaignNameDoesNotContain[i].replace(/"/g, '\"') + "' ";
}
if (campaignNameContains.length == 0) {
whereStatementsArray = [whereStatement];
} else {
for (var i = 0; i < campaignNameContains.length; i++) {
whereStatementsArray.push(whereStatement + 'AND CampaignName CONTAINS_IGNORE_CASE "' + campaignNameContains[i].replace(/"/g, '\"') + '" ');
}
}
for (var i = 0; i < whereStatementsArray.length; i++) {
var campaignReport = AdWordsApp.report(
'SELECT CampaignId '
+ 'FROM CAMPAIGN_PERFORMANCE_REPORT '
+ 'WHERE CampaignStatus = ENABLED '
+ whereStatementsArray[i]
+ 'DURING LAST_30_DAYS'
);
var rows = campaignReport.rows();
while (rows.hasNext()) {
var row = rows.next();
campaignIds.push(row.CampaignId);
}
}
if (campaignIds.length == 0) {
throw ('No campaigns found with the given settings.');
}
Logger.log(campaignIds.length + ' campaigns were found.');
return campaignIds;
}