パフォーマンスの低いキーワードを無効にするなど、キーワードに大規模な変更を加えるのは大変な作業です。キーワード ラベラー スクリプトは、定義したルールに基づいてキーワードに自動的にラベルを適用することで、このタスクを簡素化します。ラベルを適用したら、Google 広告管理画面でキーワードを簡単にフィルタして、必要な変更を適用できます。また、別のスクリプトを使用してキーワードに変更を加えられます。
このスクリプトの使用例を次に示します。
- 成果の低いキーワードにラベルを付けます。その後、Google 広告の管理画面でそれらのキーワードをフィルタし、入札単価を変更したり、一時停止または削除したりできます。
- 商品の名前など、ブランドに関連する固有名を含むキーワードにラベルを付けます。後でこのラベルを使用してキーワードビューを分割し、掲載結果を詳しく分析できます。
- 入札単価を変更するキーワードにラベルを付けます。その後、Google 広告管理画面でラベルを確認してから、一括編集で変更を加えられます。
- すべてのキーワードに品質スコアのラベルを付けます。その後、現在の品質スコアが(元のラベルに反映された)以前の品質スコアと一致しないキーワードにラベルを付けて、確認できるようにします。
- 上記の任意の組み合わせ。
このスクリプトは、新しいキーワードにラベルが付けられると、スプレッドシートへのリンクをメールで送信します。
仕組み
このスクリプトでは、次のフィールドを含む簡単な形式でルールを定義できます。
conditions
:KeywordSelector
条件のリスト(省略可)。これらの条件により、たとえば、掲載結果の統計情報に基づいてキーワードを選択できます。dateRange
: 統計情報フィールドに基づいて選択する際に使用するKeywordSelector
の期間(省略可)。省略した場合は、スクリプトのデフォルトの期間が使用されます。filter
: キーワードがルールに一致するかどうかをさらに定義するために、取得後に各キーワードに適用するオプションの関数。このフィルタを使用すると、conditions
では表現できない特定のキーワード フィールドに対して、より複雑なロジックを表現できます。labelName
: ルールに一致し、まだそのラベルを持たないキーワードに適用する必須のラベル名。
ブランドに関連する成果の低いキーワードを特定してラベル付けするためのルールの例を次に示します。
{
conditions: [
'Ctr < 0.02',
'AverageCpc > 1',
],
dateRange: 'LAST_7_DAYS',
filter: function(keyword) {
const brands = ['Product A', 'Product B', 'Product C'];
const text = keyword.getText();
for (const i = 0; i < brands.length; i++) {
if (text.indexOf(brand[i]) >= 0) {
return true;
}
}
return false;
},
labelName: 'Underperforming Branded'
}
このルールでは、キーワードのテキストに 3 つの商品ブランド名のうち少なくとも 1 つが含まれている場合、過去 7 日間のクリック率が 2% 未満、平均クリック単価が 1 ドルを超えるすべてのキーワードにラベルを付けます。
通常は、それ自体が有効な広告グループとキャンペーンに含まれる有効なキーワードにのみラベル付けします。このスクリプトでは、各ルールにこれらの条件を指定する代わりに、すべてのルールで使用されるグローバル条件を指定できます。
GLOBAL_CONDITIONS: [
'campaign.status = ENABLED',
'ad_group.status = ENABLED',
'ad_group_criterion.status = ENABLED'
]
このスクリプトの中核は、ルールを取得し、グローバル条件とルール固有の条件を使用して KeywordSelector
を作成し、結果として得られる各キーワードにフィルタを適用する関数です。完全な実装については、以下のスクリプトの getKeywordsForRule
関数をご覧ください。
このスクリプトは、ラベルを付けたすべてのキーワードをリストしたスプレッドシートへのリンクメールを送信します。後で Google 広告の管理画面にログインして、一時停止、削除、入札単価の変更など、キーワードの変更が必要かどうかを確認できます。
スケジュール設定
スクリプトを実行するかどうかと、その頻度は、ラベル付けの目標によって異なります。たとえば、パフォーマンスの低いキーワードにラベルを付ける場合は、パフォーマンスの低いキーワードをすばやく特定して修正できるように、スクリプトを毎週または毎日実行するスケジュールを設定できます。一方、スクリプトを使用してキーワードのラベル付けを伴う 1 回限りの作業を容易にする場合は、スクリプトを定期的に実行するようにスケジュールする必要はありません。
設定
- 以下のソースコードを使って新しい Google 広告スクリプトを作成します。このテンプレート スプレッドシートのコピーを使用します。
RULES
配列を更新して、ラベル付けルールを表します。以下のソースコードは、ブランドに関連する成果の低いキーワードと、成果の低い一般的なキーワードにラベルを付ける例を示しています。- スクリプトの
SPREADSHEET_URL
とRECIPIENT_EMAILS
を更新します。
ソースコード
// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @name Keyword Labeler
*
* @overview The Keyword Labeler script labels keywords based on rules that
* you define. For example, you can create a rule to label keywords that
* are underperforming. Later, you can filter for this label in Google Ads
* to decide whether to pause or remove those keywords. Rules don't have
* to be based solely on a keyword's performance. They can also be based
* on properties of a keyword such as its text or match type. For example,
* you could define "branded" keywords as those containing proper nouns
* associated with your brand, then label those keywords based on
* different performance thresholds versus "non-branded" keywords.
* Finally, the script sends an email linking to a spreadsheet when new
* keywords have been labeled. See
* https://developers.google.com/google-ads/scripts/docs/solutions/labels
* for more details.
*
* @author Google Ads Scripts Team [adwords-scripts@googlegroups.com]
*
* @version 2.0
*
* @changelog
* - version 2.0
* - Updated to use new Google Ads scripts features.
* - version 1.1.2
* - Added validation for external spreadsheet setup.
* - version 1.1.1
* - Improvements to time zone handling.
* - version 1.1
* - Modified to allow generic rules and labeling.
* - version 1.0
* - Released initial version.
*/
const CONFIG = {
// URL of the spreadsheet template.
// This should be a copy of https://goo.gl/opviBb.
SPREADSHEET_URL: 'YOUR_SPREADSHEET_URL',
// Array of addresses to be alerted via email if labels are applied.
RECIPIENT_EMAILS: [
'YOUR_EMAIL_HERE'
],
// Selector conditions to apply for all rules.
GLOBAL_CONDITIONS: [
'campaign.status = ENABLED',
'ad_group.status = ENABLED',
'ad_group_criterion.status = ENABLED'
],
// Default date range over which statistics fields are retrieved.
// Used when fetching keywords if a rule doesn't specify a date range.
DEFAULT_DATE_RANGE: 'LAST_7_DAYS'
};
/**
* Defines the rules by which keywords will be labeled.
* The labelName field is required. Other fields may be null.
* @type {Array.<{
* conditions: Array.<string>,
* dateRange: string,
* filter: function(Object): boolean,
* labelName: string,
* }>
* }
*/
const RULES = [
{
conditions: [
'metrics.ctr < 0.02',
'metrics.average_cpc > 1',
],
filter: function(keyword) {
const brands = ['Product A', 'Product B', 'Product C'];
const text = keyword.getText();
for (const brand of brands) {
if(text.indexOf(brand) >= 0) {
return true;
}
}
return false;
},
labelName: 'Underperforming Branded'
},
{
conditions: [
'metrics.ctr < 0.01',
'metrics.average_cpc > 2',
],
labelName: 'Underperforming'
}
];
function main() {
validateEmailAddresses();
const results = processAccount();
processResults(results);
}
/**
* Processes the rules on the current account.
*
* @return {Array.<Object>} An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function processAccount() {
ensureAccountLabels();
const changes = applyLabels();
return changes;
}
/**
* Processes the results of the script.
*
* @param {Array.<Object>} changes An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function processResults(changes) {
if (changes.length > 0) {
const spreadsheetUrl = saveToSpreadsheet(changes, CONFIG.RECIPIENT_EMAILS);
sendEmail(spreadsheetUrl, CONFIG.RECIPIENT_EMAILS);
} else {
console.log('No labels were applied.');
}
}
/**
* Retrieves the names of all labels in the account.
*
* @return {Array.<string>} An array of label names.
*/
function getAccountLabelNames() {
const labelNames = [];
for (const label of AdsApp.labels()) {
labelNames.push(label.getName());
}
return labelNames;
}
/**
* Checks that the account has a label for each rule and
* creates the rule's label if it does not already exist.
* Throws an exception if a rule does not have a labelName.
*/
function ensureAccountLabels() {
const labelNames = getAccountLabelNames();
for (var i = 0; i < RULES.length; i++) {
const labelName = RULES[i].labelName;
if (!labelName) {
throw `Missing labelName for rule #${i}`;
}
if (labelNames.indexOf(labelName) == -1) {
AdsApp.createLabel(labelName);
labelNames.push(labelName);
}
}
}
/**
* Retrieves the keywords in an account satisfying a rule
* and that do not already have the rule's label.
*
* @param {Object} rule An element of the RULES array.
* @return {Array.<Object>} An array of keywords.
*/
function getKeywordsForRule(rule) {
let selector = AdsApp.keywords();
let includedLabel;
// Add global conditions.
for (const globalCondition of CONFIG.GLOBAL_CONDITIONS) {
selector = selector.withCondition(globalCondition);
}
// Add selector conditions for this rule.
if (rule.conditions) {
for (const ruleCondition of rule.conditions) {
selector = selector.withCondition(ruleCondition);
}
}
if(rule.labelName){
includedLabel = AdsApp.labels()
.withCondition(`label.name = '${rule.labelName}'`)
.get();
if(includedLabel.hasNext()){
includedLabel=includedLabel.next();
}
}
// Exclude keywords that already have the label.
selector.withCondition(`LabelNames CONTAINS_NONE ["${rule.labelName}"]`);
// Add a date range.
selector = selector.forDateRange(rule.dateRange || CONFIG.DEFAULT_DATE_RANGE);
// Get the keywords.
const iterator = selector.get();
const keywords = [];
// Check filter conditions for this rule.
for (const keyword of iterator) {
if (!rule.filter || rule.filter(keyword)) {
keywords.push(keyword);
}
}
return keywords;
}
/**
* For each rule, determines the keywords matching the rule and which
* need to have a label newly applied, and applies it.
*
* @return {Array.<Object>} An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function applyLabels() {
const changes = [];
const customerId = AdsApp.currentAccount().getCustomerId();
for (const rule of RULES) {
const keywords = getKeywordsForRule(rule);
const labelName = rule.labelName;
for (const keyword of keywords) {
keyword.applyLabel(labelName);
changes.push({
customerId: customerId,
campaignName: keyword.getCampaign().getName(),
adGroupName: keyword.getAdGroup().getName(),
labelName: labelName,
keywordText: keyword.getText(),
});
}
}
return changes;
}
/**
* Outputs a list of applied labels to a new spreadsheet and gives editor access
* to a list of provided emails.
*
* @param {Array.<Object>} changes An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
* @param {Array.<Object>} emails An array of email addresses.
* @return {string} The URL of the spreadsheet.
*/
function saveToSpreadsheet(changes, emails) {
const template = validateAndGetSpreadsheet(CONFIG.SPREADSHEET_URL);
const spreadsheet = template.copy('Keyword Labels Applied');
// Make sure the spreadsheet is using the account's timezone.
spreadsheet.setSpreadsheetTimeZone(AdsApp.currentAccount().getTimeZone());
console.log(`Saving changes to spreadsheet at ${spreadsheet.getUrl()}`);
const headers = spreadsheet.getRangeByName('Headers');
const outputRange = headers.offset(1, 0, changes.length);
const outputValues = [];
for (const change of changes) {
outputValues.push([
change.customerId,
change.campaignName,
change.adGroupName,
change.keywordText,
change.labelName
]);
}
outputRange.setValues(outputValues);
spreadsheet.getRangeByName('RunDate').setValue(new Date());
for (const email of emails) {
spreadsheet.addEditor(email);
}
return spreadsheet.getUrl();
}
/**
* Sends an email to a list of email addresses with a link to a spreadsheet.
*
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @param {Array.<Object>} emails An array of email addresses.
*/
function sendEmail(spreadsheetUrl, emails) {
MailApp.sendEmail(emails.join(','), 'Keywords Newly Labeled',
`Keywords have been newly labeled in your` +
`Google Ads account(s). See ` +
`${spreadsheetUrl} for details.`);
}
/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL and email addresses at the top of the file
* only.
*/
/**
* Validates the provided spreadsheet URL and email address
* to make sure that they're set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
throw new Error('Please specify a valid Spreadsheet URL. You can find' +
' a link to a template in the associated guide for this script.');
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}
/**
* Validates the provided email address to make sure it's not the default.
* Throws a descriptive error message if validation fails.
*
* @throws {Error} If the list of email addresses is still the default
*/
function validateEmailAddresses() {
if (CONFIG.RECIPIENT_EMAILS &&
CONFIG.RECIPIENT_EMAILS[0] == 'YOUR_EMAIL_HERE') {
throw new Error('Please specify a valid email address.');
}
}