불필요한 키워드 광고비 이해하기
불필요한 키워드 광고비는 광고가 실제 판매하는 상품과 맞지 않는 검색 결과에 노출될 때 발생합니다. 이런 경우 관련 없는 클릭에도 비용이 지출되고, 실제 구매로 이어지지 않아 예산이 빠르게 소진될 수 있습니다. 예를 들어, 고급 가죽 신발을 판매하는 회사가 “신발 구매”와 같이 광범위한 키워드를 타겟팅하면, 운동화나 샌들에 관심 있는 사람들의 클릭까지 유입될 수 있습니다. 이렇게 키워드 타겟팅이 미스매치되면 광고비가 낭비되고 ROI도 떨어집니다. 기업은 이 개념을 명확히 이해하고 예산을 적절한 키워드에 집중해야 불필요한 손실을 막을 수 있습니다.
부정 키워드의 역할
부정 키워드는 Google Ads 캠페인에서 필수적인 도구입니다. 광고주가 특정 검색어를 제외시켜, 오직 관련성 높은 검색에만 광고가 노출되도록 할 수 있습니다. 예를 들어, “저렴한”, “할인”과 같은 부정 키워드를 활용하면 고급 제품을 원하는 고객만 타깃팅할 수 있습니다. 부정 키워드 리스트를 세심하게 관리하면, 불필요한 클릭을 줄이고 광고비의 효율을 개선하여 캠페인 성과를 극대화할 수 있습니다.
AI를 활용한 키워드 관리
인공지능(AI)은 광고주가 Google Ads 캠페인을 관리하는 방식을 혁신하고 있습니다. FlowHunt와 같은 AI 도구는 키워드 그룹핑에 최적화되어, 연관된 키워드를 효과적으로 식별하고 조직화할 수 있도록 돕습니다. 이 자동화는 긍정/부정 키워드 모두를 손쉽게 찾을 수 있게 하며, 캠페인 관리에 필요한 수작업을 크게 줄여줍니다. AI 기반 키워드 관리는 실시간 성과 데이터에 따라 즉각적인 변경이 가능하여, 광고비가 항상 최고의 ROI로 운용되도록 보장합니다.
불필요한 키워드 광고비를 줄이는 전략
불필요한 키워드 광고비를 줄이기 위해, 기업은 다음과 같은 전략을 도입해야 합니다:
- 키워드 리스트를 정기적으로 업데이트하여 시장 변화와 소비자 관심사를 반영하세요.
- 강력한 부정 키워드 전략 구축: 지속적으로 제외할 키워드를 리서치하고 리스트를 최신 상태로 유지하세요.
- 성과 데이터에 따라 캠페인을 모니터링 및 조정하세요.
- 매출로 이어지는 키워드와 그렇지 않은 키워드를 분석하여 타겟팅을 정교화하고 광고비를 극대화하세요.
사례 연구: PostAffiliatePro의 접근법
PostAffiliatePro는 매달 광고비 집행에서 원하는 ROI를 얻지 못해 어려움을 겪고 있었습니다. 이들은 문제 해결을 위해 AI 도입을 결정했습니다. AI 기반 도구를 도입한 이후, 키워드 관리가 자동화되어 긍정/부정 키워드를 더욱 정확하게 식별할 수 있었습니다. 이 변화로 광고비를 최적화하고 비용을 크게 절감했으며, 캠페인 효율도 높아졌습니다. 이 경험은 AI 기술로 키워드 광고비를 효과적으로 관리할 수 있음을 보여줍니다. 각 신규 키워드에 대한 분석은 첫 노출 후 1시간 이내에 진행되어, 방문자가 Google 광고를 클릭하기 전에 부정 키워드를 신속히 차단할 수 있습니다.

추가 자료
효과적인 키워드 관리와 AI 최적화에 대해 더 알고 싶다면, 다음 자료를 참고하세요:
신규 키워드 평가를 위한 Google Ads 스크립트
아래는 키워드 클러스터 평가를 위해 매 시간 실행하는 저희의 스크립트입니다.
이 스크립트는 Google Ads 캠페인 관리를 위한 여러 작업을 자동화합니다. 설정 및 구성을 위해 Google Sheets와 연동되고, Google Ads 계정에서 검색어 분석, 키워드 추가/제외, 그리고 FlowHunt API를 통한 AI 기반 키워드 클러스터링 작업을 수행합니다.


메인 함수
- 핵심 제어 로직은
main()함수에 있습니다. 이 함수는spreadsheetURL에 명시된 Google Sheets 문서를 열고,apiKey,country,language등 필요한 설정 정보를 불러옵니다. - 시스템은 먼저 신규 긍정 키워드를 FlowHunt 클러스터에 추가하며, 성공하면 할당되지 않은 검색어를 분석합니다.
키워드 분석
- 할당되지 않은 단어 분석: 모든 광고 계정의 광고 그룹을 지정된 라벨로 순회하며, 타겟팅되지 않았고 최소 1회 노출된 검색어를 Google Ads에서 가져옵니다.
- 시스템은 FlowHunt API를 통해 현재 분석 중인 키워드와 유사한 키워드를 찾고, 지정된
minimumMatch기준에 따라 필터링합니다. - 기준에 맞는 검색어는 긍정 키워드로 추가되고, 그렇지 않은 경우 부정 키워드로 처리되어 Google Sheet 및 광고 캠페인에 반영됩니다.
FlowHunt 연동
- API 연동: 스크립트는
callFlowHuntApi()함수를 통해 FlowHunt API와 연동하여 워크스페이스 ID 조회, 키워드 클러스터링 등을 수행합니다. - 클러스터에 키워드 추가: 긍정 키워드를 FlowHunt로 전송하여 실시간 Google Ads 검색 데이터 기반으로 클러스터링합니다.
- 추가/부정 키워드는 별도 시트에 기록되어 지속적인 모니터링 및 검토가 가능합니다.
사용 방법
이 스크립트를 배포하려면, 다음 단계를 따라야 합니다:
- 유효한 Google Sheets URL을 제공하고, 필요 시트(“Settings”, “AddedKW”, “NegativeKW”)가 존재하는지 확인하세요.
- Google Sheets에서 FlowHunt API 키, 국가 코드 등 운영 설정을 올바르게 구성하세요.
- Google Ads Script 환경에서 API 접근 권한이 활성화된 상태로 스크립트를 실행하세요.

실제 Google Sheet 문서의 링크를 꼭 입력해야 합니다. 나머지는 저희가 책임집니다. 캠페인에 속한 키워드를 식별하고, 부정/긍정 키워드 모두를 자동으로 관리합니다.
부정 키워드 자동 관리를 위한 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;
}

