이번 글에서는 구글 애즈 광고 성과를 AI에게 분석하도록 하고 인사이트/개선안을 도출하는 워크플로우에 대해 이야기하려 한다.
📌이 글에 나오는 툴 정보📌
1️⃣ n8n
-url: https://n8n.io/
-여러 api를 한 번에 연동해 나만의 AI 에이전트 생성 가능 "구독 필요"
구글 애즈와 n8n을 연결하는 방법은 2가지가 있다.
[방법_01] https request 노드 활용
[방법_02] google ads 노드 활용
다만 구글 애즈 광고 형태가 다양한 만큼 [방법_02]보다는 [방법_01]이 원하는 데이터 항목을 다양하게 가져올 수 있다. 그래서 이번 글의 워크플로우도 [방법_01]을 활용했다.
❗️다만 구글 애즈든, 메타든 캠페인 구조, 성과 기준이 제각각이므로 세부 내용은 회사 상황에 맞춰야 한다. 따라서 아래 워크플로우는 참고 목적으로 활용하면 더 좋을 거라 생각한다.
✨오늘의 작업 요약✨

👇아래 워크플로우를 함께 공유한다. n8n에 복붙 하면 워크플로우를 세부 세팅은 확인할 수 있으므로 참고하며 이 글을 보면 된다.
① Schedule Trigger 노드(주간 스케줄 트리거): 일간, 주간, 월간으로 내가 원하는 요일, 시간을 지정할 수 있다.
② HTTP Request 노드(구글 애즈 PMax 성과 데이터 가져오기): 내가 google ads 와 연동하여 원하는 데이터(예: 노출수, 클릭수 등)를 가져온다.
1)URL
URL끝에 어떤 메서드가 붙는지도 중요하다. 만약 운영하는 캠페인/에셋 수가 많지 않다면 결과를 한 번에 반환하는 googleAds:search 를 쓰면 된다. 그러나 운영하는 캠페인/에셋 수가 많다면 googleAds:searchStream 를 쓰면 된다. 자세한 가이드는 이곳에서 확인할 수 있다.
https://googleads.googleapis.com/
{{$env.GOOGLE_ADS_API_VERSION}}/customers/{{$env.GOOGLE_ADS_CUSTOMER_ID}}/
googleAds:search
https://googleads.googleapis.com/
{{$env.GOOGLE_ADS_API_VERSION}}/customers/{{$env.GOOGLE_ADS_CUSTOMER_ID}}/
googleAds:searchStream
2)Authentication ► Credential Type
내 구글 애즈 계정과 연동해야 하는데 자세한 내용은 아래 글을 참고하면 된다.
https://ironyoo.tistory.com/49
3)Send Body
구글 애즈와 연동했으니 어떤 데이터 항목(예: 캠페인명, 노출 수, 클릭 수 등)을 가져올지 정해야 하는데 이를 JSON 으로 지정한다고 보면 된다.
{
"query":
"SELECT campaign.name, campaign.id, campaign.status, asset_group.name, asset_group.id, asset_group.status, metrics.impressions, metrics.clicks, metrics.ctr, metrics.conversions, metrics.conversions_value, metrics.cost_micros, metrics.cost_per_conversion
FROM asset_group
WHERE segments.date DURING LAST_7_DAYS AND campaign.advertising_channel_type = 'PERFORMANCE_MAX' AND campaign.status = 'ENABLED'
ORDER BY metrics.impressions DESC"
}
③ Code in JavaScript 노드(성과 지표 계산): 가져온 데이터를 그대로 AI에게 분석시키면 토큰을 많이 소진하게 된다. 따라서 미리 성과 지표들을 계산해서 전달하는 것이 좋다. 아래 코드를 통해 미리 데이터를 가공하도록 한다.
// Performance Max 주간 성과 데이터 분석
const items = $input.all();
const results = items[0].json.results || [];
const allAssetGroups = [];
const campaignPerformance = {};
results.forEach(result => {
const campaignName = result.campaign?.name || 'Unknown';
const campaignId = result.campaign?.id || '';
const assetGroupName = result.assetGroup?.name || 'Unknown';
const assetGroupId = result.assetGroup?.id || '';
const assetGroupStatus = result.assetGroup?.status || '';
const impressions = parseInt(result.metrics?.impressions || 0);
const clicks = parseInt(result.metrics?.clicks || 0);
const ctr = parseFloat(result.metrics?.ctr || 0) * 100;
const conversions = parseFloat(result.metrics?.conversions || 0);
const conversionsValue = parseFloat(result.metrics?.conversionsValue || 0);
const cost = parseFloat(result.metrics?.costMicros || 0) / 1000000;
const roas = cost > 0 ? (conversionsValue / cost) : 0;
const cpa = conversions > 0 ? (cost / conversions) : 0;
const conversionRate = clicks > 0 ? ((conversions / clicks) * 100) : 0;
allAssetGroups.push({
campaign_id: campaignId,
campaign_name: campaignName,
asset_group_id: assetGroupId,
asset_group_name: assetGroupName,
asset_group_status: assetGroupStatus,
impressions,
clicks,
ctr: ctr.toFixed(2),
conversions: conversions.toFixed(1),
conversions_value: conversionsValue.toFixed(0),
cost: cost.toFixed(0),
roas: roas.toFixed(2),
cpa: cpa.toFixed(0),
conversion_rate: conversionRate.toFixed(2)
});
if (!campaignPerformance[campaignName]) {
campaignPerformance[campaignName] = {
campaign_id: campaignId,
total_impressions: 0,
total_clicks: 0,
total_conversions: 0,
total_conversions_value: 0,
total_cost: 0,
asset_group_count: 0
};
}
campaignPerformance[campaignName].total_impressions += impressions;
campaignPerformance[campaignName].total_clicks += clicks;
campaignPerformance[campaignName].total_conversions += conversions;
campaignPerformance[campaignName].total_conversions_value += conversionsValue;
campaignPerformance[campaignName].total_cost += cost;
campaignPerformance[campaignName].asset_group_count += 1;
});
const campaignMetrics = Object.entries(campaignPerformance).map(([name, data]) => ({
campaign_name: name,
campaign_id: data.campaign_id,
total_impressions: data.total_impressions,
total_clicks: data.total_clicks,
total_conversions: data.total_conversions.toFixed(1),
total_conversions_value: data.total_conversions_value.toFixed(0),
total_cost: data.total_cost.toFixed(0),
roas: data.total_cost > 0 ? (data.total_conversions_value / data.total_cost).toFixed(2) : '0',
cpa: data.total_conversions > 0 ? (data.total_cost / data.total_conversions).toFixed(0) : '0',
avg_ctr: data.total_impressions > 0 ? ((data.total_clicks / data.total_impressions) * 100).toFixed(2) : '0',
asset_group_count: data.asset_group_count
}));
allAssetGroups.sort((a, b) => parseFloat(b.roas) - parseFloat(a.roas));
const topCount = Math.max(1, Math.ceil(allAssetGroups.length * 0.2));
const bottomCount = Math.max(1, Math.ceil(allAssetGroups.length * 0.2));
const topAssetGroups = allAssetGroups.slice(0, topCount);
const bottomAssetGroups = allAssetGroups.slice(-bottomCount);
const topNames = new Set(topAssetGroups.map(a => a.asset_group_name));
const bottomNames = new Set(bottomAssetGroups.map(a => a.asset_group_name));
allAssetGroups.forEach(ag => {
if (topNames.has(ag.asset_group_name)) ag.performance_tier = '상위 20%';
else if (bottomNames.has(ag.asset_group_name)) ag.performance_tier = '하위 20%';
else ag.performance_tier = '평균';
});
const totalImpressions = allAssetGroups.reduce((s, a) => s + parseInt(a.impressions), 0);
const totalClicks = allAssetGroups.reduce((s, a) => s + parseInt(a.clicks), 0);
const totalConversions = allAssetGroups.reduce((s, a) => s + parseFloat(a.conversions), 0);
const totalCost = allAssetGroups.reduce((s, a) => s + parseFloat(a.cost), 0);
const totalConversionsValue = allAssetGroups.reduce((s, a) => s + parseFloat(a.conversions_value), 0);
return {
overall_metrics: {
total_asset_groups_analyzed: allAssetGroups.length,
date_range: '지난 7일',
analysis_date: new Date().toISOString(),
total_impressions: totalImpressions,
total_clicks: totalClicks,
total_conversions: totalConversions.toFixed(1),
total_conversions_value: totalConversionsValue.toFixed(0),
total_cost: totalCost.toFixed(0),
overall_roas: totalCost > 0 ? (totalConversionsValue / totalCost).toFixed(2) : '0',
overall_cpa: totalConversions > 0 ? (totalCost / totalConversions).toFixed(0) : '0',
avg_ctr: totalImpressions > 0 ? ((totalClicks / totalImpressions) * 100).toFixed(2) : '0'
},
all_asset_groups: allAssetGroups,
campaign_performance: campaignMetrics,
top_performers: topAssetGroups,
bottom_performers: bottomAssetGroups
};
④ AI노드: 가공된 데이터를 토대로 1차 성과 분석을 시킨다. 프롬프트 내용은 각자 상황에 맞게 AI 도움을 받으면 된다.
[System 프롬프트]
당신은 Google Performance Max 캠페인 성과 분석 전문가입니다. PMax의 자동화 특성을 이해하고, 에셋그룹 구성과 오디언스 시그널 관점에서 마케팅 담당자가 즉시 실행 가능한 인사이트를 제공하세요.
[User 프롬프트]
다음 Google Performance Max 캠페인 주간 성과 데이터를 분석해 주세요:
## 전체 성과 (지난 7일)
- 총 에셋그룹 수: {{ $('성과 지표 계산').item.json.overall_metrics.total_asset_groups_analyzed }}개
- 총 노출수: {{ $('성과 지표 계산').item.json.overall_metrics.total_impressions }}회
- 총 클릭수: {{ $('성과 지표 계산').item.json.overall_metrics.total_clicks }}회- 평균 CTR: {{ $('성과 지표 계산').item.json.overall_metrics.avg_ctr }}%
- 총 전환수: {{ $('성과 지표 계산').item.json.overall_metrics.total_conversions }}건
- 총 전환가치: {{ $('성과 지표 계산').item.json.overall_metrics.total_conversions_value }}원
- 총 비용: {{ $('성과 지표 계산').item.json.overall_metrics.total_cost }}원
-전체 ROAS: {{ $('성과 지표 계산').item.json.overall_metrics.overall_roas }}
- 전체 CPA: {{ $('성과 지표 계산').item.json.overall_metrics.overall_cpa }}원
## 캠페인별 성과
{{ $('성과 지표 계산').itehttp://m.json.campaign_performance.map((c, i) => `${i+1}. ${c.campaign_name}: ROAS ${c.roas} / CPA ${c.cpa}원 / 비용 ${c.total_cost}원 / 전환 ${c.total_conversions}건`).join('\n') }}
## 상위 성과 에셋그룹(ROAS 상위 20%)
{{ $('성과 지표 계산').itehttp://m.json.top_performers.map((a, i) => `${i+1}. ${a.asset_group_name} (${a.campaign_name}): ROAS ${a.roas} / CPA ${a.cpa}원 / CTR ${a.ctr}% / 전환 ${a.conversions}건`).join('\n') }}
## 하위 성과 에셋그룹(ROAS 하위 20%)
{{ $('성과 지표 계산').itehttp://m.json.bottom_performers.map((a, i) => `${i+1}. ${a.asset_group_name} (${a.campaign_name}): ROAS ${a.roas} / CPA ${a.cpa}원 / CTR ${a.ctr}% / 전환 ${a.conversions}건`).join('\n') }}
다음 형식으로 주간 리포트를 작성해주세요:
1. **이번 주 핵심 요약** (3줄): 가장 중요한 성과 변화
2. **ROAS/CPA 분석**: 수익성 관점의 구체적 진단
3. **상위 에셋그룹 성공 요인**: 잘 된 이유 분석
4. **하위 에셋그룹 문제 진단**: 개선 필요 이유
5. **즉시 실행 가능한 개선안** (5가지): PMax 에셋 구성, 오디언스 시그널, 예산 배분 관점
6. **다음 주 테스트 제안** (2가지): 구체적 실험 가설
⑤ Code in JavaScript 노드(인사이트 추출 및 정리, 하위성과 에셋그룹 필터링): 1차 분석을 통해 나온 AI 인사이트를 보기 좋게 정리하도록 하는 코드다.
// OpenAI 응답에서 인사이트 추출
const openaiResponse = $input.first().json;
const aiInsights = openaiResponse.choices[0].message.content;
const performanceData = $('성과 지표 계산').first().json;
return {
ai_insights: aiInsights,
analysis_summary: {
date: new Date().toISOString(),
total_asset_groups: performanceData.overall_metrics.total_asset_groups_analyzed,
overall_roas: performanceData.overall_metrics.overall_roas,
overall_cpa: performanceData.overall_metrics.overall_cpa,
avg_ctr: performanceData.overall_metrics.avg_ctr,
top_asset_group: performanceData.top_performers[0]?.asset_group_name || 'N/A',
top_asset_group_roas: performanceData.top_performers[0]?.roas || '0',
bottom_asset_group: performanceData.bottom_performers[0]?.asset_group_name || 'N/A',
bottom_asset_group_roas: performanceData.bottom_performers[0]?.roas || '0'
},
top_performers: performanceData.top_performers,
bottom_performers: performanceData.bottom_performers,
all_asset_groups: performanceData.all_asset_groups,
campaign_performance: performanceData.campaign_performance
};
// 하위 20% 에셋그룹 필터링
const allAssetGroups = $('인사이트 추출 및 정리').first().json.all_asset_groups;
const bottomPerformers = allAssetGroups.filter(ag => ag.performance_tier === '하위 20%');
return bottomPerformers.map(ag => ({
json: ag
}));
⑥ HTTP Request 노드(일별 성과 데이터 조회): 전체 성과가 어떤지, 상위/하위 성과 그룹이 어떻게 되는지 등등 1차 분석이 완료되면 하위 성과 그룹은 어떤 이슈가 있었던 것인지 구체적인 확인이 필요하다. 이를 위해 하위 성과 그룹을 일별로 쪼개서 봐야 하므로, 구글 애즈 다시 접속해 일별 데이터 받아오는 절차다. Send body 에서 아래 JSON 을 통해 원하는 데이터를 얻는다.
{"query":
"SELECT asset_group.id, asset_group.name, campaign.name, segments.date, metrics.impressions, metrics.clicks, metrics.ctr, metrics.conversions, metrics.conversions_value, metrics.cost_micros
FROM asset_group
WHERE segments.date DURING LAST_7_DAYS AND campaign.advertising_channel_type = 'PERFORMANCE_MAX' AND asset_group.id = '{{ $json.asset_group_id }}'
ORDER BY segments.date ASC"}
⑦ Code in JavaScript 노드(일별 추이 분석): AI에게 분석을 시키기 전에 미리 데이터를 가공하는 과정이다. 예를 들어 하위 성과 그룹이 지속적으로 성과가 하락했는지 확인하기 위해서 주 전반부, 후반부 평균을 비교하는 등 세부 데이터를 가공하는 것이라 보면 된다.
// 일별 성과 데이터로 추이(trend) 분석
// 이 노드는 에셋그룹 1개씩 실행됩니다
const dailyResults = $input.first().json.results || [];
const assetGroupData = $('하위 성과 에셋그룹 필터링').item.json;
// 날짜순 정렬 및 일별 지표 계산
const dailySeries = dailyResults
.map(r => {
const cost = parseFloat(r.metrics?.costMicros || 0) / 1000000;
const conversionsValue = parseFloat(r.metrics?.conversionsValue || 0);
const conversions = parseFloat(r.metrics?.conversions || 0);
const clicks = parseInt(r.metrics?.clicks || 0);
const impressions = parseInt(r.metrics?.impressions || 0);
return {
date: r.segments?.date || '',
impressions,
clicks,
ctr: impressions > 0 ? ((clicks / impressions) * 100).toFixed(2) : '0.00',
conversions: conversions.toFixed(1),
conversions_value: conversionsValue.toFixed(0),
cost: cost.toFixed(0),
roas: cost > 0 ? (conversionsValue / cost) : 0,
cpa: conversions > 0 ? (cost / conversions) : 0
};
})
.sort((a, b) => a.date.localeCompare(b.date));
// ── 추이 분석 ──────────────────────────────────────
// 전반부(1~3일) vs 후반부(5~7일) 평균 비교
const firstHalf = dailySeries.slice(0, 3);
const secondHalf = dailySeries.slice(-3);
const avg = (arr, key) =>
arr.length ? arr.reduce((s, d) => s + parseFloat(d[key]), 0) / arr.length : 0;
const roasFirst = avg(firstHalf, 'roas');
const roasLast = avg(secondHalf, 'roas');
const cpaFirst = avg(firstHalf, 'cpa');
const cpaLast = avg(secondHalf, 'cpa');
const costFirst = avg(firstHalf, 'cost');
const costLast = avg(secondHalf, 'cost');
const convFirst = avg(firstHalf, 'conversions');
const convLast = avg(secondHalf, 'conversions');
const changePct = (before, after) =>
before > 0 ? (((after - before) / before) * 100).toFixed(1) : '0.0';
const roasChangePct = parseFloat(changePct(roasFirst, roasLast));
const cpaChangePct = parseFloat(changePct(cpaFirst, cpaLast));
// 단조 감소 여부: ROAS가 매일 전일보다 낮아지는 경우
const roasSeries = dailySeries.map(d => d.roas);
const isMonotonicDecline = roasSeries.length >= 3 &&
roasSeries.every((v, i) => i === 0 || v <= roasSeries[i - 1]);
// 추이 방향 판단
let trendDirection;
let urgencyLevel;
if (isMonotonicDecline) {
trendDirection = '📉 매일 지속 하락';
urgencyLevel = '🚨 즉시 조치 필요';
} else if (roasChangePct <= -20) {
trendDirection = '📉 급격한 하락';
urgencyLevel = '🚨 즉시 조치 필요';
} else if (roasChangePct <= -5) {
trendDirection = '↘ 지속 하락';
urgencyLevel = '⚠️ 이번 주 조치 권장';
} else if (roasChangePct < 0) {
trendDirection = '↘ 소폭 하락';
urgencyLevel = '👀 모니터링 지속';
} else if (roasChangePct < 5) {
trendDirection = '→ 보합';
urgencyLevel = '👀 모니터링 지속';
} else {
trendDirection = '↗ 개선 중';
urgencyLevel = '✅ 유지 관찰';
}
// 일별 ROAS 흐름 요약 문자열
const dailyRoasSummary = dailySeries
.map(d => `${d.date}: ROAS ${parseFloat(d.roas).toFixed(2)} / 비용 ${d.cost}원 / 전환 ${d.conversions}건`)
.join('\n');
return {
// 원본 에셋그룹 데이터 전달
...assetGroupData,
// 일별 원본 시리즈
daily_series: dailySeries,
// 추이 분석 결과
trend_analysis: {
trend_direction: trendDirection,
urgency_level: urgencyLevel,
is_monotonic_decline: isMonotonicDecline,
roas_change_pct: roasChangePct.toFixed(1),
cpa_change_pct: cpaChangePct.toFixed(1),
cost_change_pct: changePct(costFirst, costLast),
conv_change_pct: changePct(convFirst, convLast),
first_3days_avg_roas: roasFirst.toFixed(2),
last_3days_avg_roas: roasLast.toFixed(2),
first_3days_avg_cpa: cpaFirst.toFixed(0),
last_3days_avg_cpa: cpaLast.toFixed(0),
daily_roas_summary: dailyRoasSummary
}
};
⑧ AI 노드(2차 하위 성과 분석): 2차 분석은 개선안을 도출하는 목적으로 진행된다.
[System 프롬프트]
당신은 Google Performance Max 캠페인 최적화 전문가입니다. PMax 에셋그룹 구성 개선, 오디언스 시그널 설정, 에셋 품질 향상 관점에서 실행 가능한 개선안을 제시하세요. 일별 추이가 지속적으로 하락 중이라면 긴급 조치 안을 우선 제시하고, 일시적 부진이라면 중장기 개선안을 제안하세요.
[User 프롬프트]
다음 성과가 낮은 Performance Max 에셋그룹의 상세 데이터와 일별 추이를 분석하고 개선안을 제안해 주세요.
## 에셋그룹 주간 종합 성과
- 캠페인: {{ $json.campaign_name }}
- 에셋그룹: {{ $json.asset_group_name }}
- 주간 ROAS: {{ $json.roas }}
- 주간 CPA: {{ $json.cpa }}원
- 주간 CTR: {{ $json.ctr }}%
- 전환수: {{ $json.conversions }}건
- 노출수: {{ $json.impressions }}회
- 총비용: {{ $json.cost }}원
- 성과 등급: {{ $json.performance_tier }}
## 📊 7일 일별 추이 분석
- 추이 방향: {{ $json.trend_analysis.trend_direction }}
- 긴급도: {{ $json.trend_analysis.urgency_level }}
- 매일 단조 하락 여부: {{ $json.trend_analysis.is_monotonic_decline ? '예 (매일 하락 중)' : '아니요' }}
- ROAS 변화율 (초반 3일 → 후반 3일): {{ $json.trend_analysis.roas_change_pct }}%
({{ $json.trend_analysis.first_3days_avg_roas }} → {{ $json.trend_analysis.last_3days_avg_roas }})
- CPA 변화율: {{ $json.trend_analysis.cpa_change_pct }}%
({{ $json.trend_analysis.first_3days_avg_cpa }}원 → {{ $json.trend_analysis.last_3days_avg_cpa }}원)
- 비용 변화율: {{ $json.trend_analysis.cost_change_pct }}%
- 전환 변화율: {{ $json.trend_analysis.conv_change_pct }}%
## 📅 일별 상세 수치
{{ $json.trend_analysis.daily_roas_summary }}
## 주간 전체 인사이트
{{ $('인사이트 추출 및 정리').first().json.ai_insights }}
## 요청사항
위 일별 추이를 반드시 반영하여 개선안을 작성해 주세요.
- 지속 하락 중이면: 즉각 조치 항목을 최우선으로 제시
- 일시적 부진이면: 구조적 개선안 중심으로 제시
반드시 아래 JSON 형식으로만 응답하세요 (다른 텍스트 없이):
{
"trend_diagnosis": "추이 기반 진단 (2~3 문장)",
"is_urgent": true 또는 false,
"immediate_actions": ["즉시 실행 액션 1", "즉시 실행 액션 2"],
"asset_improvements": {
"headlines": ["추천 헤드라인 1", "추천 헤드라인 2", "추천 헤드라인 3"],
"descriptions": ["추천 설명 1", "추천 설명 2"],
"image_direction": "이미지 에셋 방향성 설명"
},
"audience_signal_suggestions": ["오디언스 시그널 제안 1", "오디언스 시그널 제안 2"],
"budget_recommendation": "예산 조정 제안",
"improvement_rationale": "개선 근거 및 예상 효과"
}
⑨ Code in JavaScript 노드(에셋그룹 개선안 데이터 결합, 최종 리포트 형태): 1차와 2차 분석 내용을 합하여 최종 리포트 형태로 만든다. 노션이나 슬랙으로 받고자 하는 형태에 맞추는 거라 보면 된다. 이걸 바로 노션/메일/슬랙으로 보내 매주 인사이트 정보를 받게 된다고 보면 된다. (노션의 경우, 로우형태로 데이터를 받아 차트 기능을 활용해 대시보드를 구축할 수도 있다.)
// OpenAI 응답 파싱 및 원본 에셋그룹 + 추이 데이터 결합
const openaiResponse = $input.first().json;
const suggestions = JSON.parse(openaiResponse.choices[0].message.content);
// 일별 추이 분석 노드에서 원본 + 추이 데이터 가져오기
const sourceData = $('일별 추이 분석').item.json;
const trend = sourceData.trend_analysis;
return {
// 원본 에셋그룹 정보
campaign_name: sourceData.campaign_name,
asset_group_name: sourceData.asset_group_name,
asset_group_id: sourceData.asset_group_id,
roas: sourceData.roas,
cpa: sourceData.cpa,
ctr: sourceData.ctr,
conversions: sourceData.conversions,
impressions: sourceData.impressions,
cost: sourceData.cost,
conversion_rate: sourceData.conversion_rate,
performance_tier: sourceData.performance_tier,
// 일별 추이 분석 결과
trend_direction: trend.trend_direction,
urgency_level: trend.urgency_level,
is_monotonic_decline: trend.is_monotonic_decline,
roas_change_pct: trend.roas_change_pct,
cpa_change_pct: trend.cpa_change_pct,
first_3days_avg_roas: trend.first_3days_avg_roas,
last_3days_avg_roas: trend.last_3days_avg_roas,
daily_roas_summary: trend.daily_roas_summary,
// AI 진단 및 개선안
trend_diagnosis: suggestions.trend_diagnosis || '',
is_urgent: suggestions.is_urgent || false,
immediate_action_1: suggestions.immediate_actions?.[0] || '',
immediate_action_2: suggestions.immediate_actions?.[1] || '',
suggested_headline_1: suggestions.asset_improvements?.headlines[0] || '',
suggested_headline_2: suggestions.asset_improvements?.headlines[1] || '',
suggested_headline_3: suggestions.asset_improvements?.headlines[2] || '',
suggested_description_1: suggestions.asset_improvements?.descriptions[0] || '',
suggested_description_2: suggestions.asset_improvements?.descriptions[1] || '',
image_direction: suggestions.asset_improvements?.image_direction || '',
audience_signal_1: suggestions.audience_signal_suggestions?.[0] || '',
audience_signal_2: suggestions.audience_signal_suggestions?.[1] || '',
budget_recommendation: suggestions.budget_recommendation || '',
improvement_rationale: suggestions.improvement_rationale || '',
// 분석 날짜 및 상태
analysis_date: new Date().toISOString().split('T')[0],
status: suggestions.is_urgent ? '🚨 즉시 조치 필요' : '검토 필요'
};
// 에셋그룹 개선안 데이터 결합1의 모든 아이템 수집
const allItems = $input.all();
// AI ① 전체 인사이트 (모든 아이템에 동일하게 존재)
const overallInsights = $('인사이트 추출 및 정리').first().json;
// AI ② 에셋그룹별 개선안 목록으로 정리
const assetGroupPlans = allItems.map(item => {
const d = item.json;
return {
asset_group_name: d.asset_group_name,
campaign_name: d.campaign_name,
roas: d.roas,
trend_direction: d.trend_direction,
urgency_level: d.urgency_level,
is_urgent: d.is_urgent,
trend_diagnosis: d.trend_diagnosis,
immediate_action_1: d.immediate_action_1,
immediate_action_2: d.immediate_action_2,
suggested_headline_1: d.suggested_headline_1,
suggested_headline_2: d.suggested_headline_2,
suggested_description_1: d.suggested_description_1,
audience_signal_1: d.audience_signal_1,
budget_recommendation: d.budget_recommendation,
status: d.status
};
});
// 긴급 항목 별도 분리
const urgentGroups = assetGroupPlans.filter(p => p.is_urgent);
return {
// ── AI ① 전체 주간 분석 ──────────────────
report_date: new Date().toISOString().split('T')[0],
overall_roas: overallInsights.analysis_summary.overall_roas,
overall_cpa: overallInsights.analysis_summary.overall_cpa,
weekly_ai_analysis: overallInsights.ai_insights,
// ── AI ② 하위 에셋그룹 개선안 ──────────────
total_bottom_groups: assetGroupPlans.length,
urgent_count: urgentGroups.length,
urgent_groups: urgentGroups, // 🚨 즉시 조치 필요 목록
all_improvement_plans: assetGroupPlans // 전체 개선안 목록
};
```
---
## 최종 데이터 구조
```
{
report_date: "2026-03-31",
overall_roas: "3.2",
weekly_ai_analysis: "...(AI ① 전체 분석)...", ← AI ① 결과
urgent_count: 2,
urgent_groups: [ ← AI ② 결과 (긴급만)
{ asset_group_name: "에셋A", status: "🚨 즉시 조치 필요", ... },
{ asset_group_name: "에셋B", status: "🚨 즉시 조치 필요", ... }
],
all_improvement_plans: [ ← AI ② 결과 (전체)
{ asset_group_name: "에셋A", ... },
{ asset_group_name: "에셋B", ... },
{ asset_group_name: "에셋C", ... }
]
}
이번 글에서는 구글 애즈 사례만 이야기했지만 이걸로 활용할 수 있는 건 많다. 구글 애즈뿐만 아니라 메타를 연동해 데일리 리포트의 로우 데이터를 업데이트하고, AI 인사이트/개선 내용을 받아볼 수도 있기 때문. 앞서 이야기한 것처럼 노션/메일/슬랙으로 받아볼 수 있고 더 나아가 노션 차트 기능을 활용해 대시보드도 구축도 가능하다. 이런 자동화만으로 업무 시간을 좀 더 효율적으로 쓸 수 있게 된다.
'AI > [AI_Marketing] trial and error' 카테고리의 다른 글
| n8n 구글 애즈 계정 연동하기 (0) | 2026.04.02 |
|---|---|
| [AI마케팅자동화] 특정 유튜브 채널 댓글 분석 및 구글 스프레드시트 자동 업로드 (0) | 2026.03.23 |
| [AI마케팅자동화] 유튜브 인기 영상 댓글 분석 및 구글 스프레드시트 자동 업로드 (0) | 2026.03.23 |
| n8n 유튜브 노드 개인 유튜브 계정 연결하기 (0) | 2026.03.23 |
| [AI마케팅자동화] 리뷰 크롤링 AI 분석 워드클라우드 시각화 자동 메일 발송(cursurAI, 바이브 코딩) (0) | 2026.03.18 |