Google 购物是一项很棒的功能,但在关键字出价方面非常不精确。在默认模式下,您只需向 Google 提供产品提要,并希望它与查询很好地同步。因此,我们制作了一个脚本来让您更好地控制关键字,而不是这种相当生硬的方法。
脚本是如何工作的?
该脚本读取您创建的 Google 电子表格并将其与您的广告系列查询进行匹配。如果某个查询与您的某个关键字不匹配,则会添加一个否定关键字以排除该查询。
该脚本将查询分成几部分,并尽可能使短语匹配否定,因此每个否定都可以排除尽可能多的不需要的搜索,同时保留与关键字匹配的任何内容。这有点像黑客,但如果你继续阅读,你会发现结果是值得的。
使用此脚本,您可以只针对关键搜索词运行一个广告系列。但这也意味着您可以像最好的搜索广告一样投放您的购物广告系列——使用单独的广泛和精确广告系列。Exact 广告系列将带来您“一切照旧”的流量,而您的 Broad 广告系列将带来不太可预测的长尾查询。
使用脚本的美妙之处在于您可以获得比没有规模限制的更细粒度的内容。Exact 广告系列包含广告组,每个广告组都有自己的一组关键字,您可以在电子表格中定义这些关键字。因此,您可以将特定关键字与出价和产品进行匹配。这是一个非常方便的功能 – 能够为特定的通用查询决定要显示的产品范围。
我如何使用它?
- 将您的完全匹配关键字及其广告系列和广告组写入 Google 电子表格。请注意,这里没有“紧密变体”——脚本将排除与字母相同的搜索。另请注意,脚本会忽略大小写并将所有标点符号(与符号除外)视为空格,因此此处的“蜘蛛侠”与“蜘蛛侠 s”相同。
- 将您的广泛购物广告系列设置为高优先级。
- 从电子表格中复制所有关键字并将它们添加为完全否定。
- 将您的精确购物广告系列设置为低优先级,这样它只会选择广泛广告系列无法处理的查询。
- 复制脚本并将其粘贴到您的帐户中。在脚本的顶部,您需要将电子表格 URL 更改为包含关键字的 Google Doc 电子表格的 URL。
- 在第一次运行脚本之前,单击“预览”——脚本将运行但实际上不会进行更改,因此您可以看到脚本将添加哪些底片。如果有任何问题,那么日志中会有消息。
- 当看起来一切正常时,单击“立即运行脚本”,这样脚本将真正运行并实际添加底片。然后,安排脚本每天运行——这意味着它可以继续阅读您的新查询并添加新的否定。
稍后添加新的精确关键字:
- 在电子表格的末尾写下关键字及其广告系列和广告组。
- 将关键字作为完全匹配否定添加到广泛广告系列。
- 查看精确广告系列中关键字的广告组,并删除所有会排除该关键字的否定关键字。
由于该脚本每天检查搜索查询,因此它可以为所需的任何内容添加否定词。如果您要向广泛广告系列添加否定词,则不必担心更改精确广告系列的否定词;如果从精确广告系列中删除否定词,则不必担心输入错误的查询 – 脚本一旦出现在 SQR 中,将填写必要的否定。
如果你想分摊你的预算,您可以有多个精确广告系列;因为您使用脚本的否定信息汇集查询,所以您可以有多个具有相同优先级的广告系列。
Google 购物关键字出价脚本代码
// ID: 4136388fb2fa654eca6539d45d76db3e
/**
*
* Exact Match For Shopping
*
* This script reads a list of exact match keywords for Shopping campaigns from a Google Doc,
* and then excludes any search queries from those camapigns if they do not match those keywords.
*
* Version: 1.0
* Google AdWords Script maintained by brainlabsdigital.com
*
*/
function main() {
// Put your spreadsheet's URL here:
var spreadsheetUrl = 'https://docs.google.com/YOUR-SPREADSHEET-URL-HERE';
// Make sure the keywords are in columns A to C in the first sheet.
// ////////////////////////////////////////////////////////////////////////////
var dateRange = 'YESTERDAY';
// By default the script just looks at yesterday's search queries.
var impressionThreshold = 0;
// The script only looks at searches with impressions higher than this threshold.
// Use this if you get a wide range of searches and only want to exclude the highest volume ones.
// Read the spreadsheet
try {
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
} catch (e) {
Logger.log("Problem with the spreadsheet URL: '" + e + "'");
Logger.log('Make sure you have correctly copied in your own spreadsheet URL.');
return;
}
var sheet = spreadsheet.getSheets()[0];
var spreadsheetData = sheet.getDataRange().getValues();
// Record each campaigns' keywords, and the words (of more than 4 characters) that are in the keywords
var keywords = {};
var words = {};
var numberOfKeywords = 0;
var numberOfadGroups = 0;
for (var i = 1; i < spreadsheetData.length; i++) {
var campaignName = spreadsheetData[i][0];
var adGroupName = spreadsheetData[i][1];
if (keywords[campaignName] == undefined) {
keywords[campaignName] = [];
words[campaignName] = {};
}
if (keywords[campaignName][adGroupName] == undefined) {
keywords[campaignName][adGroupName] = [];
words[campaignName][adGroupName] = {};
numberOfadGroups++;
}
var keyword = spreadsheetData[i][2];
keyword = keyword.toLowerCase().replace(/[^wsd&]/g, ' ').replace(/ +/g, ' ').trim();
keywords[campaignName][adGroupName].push(keyword);
numberOfKeywords++;
var keywordWords = keyword.split(' ');
for (var k = 0; k < keywordWords.length; k++) {
if (keywordWords[k].length > 4) {
words[campaignName][adGroupName][keywordWords[k]] = true;
}
}
}
var campaignNames = Object.keys(keywords);
Logger.log('Found ' + numberOfKeywords + ' keywords for ' + numberOfadGroups + ' ad groups in ' + campaignNames.length + ' campaign(s).');
// Get the IDs of the ad groups named in the spreadsheet
var adGroupIds = [];
var campaignIds = [];
var campaignReport = AdWordsApp.report(
'SELECT CampaignName, AdGroupName, CampaignId, AdGroupId '
+ 'FROM ADGROUP_PERFORMANCE_REPORT '
+ 'WHERE Impressions > 0 '
+ 'AND CampaignName IN ["' + campaignNames.join('","') + '"] '
+ 'DURING ' + dateRange
);
var campaignRows = campaignReport.rows();
while (campaignRows.hasNext()) {
var row = campaignRows.next();
if (campaignIds.indexOf(row.CampaignId) < 0) {
campaignIds.push(row.CampaignId);
}
if (keywords[row.CampaignName][row.AdGroupName] != undefined) {
adGroupIds.push(row.AdGroupId);
}
} // end while
if (adGroupIds.length == 0) {
Logger.log('Could not find any ad groups with impressions that matched the given names.');
return;
}
Logger.log('Found ' + adGroupIds.length + ' ad groups in ' + campaignIds.length + ' campaign(s) with impressions that matched the given names.');
// Initialise the arrays for each campaign, and sorts the keywords from longest to shortest
var negativeQueries = {}; // Contains the queries
var exactNegatives = {}; // Contains any negatives to add with exact match
var phraseNegatives = {}; // Contains any negatives to add with phrase match
for (var campaignName in keywords) {
negativeQueries[campaignName] = {};
exactNegatives[campaignName] = {};
phraseNegatives[campaignName] = {};
for (var adGroupName in keywords[campaignName]) {
negativeQueries[campaignName][adGroupName] = [];
exactNegatives[campaignName][adGroupName] = [];
phraseNegatives[campaignName][adGroupName] = [];
keywords[campaignName][adGroupName].sort(function (a, b) {
return b.length - a.length;
});
}
}
// Get the queries that don't exactly match keywords
var report = AdWordsApp.report(
'SELECT Query, AdGroupId, CampaignId, CampaignName, AdGroupName, Impressions '
+ 'FROM SEARCH_QUERY_PERFORMANCE_REPORT '
+ 'WHERE AdGroupId IN [' + adGroupIds.join(',') + '] '
+ 'AND Impressions > ' + impressionThreshold + ' '
+ 'DURING ' + dateRange
);
var rows = report.rows();
var numberQueries = 0;
while (rows.hasNext()) {
var row = rows.next();
var query = row.Query.toLowerCase().replace(/[^wsd&]/g, ' ').replace(/ +/g, ' ').trim();
var campaignName = row.CampaignName;
var adGroupName = row.AdGroupName;
if (keywords[campaignName][adGroupName].indexOf(query) < 0) {
negativeQueries[campaignName][adGroupName].push(query);
numberQueries++;
}
}
// Process queries
Logger.log('Processing ' + numberQueries + ' queries that do not match any keywords.');
var numberExactNegatives = 0;
var numberPotentialPhraseNegatives = 0;
for (var campaignName in negativeQueries) {
for (var adGroupName in negativeQueries[campaignName]) {
for (var i = 0; i < negativeQueries[campaignName][adGroupName].length; i++) {
var query = negativeQueries[campaignName][adGroupName][i];
var queryDone = false;
// If the query is contained within a keyword, it has to be an exact match negative
if (isStringInsideKeywords(query, keywords[campaignName][adGroupName])) {
exactNegatives[campaignName][adGroupName].push(query);
numberExactNegatives++;
continue;
}
// Check each word (that's over 4 characters) in the query - if it's not in the words array
// then it isn't in the keywords, so it's fine to use as a phrase negative
var queryWords = query.split(' ');
for (var w = 0; w < queryWords.length; w++) {
if (queryWords[w].length > 4) {
if (words[campaignName][adGroupName][queryWords[w]] == undefined) {
phraseNegatives[campaignName][adGroupName].push(queryWords[w]);
queryDone = true;
break;
}
}
}
// Check if there is a keyword inside the query. If there is, see if the part of the query before
// or after the keyword could be used as a phrase negative.
for (var k = 0; k < keywords[campaignName][adGroupName].length && !queryDone; k++) {
var keyword = keywords[campaignName][adGroupName][k];
if ((' ' + query + ' ').indexOf(' ' + keyword + ' ') > -1) {
var queryBits = (' ' + query + ' ').split(' ' + keyword + ' ');
queryBits[0] = queryBits[0].trim();
queryBits[1] = queryBits[1].trim();
if (queryBits[0].length > 0 && !isStringInsideKeywords(queryBits[0], keywords[campaignName][adGroupName])) {
phraseNegatives[campaignName][adGroupName].push(queryBits[0]);
queryDone = true;
break;
}
if (queryBits[1].length > 0 && !isStringInsideKeywords(queryBits[1], keywords[campaignName][adGroupName])) {
phraseNegatives[campaignName][adGroupName].push(queryBits[1]);
queryDone = true;
break;
}
}
}
// If nothing smaller than the full query would work, then add the full query as a negative
if (!queryDone) {
phraseNegatives[campaignName][adGroupName].push(query);
}
numberPotentialPhraseNegatives++;
}
}
}
Logger.log('Found ' + numberPotentialPhraseNegatives + ' potential phrase match negatives and ' + numberExactNegatives + ' exact match negatives.');
// Remove any redundant phrase negatives
Logger.log('Checking for redundant negatives.');
var numberPhraseNegatives = 0;
for (var campaignName in negativeQueries) {
for (var adGroupName in negativeQueries[campaignName]) {
// Order the phrases from shortest to longest
phraseNegatives[campaignName][adGroupName].sort(function (a, b) {
return a.length - b.length;
});
for (var i = 0; i < phraseNegatives[campaignName][adGroupName].length; i++) {
var shorterPhrase = ' ' + phraseNegatives[campaignName][adGroupName][i] + ' ';
// As the array is now ordered, any phrase negatives with higher indices must be longer than shorterPhrase
for (var j = i + 1; j < phraseNegatives[campaignName][adGroupName].length; j++) {
var longerPhrase = ' ' + phraseNegatives[campaignName][adGroupName][j] + ' ';
// If the shorterPhrase is within the longerPhrase, then the longerPhrase is redundant
// so it is removed from the array. This also means duplicates are removed.
if (longerPhrase.indexOf(shorterPhrase) > -1) {
phraseNegatives[campaignName][adGroupName].splice(j, 1);
j--;
}
}
}
numberPhraseNegatives += phraseNegatives[campaignName][adGroupName].length;
}
}
Logger.log('Going to create ' + numberPhraseNegatives + ' phrase match negatives and ' + numberExactNegatives + ' exact match negatives');
// Iterate through the Shopping ad groups and add the negative keywords
var groupIterator = AdWordsApp.shoppingAdGroups()
.withIds(adGroupIds)
.get();
while (groupIterator.hasNext()) {
var adGroup = groupIterator.next();
var adGroupName = adGroup.getName();
var campaignName = adGroup.getCampaign().getName();
for (var i = 0; i < exactNegatives[campaignName][adGroupName].length; i++) {
adGroup.createNegativeKeyword('[' + exactNegatives[campaignName][adGroupName][i] + ']');
}
for (var i = 0; i < phraseNegatives[campaignName][adGroupName].length; i++) {
adGroup.createNegativeKeyword('"' + phraseNegatives[campaignName][adGroupName][i] + '"');
}
}
Logger.log('Finished.');
} // end main function
// Check if a word is a substring of any strings in the keywords array
function isStringInsideKeywords(word, keywords) {
for (var k = 0; k < keywords.length; k++) {
var keyword = ' ' + keywords[k] + ' ';
if (keyword.indexOf(' ' + word + ' ') > -1) {
return true;
}
}
return false;
}