📊 퍌포뚌슀 마쌀팅/GA 연동 대시볎드 (W/Genspark)

누구륌 위한 묞서? Genspark륌 처음 쓰는 ꎑ고팀·마쌀터.
읜는 데 걞늬는 시간 ⏱ 앜 15분 (싀습 포핚 40~60분)
결곌묌 맀음 아칚 ꎑ고 성곌 자동 요앜 메음 + 읞터랙티람 대시볎드 + 큎늭 한 번윌로 ꎑ고죌 슬띌읎드 볎고서

🚀 데읎터 대시볎드, Genspark로 얎떻게 만듀었나 (쎈볎용)

💡 읎 섹션은 "나도 우늬 회사 대시볎드 하나 만듀고 ì‹¶ë‹€" 하는 분듀을 위한 안낎입니닀.
윔드 몰띌도 됩니닀. Genspark 채팅찜에 데읎터소슀와 프롬프튞만 붙여넣윌멎 AI가 알아서 워크슀페읎슀 · 테읎랔 · 대시볎드 · 자동 람늬핑 메음까지 만듀얎쀍니닀.

🎯 완성된 대시볎드 몚습

죌요 구성 요소

  • 🎛 상닚 필터바 — êž°ê°„ 프늬셋 (최귌 7음 / 14음 / 읎번 달 / 저번 달 / 전첎) + 컀슀텀 날짜 + 음/죌/월 집계
  • 📊 6개 KPI 칎드 — Spend · Revenue · ROAS · CTR · CPA · Purchases (ì „êž° 대비 슝감 %)
  • 📈 튾렌드 찚튞 — 음자별 Spend + ROAS 읎쀑 축
  • 🥧 캠페읞 믹슀 — A vs B 지출 도넛
  • 🏆 캠페읞 비교표 — ROAS 순 순위
  • ⚠ 저횚윚 ꎑ고 — 지출 $20↑, 구맀 0걎 룰
  • 🫧 버랔 찚튞 — 소재별 Spend × ROAS × Purchases 3찚원
  • 🎬 ꎑ고 소재 상섞 — 소재별 성곌 TOP 10

1. 읎 대시볎드가 묎엇을 핎죌나요?

섞 가지가 자동윌로 돌아갑니닀.

✅ 맀음 아칚 N시, ì–Žì œ ꎑ고 성곌 요앜 메음

전날 지출·맀출·ROAS·CPA·CTR을 한눈에 볌 수 있는 람늬핑읎 읎메음로 옵니닀. 사람읎 ꎑ고 맀니저륌 엎얎볎Ʞ 전에 읎상 신혞륌 뚌저 파악할 수 있얎요.

✅ 얞제든 엎얎볎는 읞터랙티람 대시볎드

  • êž°ê°„ 필터 (였늘 / ì–Žì œ / 최귌 7음 / 30음 / 읎번 달 / 컀슀텀)
  • 음별·죌별·월별 귞레읞 전환
  • 캠페읞별·소재별 성곌 칎드, 저횚윚 소재 자동 감지
  • 소재 횚윚 버랔 찚튞 (X=지출, Y=ROAS, 원 크Ʞ=구맀수)

✅ ꎑ고죌 볎고 슬띌읎드 자동 생성

"읎번 죌 늬포튞 만듀얎쀘" 한마디로 8장짜늬 ꎑ고죌용 슬띌읎드 데크가 데읎터 Ʞ반윌로 완성됩니닀.


2. 묎엇을 쀀비핎알 하나요?

데읎터 소슀방식자동화
Google AdsGoogle Ads Scripts → 시튞✅ 맀음 였전 N시 자동
GA4Apps Script → 시튞✅ 맀음 였전 N시 자동
Meta AdsAds Manager에서 CSV 닀욎로드 → 채팅찜 업로드📎 맀음 3분 수동
💡 Meta Ads도 자동화하고 싶닀멎? 타사 AI 도구(예: GPT나 큎로드에서 Meta Ads 컀넥터륌 연결핎서 슀프레드시튞에 맀음 자동윌로 부은 후 → 젠슀파크 대시볎드와 자동윌로 연결할 수 있습니닀. 읎 튜토늬얌에서는 신규 유저의 진입 장벜을 고렀핎 수동 업로드로 시작하고, 익숙핎지멎 자동화 도구륌 추가하는 것을 권장합니닀.

3. STEP 1 · 구Ꞁ 시튞 쀀비

새 시튞 하나 만듀얎죌섞요. 읎늄은 자유롭게 (예: "ꎑ고 raw 데읎터").

읎 시튞 하나에 닀음 탭 3개가 자동윌로 생성됩니닀:

  • ads_daily_performance (Google Ads 슀크늜튞가 자동 생성)
  • ga4_daily (GA4 슀크늜튞가 자동 생성)
  • 필요 시 Meta CSV 붙여넣Ʞ용 탭 수동 추가

시튞 URL을 복사핎두섞요 — 두 슀크늜튞에 몚두 필요합니닀.


4. STEP 2 · Google Ads 자동 연동 🟢

4-1. 슀크늜튞 등록

  1. Google Ads 로귞읞
  2. 상닚 메뉎 도구 → 음ꎄ 작업 → 슀크늜튞
  3. + 새 슀크늜튞 큎늭
  4. 슀크늜튞 읎늄: 대시볎드_시튞_동Ʞ화
  5. 아래 윔드륌 전부 복사핎서 붙여넣Ʞ
📄 <b>google_ads_to_sheet.js</b> — 큎늭핎서 윔드 펌치Ʞ
/**
 * Google Ads → Google Sheets 자동 수집 슀크늜튞
 * ─────────────────────────────────────────────
 * 위치: Google Ads → 도구 → 음ꎄ 작업 → 슀크늜튞 → + 새 슀크늜튞
 * 싀행: 승읞 → 믞늬볎Ʞ(였류 확읞) → 싀행 → 맀음 였전 6시 슀쌀쀄
 *
 * 시튞 구조 (자동 생성):
 *   ads_daily_performance : 음자별 ꎑ고 성곌 raw
 *   fx_rates              : 환윚 ì°žì¡°ìš© (USD→KRW)
 *
 * 필수 섀정: 아래 SPREADSHEET_URL 만 볞읞 시튞 URL로 교첎
 */

// ═══════════════════════════════════════════════
// 🛠 여Ʞ만 수정하섞요
// ═══════════════════════════════════════════════
const SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/여Ʞ에_시튞_URL_입력';

// 수집 êž°ê°„: LAST_7_DAYS / LAST_14_DAYS / LAST_30_DAYS / THIS_MONTH
const DATE_RANGE = 'LAST_7_DAYS';

// 시튞 탭 읎늄 (원하멎 변겜)
const SHEET_NAME = 'ads_daily_performance';

// ═══════════════════════════════════════════════
// 메읞
// ═══════════════════════════════════════════════
function main() {
  const ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
  let sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
    sheet.appendRow([
      'report_date', 'account_id', 'account_name',
      'campaign_id', 'campaign_name', 'campaign_objective',
      'adset_id', 'adset_name',
      'ad_id', 'ad_name', 'creative_type', 'ad_status',
      'spend', 'impressions', 'reach', 'frequency',
      'clicks', 'link_clicks', 'ctr', 'cpc', 'cpm',
      'purchases', 'purchase_value',
      'add_to_cart', 'initiate_checkout',
      'roas', 'cpa', 'currency', 'source'
    ]);
  }

  const account = AdsApp.currentAccount();
  const accountId = account.getCustomerId();
  const accountName = account.getName();
  const currency = account.getCurrencyCode();

  const report = AdsApp.report(
    `SELECT
       segments.date,
       campaign.id, campaign.name, campaign.advertising_channel_type,
       ad_group.id, ad_group.name,
       ad_group_ad.ad.id, ad_group_ad.ad.name, ad_group_ad.ad.type, ad_group_ad.status,
       metrics.cost_micros, metrics.impressions, metrics.clicks, metrics.ctr,
       metrics.average_cpc, metrics.average_cpm,
       metrics.conversions, metrics.conversions_value
     FROM ad_group_ad
     WHERE segments.date DURING ${DATE_RANGE}
       AND ad_group_ad.status != 'REMOVED'`
  );

  const rows = report.rows();
  const buffer = [];
  while (rows.hasNext()) {
    const r = rows.next();
    const spend = Number(r['metrics.cost_micros']) / 1e6;
    const conversions = Number(r['metrics.conversions']) || 0;
    const revenue = Number(r['metrics.conversions_value']) || 0;
    const clicks = Number(r['metrics.clicks']) || 0;
    const impressions = Number(r['metrics.impressions']) || 0;

    buffer.push([
      r['segments.date'],
      accountId, accountName,
      r['campaign.id'], r['campaign.name'], r['campaign.advertising_channel_type'],
      r['ad_group.id'], r['ad_group.name'],
      r['ad_group_ad.ad.id'], r['ad_group_ad.ad.name'],
      r['ad_group_ad.ad.type'], r['ad_group_ad.status'],
      spend, impressions, 0, 0,
      clicks, clicks, Number(r['metrics.ctr']) || 0,
      Number(r['metrics.average_cpc']) / 1e6, Number(r['metrics.average_cpm']) / 1e6,
      conversions, revenue,
      0, 0,
      spend > 0 ? revenue / spend : 0,
      conversions > 0 ? spend / conversions : 0,
      currency, 'google_ads_script'
    ]);
  }

  if (buffer.length === 0) {
    Logger.log('수집된 데읎터가 없습니닀.');
    return;
  }

  // Ʞ졎 데읎터 쀑 같은 êž°ê°„(report_date) 삭제 후 append (upsert 대첎)
  const existingDates = new Set(buffer.map(r => r[0]));
  const lastRow = sheet.getLastRow();
  if (lastRow > 1) {
    const dateCol = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
    for (let i = dateCol.length - 1; i >= 0; i--) {
      const d = dateCol[i][0];
      const iso = d instanceof Date
        ? Utilities.formatDate(d, 'GMT+9', 'yyyy-MM-dd')
        : String(d);
      if (existingDates.has(iso)) sheet.deleteRow(i + 2);
    }
  }

  sheet.getRange(sheet.getLastRow() + 1, 1, buffer.length, buffer[0].length).setValues(buffer);
  Logger.log(`✅ ${buffer.length}행 업로드 완료 · 계정 ${accountName}`);
}

4-2. 섀정 수정

윔드 ë§š 위 읎 쀄을 볞읞 시튞 URL로 교첎:

const SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/여Ʞ에_시튞_URL_입력';

4-3. 승읞 + 테슀튞

  1. 승읞 큎늭 → 구Ꞁ 계정 승읞 팝업 → 시튞·ꎑ고 데읎터 ì ‘ê·Œ 허용
  2. 믞늬볎Ʞ 큎늭 → 였류 없읎 로귞에 ✅ N행 업로드 완료 나였멎 OK
  3. 싀행 한 번 눌러서 시튞에 데읎터가 듀얎왔는지 확읞

4-4. 맀음 자동 싀행 섀정

  1. 슀크늜튞 목록 화멎윌로 돌아가Ʞ
  2. 방ꞈ 만든 슀크늜튞의 빈도 큎늭
  3. 빈도: 맀음, 시간: 였전 6시 섀정 → 저장
✅ 읎제 맀음 새벜 6시에 얎제까지의 Google Ads 성곌가 시튞에 자동윌로 쌓입니닀.

5. STEP 3 · GA4 자동 연동 🟠

5-1. Apps Script ì—Žêž°

같은 시튞에서:

  1. 확장 프로귞랚 → Apps Script
  2. 새 프로젝튞가 엎늜니닀. 읎늄: GA4_동Ʞ화

5-2. ⚠ 서비슀 추가 (제음 쀑요!)

왌쪜 사읎드바 서비슀 옆의 + 큎늭 → 목록에서 Google Analytics Data API 선택 → 추가

❗ 읎걞 안 하멎 슀크늜튞가 절대 안 돕니닀. AnalyticsData is not defined 였류가 뜚멎 읎 닚계륌 빠뜚늰 것.

5-3. 윔드 붙여넣Ʞ + 속성 ID 입력

아래 윔드륌 전부 복사핎서 붙여넣Ʞ한 ë’€, GA4_PROPERTY_ID 한 쀄만 볞읞 값윌로 수정:

📄 ga4_to_sheet.gs— 큎늭핎서 윔드 펌치Ʞ
/**
 * GA4 → Google Sheets 자동 수집 슀크늜튞
 * ─────────────────────────────────────────
 * 위치: 핎당 시튞에서 확장 프로귞랚 → Apps Script
 * ⚠ 필수: 왌쪜 [서비슀] + 버튌 → "Google Analytics Data API" 추가
 * 싀행 순서:
 *   1) backfill90Days   (곌거 90음 채우Ʞ)
 *   2) installDailyTrigger (맀음 07시 자동 싀행 섀정)
 *
 * 시튞 탭 (자동 생성): ga4_daily
 */

// ═══════════════════════════════════════════════
// 🛠 여Ʞ만 수정하섞요
// ═══════════════════════════════════════════════
const GA4_PROPERTY_ID = '000000000';  // 예: 366789012 (숫자만)
const SHEET_TAB = 'ga4_daily';

// ═══════════════════════════════════════════════
// 맀음 07시 튞늬거 섀치 (딱 한 번만 싀행)
// ═══════════════════════════════════════════════
function installDailyTrigger() {
  // Ʞ졎 dailySync 튞늬거 있윌멎 제거 (쀑복 방지)
  ScriptApp.getProjectTriggers().forEach(t => {
    if (t.getHandlerFunction() === 'dailySync') ScriptApp.deleteTrigger(t);
  });
  ScriptApp.newTrigger('dailySync')
    .timeBased().atHour(7).everyDays(1)
    .create();
  Logger.log('✅ 맀음 였전 7시 dailySync 튞늬거 섀치 완료');
}

// ═══════════════════════════════════════════════
// 맀음 싀행: ì–Žì œ 데읎터만 upsert
// ═══════════════════════════════════════════════
function dailySync() {
  const y = new Date(); y.setDate(y.getDate() - 1);
  const iso = Utilities.formatDate(y, 'GMT+9', 'yyyy-MM-dd');
  fetchRange(iso, iso);
}

// ═══════════════════════════════════════════════
// 쎈Ʞ 백필: 최귌 90음 (딱 한 번만 싀행)
// ═══════════════════════════════════════════════
function backfill90Days() {
  const end = new Date(); end.setDate(end.getDate() - 1);
  const start = new Date(); start.setDate(start.getDate() - 90);
  fetchRange(
    Utilities.formatDate(start, 'GMT+9', 'yyyy-MM-dd'),
    Utilities.formatDate(end, 'GMT+9', 'yyyy-MM-dd')
  );
}

// ═══════════════════════════════════════════════
// 윔얎: GA4 Data API 혞출 → 시튞 upsert
// ═══════════════════════════════════════════════
function fetchRange(startDate, endDate) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(SHEET_TAB);
  if (!sheet) {
    sheet = ss.insertSheet(SHEET_TAB);
    sheet.appendRow([
      'report_date', 'source_medium', 'campaign',
      'sessions', 'active_users', 'new_users',
      'engaged_sessions', 'avg_engagement_time',
      'conversions', 'purchase_revenue', 'transactions',
      'imported_at'
    ]);
  }

  const request = {
    dateRanges: [{ startDate, endDate }],
    dimensions: [
      { name: 'date' },
      { name: 'sessionSourceMedium' },
      { name: 'sessionCampaignName' }
    ],
    metrics: [
      { name: 'sessions' }, { name: 'activeUsers' }, { name: 'newUsers' },
      { name: 'engagedSessions' }, { name: 'userEngagementDuration' },
      { name: 'conversions' }, { name: 'purchaseRevenue' }, { name: 'transactions' }
    ],
    limit: 100000
  };

  const response = AnalyticsData.Properties.runReport(request, `properties/${GA4_PROPERTY_ID}`);
  const rows = response.rows || [];
  if (rows.length === 0) {
    Logger.log(`⚠ ${startDate}~${endDate} 조회 결곌 0걎`);
    return;
  }

  const now = Utilities.formatDate(new Date(), 'GMT+9', 'yyyy-MM-dd HH:mm:ss');
  const buffer = rows.map(r => {
    const d = r.dimensionValues, m = r.metricValues;
    const dateISO = `${d[0].value.slice(0,4)}-${d[0].value.slice(4,6)}-${d[0].value.slice(6,8)}`;
    const sessions = Number(m[0].value) || 0;
    const engTime = Number(m[4].value) || 0;
    return [
      dateISO, d[1].value, d[2].value,
      sessions, Number(m[1].value) || 0, Number(m[2].value) || 0,
      Number(m[3].value) || 0, sessions > 0 ? engTime / sessions : 0,
      Number(m[5].value) || 0, Number(m[6].value) || 0, Number(m[7].value) || 0,
      now
    ];
  });

  // upsert: 같은 (date, source_medium, campaign) 조합 삭제 후 append
  const keys = new Set(buffer.map(r => `${r[0]}|${r[1]}|${r[2]}`));
  const lastRow = sheet.getLastRow();
  if (lastRow > 1) {
    const existing = sheet.getRange(2, 1, lastRow - 1, 3).getValues();
    for (let i = existing.length - 1; i >= 0; i--) {
      const d = existing[i][0];
      const iso = d instanceof Date
        ? Utilities.formatDate(d, 'GMT+9', 'yyyy-MM-dd')
        : String(d);
      const k = `${iso}|${existing[i][1]}|${existing[i][2]}`;
      if (keys.has(k)) sheet.deleteRow(i + 2);
    }
  }

  sheet.getRange(sheet.getLastRow() + 1, 1, buffer.length, buffer[0].length).setValues(buffer);
  Logger.log(`✅ ${buffer.length}행 · ${startDate}~${endDate}`);
}

GA4 속성 ID 찟는 법:

GA4 → ꎀ늬(⚙) → 속성 섀정 → 속성 섞부 정볎 → 상닚 속성 ID (9자늬 숫자)

5-4. 딱 한 번씩 싀행

닀음 핚수륌 순서대로 한 번씩만 싀행:

  1. 상닚 핚수 드롭닀욎에서 backfill90Days 선택 → ▶ 싀행

    → 곌거 90음치 데읎터가 시튞에 채워집니닀 (1~2분 소요)

  2. 닀시 드롭닀욎에서 installDailyTrigger 선택 → ▶ 싀행

    → 맀음 였전 7시 자동 싀행 튞늬거 섀치

✅ 읎제 맀음 였전 7시에 GA4 ì–Žì œ 데읎터가 시튞에 자동윌로 upsert됩니닀.

6. STEP 4 · Meta Ads, 넀읎버 등 CSV 수동 업로드 🔵

아직까지 몚든 AI가 몚든 맀첎사 얎드믌에 자유롭게 접속하지는 못핎요. Meta Ads는 타 AI에서는 MCP 연동읎 가능하나 투뚞치 Ʞ술적읎니까

6-1. Ads Manager에서 CSV 닀욎로드

  1. Meta Ads Manager 로귞읞
  2. 상닚 ìš°ìž¡ 볎고서 → 사용자 지정 볎고서
  3. 컬럌: 캠페읞·ꎑ고 섞튞·ꎑ고·지출·녞출·도달·큎늭·CTR·CPC·CPM·구맀·구맀 전환 값·장바구니 추가·결제 시작
  4. 분석 êž°ê°„: ì–Žì œ (또는 원하는 êž°ê°„)
  5. ìš°ìž¡ 상닚 낎볎낎Ʞ → CSV

6-2. 채팅찜에 업로드

Genspark 워크슀페읎슀 채팅찜에 귞대로 파음 첚부 후 읎렇게 말하멎 됩니닀:

"ì–Žì œ Meta ꎑ고 raw 업로드했얎. ads_daily_performance 테읎랔에 upsert핎쀘"

7. STEP 5 · Genspark 워크슀페읎슀에 시튞 연결

  1. Genspark 워크슀페읎슀 화멎 ìš°ìž¡ 상닚 데읎터 소슀 추가
  2. Google Sheets 선택 → 위에서 만든 시튞 URL 붙여넣Ʞ