理解无效关键词花费
当您的广告出现在与您所售产品不匹配的搜索结果中时,就会发生无效关键词花费。这会迅速消耗您的预算,因为每一次无关的点击都需要花钱,但却无法带来销售。例如,如果一家销售高端皮鞋的公司使用了“买鞋”这样的宽泛关键词,可能会吸引对运动鞋或凉鞋感兴趣的人点击,而这些并不是他们的目标客户。这种关键词定向不匹配会导致广告花费浪费,投资回报率下降。企业需要理解这个概念,才能避免不必要的经济损失,把预算集中在正确的关键词上。
否定关键词的作用
否定关键词是任何 Google Ads 活动中必不可少的工具。它们允许广告主排除特定搜索词,从而保证只有相关的搜索才会触发广告展示。例如,使用“便宜”或“打折”这样的否定关键词,可以让皮鞋公司避免吸引对高端产品无兴趣的人点击。通过精心制定否定关键词列表,企业能够优化广告支出,减少无效点击,并提升整体活动效果。
用 AI 管理关键词
人工智能(AI)正在改变广告主管理 Google Ads 活动的方式。像 FlowHunt 这样的 AI 工具专为关键词分组设计,帮助企业更有效地识别和组织相关关键词。这种自动化简化了寻找正向和否定关键词的流程,大大减少了人工管理活动的工作量。AI 驱动的关键词管理能够根据表现数据实时调整,确保广告支出持续优化,实现最佳投资回报。
降低无效关键词花费的策略
为了减少无效关键词花费,企业应采取多种策略:
- 定期更新关键词列表,以适应市场趋势和消费者兴趣的变化。
- 实施强有力的否定关键词策略,持续调研和更新需要排除的词汇。
- 根据表现数据监控和调整活动。
- 分析哪些关键词带来销售,哪些没有,以优化定向并最大化广告支出。
案例分析:PostAffiliatePro 的方法
PostAffiliatePro 曾面临每月广告支出困扰,难以从 Google Ads 活动中获得理想的投资回报率。他们决定利用 AI 来解决这个问题。通过引入 AI 工具,他们自动化了关键词管理流程,更精准地识别正向和否定关键词。这一改变帮助他们优化了广告预算,显著降低了成本,提高了活动效率。他们的经验凸显了用 AI 技术高效管理关键词花费的益处。每个新关键词的分析会在首次展示后一小时内完成。这种速度有助于在访客点击 Google 广告前及时捕捉否定关键词。

拓展资源
如果您有兴趣进一步了解高效关键词管理和 AI 优化,可参考以下资源:
用于评估每个新关键词的 Google Ads 脚本
以下是我们的脚本,每小时运行一次,用于评估关键词集群。
该脚本旨在自动化 Google Ads 活动管理的多个任务。它与 Google 表格进行配置和设置交互,并在 Google Ads 账户中执行操作,如分析搜索词、添加或排除关键词,以及通过 FlowHunt API 实现高级 AI 关键词聚类。


主要功能
- 核心控制逻辑位于
main()函数中。该函数会打开通过spreadsheetURL指定的 Google 表格,获取如apiKey、country、language等必要配置。 - 系统首先尝试将新的正向关键词添加到 FlowHunt 集群。如果成功,则继续分析尚未分配的搜索词。
关键词分析
- 分析未分配词汇: 该函数遍历账户中所有带有特定标签的广告组。它检索 Google Ads 中尚未被定向且至少有一次展示的搜索词。
- 系统利用 FlowHunt API 查找与当前分析关键词相似的关键词,并根据设定的
minimumMatch标准进行筛选。 - 符合标准的搜索词会被添加为正向关键词,如果未达到标准,则作为否定关键词标记,并在 Google 表格和 Google Ads 活动中相应更新。
FlowHunt 集成
- API 交互: 脚本依赖
callFlowHuntApi()函数与 FlowHunt API 进行各种任务,包括获取工作区 ID 和关键词聚类。 - 添加关键词到集群: 该功能将正向关键词推送回 FlowHunt 进行聚类,确保其由 Google Ads 实时搜索查询数据驱动。
- 新增和否定关键词会记录在不同的表单中,便于持续跟踪和审核。
使用方法
部署该脚本,用户需:
- 提供有效的 Google 表格 URL,并确保包含必要的表单(“Settings”、“AddedKW”、“NegativeKW”)。
- 在 Google 表格中正确配置 FlowHunt API 密钥、国家代码及其他操作设置。
- 确保脚本在已启用适当 API 访问权限的 Google Ads Script 环境下执行。

请务必设置您自己的 Google 表格真实链接。剩下的交给我们的魔法吧。我们会识别属于活动的关键词,并实现关键词(包括否定与正向)的自动化管理。
Google Ads 自动管理否定关键词脚本
//Global variables
var spreadsheetURL;
var spreadsheet;
var sheetSettings;
var sheetAddedKW;
var sheetNegativeKW;
var apiKey;
var labelName;
var country;
var language;
var location;
var urlsCount;
var minimumMatch;
var workspaceId;
function main() {
// Provide the Google Sheets URL here
spreadsheetURL = "https://docs.google.com/spreadsheets/d/....... FULL URL TO GOOGLE SHEET";
spreadsheet = SpreadsheetApp.openByUrl(spreadsheetURL);
sheetSettings = spreadsheet.getSheetByName("Settings");
sheetAddedKW = spreadsheet.getSheetByName("AddedKW");
sheetNegativeKW = spreadsheet.getSheetByName("NegativeKW");
apiKey = getSettingValue("FlowHuntAPIkey")
labelName = getSettingValue("LabelName")
country = getSettingValue("CountryCode")
language = getSettingValue("LanguageCode")
location = getSettingValue("Location")
urlsCount = getSettingValue("TopUrlsCount")
minimumMatch = getSettingValue("MinimumMatch")
workspaceId = getWorkspaceId()
if (workspaceId.length < 10) {
Logger.log("Failed to load workspace id from FlowHunt, check API key");
return;
}
Logger.log("FlowHunt WorkspaceId: " + workspaceId);
if (addPositiveKWsToCluster() == 0) {
// Analyze new keywords just if all positive keywords added already
analyzeNotAssignedWords();
}
}
function analyzeNotAssignedWords() {
Logger.log("*** START Checking not assigned keywords");
// Iterate through all ad groups in the account
var adGroupsIterator = AdsApp.adGroups().get();
while (adGroupsIterator.hasNext()) {
var adGroup = adGroupsIterator.next();
var groupName = adGroup.getId() + " - " + adGroup.getName();
if (hasLabel(adGroup, labelName)) {
// Get the search terms for the current ad group ordered by clicks in the last X days
var searchTermsQuery = "SELECT Query FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
"WHERE AdGroupId = " + adGroup.getId() +
" AND QueryTargetingStatus = \"NONE\" " +
"DURING TODAY";
var searchTermsIterator = AdsApp.report(searchTermsQuery).rows();
var adGroupKeywords = [];
while (searchTermsIterator.hasNext()) {
var searchTerm = searchTermsIterator.next();
var searchTermText = searchTerm["Query"].trim();
var similarQueries = getSimilarQueries(groupName, searchTermText)
var filteredSimilarQueries = getFilteredSimilarQueries(similarQueries);
if (filteredSimilarQueries.length > 0) {
var keywordOperation = adGroup.newKeywordBuilder().withText("[" + searchTermText + "]").build();
if (keywordOperation.isSuccessful()) {
adGroupKeywords.push(searchTermText);
var rowData = [groupName, searchTermText, new Date(), "ADDING AS POSITIVE, REVIEW!", JSON.stringify(filteredSimilarQueries)];
sheetAddedKW.appendRow(rowData);
} else {
Logger.log("Failed to add keyword as positive:" + searchTermText)
}
} else {
// add to negative
adGroup.createNegativeKeyword("[" + searchTermText + "]");
Logger.log("Excluded search term in ad group '" + groupName + "': " + searchTermText);
var rowData = [groupName, "[" + searchTermText + "]", new Date(), JSON.stringify(similarQueries)];
sheetNegativeKW.appendRow(rowData);
}
}
if (adGroupKeywords.length > 0) {
//Add all keywords in the list to FlowHunt Cluster
addKeywordsToFlowHunt(groupName, adGroupKeywords);
}
}
}
Logger.log("*** FINISHED Checking not assigned keywords");
}
function getSimilarQueries(groupName, query) {
result = callFlowHuntApi("/serp/serp/cluster/query_intersections?workspace_id="+workspaceId, "POST", {
"query": query,
"country": country,
"language": language,
"location": location,
"group_name": groupName,
"live_mode": true,
"max_position": urlsCount
});
Logger.log(result)
if (result.status=="SUCCESS") {
return JSON.parse(result.result);
}
return []
}
function getFilteredSimilarQueries(similarQueries) {
filtered = [];
for (var i=1; i<similarQueries.length; i++){
if (similarQueries[i].count>=minimumMatch) {
filtered.push(similarQueries[i]);
}
}
return filtered;
}
function addPositiveKWsToCluster() {
Logger.log("*** START Checking new campaign keywords");
// Iterate through all ad groups in the account
var adGroupsIterator = AdsApp.adGroups().get();
var processedKWs = sheetAddedKW.getDataRange().getValues();
var processedKWsMap = {};
var rowsAdded = 0;
for (var i = 1; i < processedKWs.length; i++) { // Start at 1 to skip header row if exists
var groupName = processedKWs[i][0];
var keyword = processedKWs[i][1];
processedKWsMap[groupName + '|' + keyword] = true;
}
while (adGroupsIterator.hasNext()) {
var adGroup = adGroupsIterator.next();
var groupName = adGroup.getId() + " - " + adGroup.getName();
if (hasLabel(adGroup, labelName)) {
var keywordsIterator = adGroup.keywords().get();
var adGroupKeywords = [];
while (keywordsIterator.hasNext()) {
var keyword = keywordsIterator.next();
if (keyword.isEnabled()) {
var key = groupName + '|' + keyword.getText();
if (!processedKWsMap[key]) {
adGroupKeywords.push(keyword.getText());
var rowData = [groupName, keyword.getText(), new Date(), "Already present in campaign"];
sheetAddedKW.appendRow(rowData);
processedKWsMap[key] = true;
}
}
}
if (adGroupKeywords.length > 0) {
//Add all keywords in the list to FlowHunt Cluster
addKeywordsToFlowHunt(groupName, adGroupKeywords);
} else {
Logger.log("No new keywords in Group: " + groupName);
}
rowsAdded = rowsAdded + adGroupKeywords.length
}
}
Logger.log("*** FINISHED Checking new campaign keywords");
return rowsAdded;
}
function addKeywordsToFlowHunt(GroupName, adGroupKeywords) {
requests = []
adGroupKeywords.forEach(function(keyword) {
requests.push(
{
"query": keyword,
"country": country,
"language": language,
"location": location,
"group_name": GroupName,
"count_urls": 30
}
);
});
callFlowHuntApi("/serp/serp/cluster/add_queries?workspace_id="+workspaceId, "POST", {"requests":requests});
}
function getSettingValue(settingName) {
var data = sheetSettings.getDataRange().getValues();
for (var i = 0; i < data.length; i++) {
if (data[i][0] === settingName) {
return data[i][1];
}
}
return null;
}
function getWorkspaceId() {
result = callFlowHuntApi("/auth/me", "GET")
if (result !== null) {
return result.api_key_workspace_id;
}
}
function callFlowHuntApi(endpoint, method, requestBody) {
var url = "https://api.flowhunt.io/v2" + endpoint;
var headers = {
"Api-Key": apiKey,
"Content-Type": "application/json"
};
var options = {
"method" : method, // or "post", "put", etc.
"headers" : headers,
"payload": JSON.stringify(requestBody)
};
try {
var response = UrlFetchApp.fetch(url, options);
var responseData = JSON.parse(response.getContentText());
Logger.log(responseData);
return responseData;
} catch (e) {
Logger.log("An error occurred: " + e.message);
}
return null;
}
function hasLabel(adGroup, labelName) {
var labels = adGroup.labels().get();
while (labels.hasNext()) {
var label = labels.next();
if (label.getName() === labelName) {
Logger.log("Processing Adgroup " + adGroup.getName());
return true;
}
}
return false;
}

