Google Ads Export Script (v2 – עם תמיכה מורחבת ב-Performance Max)

הוראות התקנה:

  1. פתח Google Ads עם החשבון [email protected].
  2. בחר חשבון לקוח ספציפי (למשל קו פרישה).
  3. Tools 🔧 → Bulk Actions → Scripts → לחץ "+".
  4. העתק את כל הקוד למטה והדבק בעורך הסקריפטים.
  5. שמור, לחץ Authorize ואשר הרשאות גישה ל-Sheets.
  6. לחץ Preview ואז Run.
  7. בלוגים של הסקריפט (תפריט "Logs") יופיע URL של ה-Sheet שנוצר. שתף אותו "כל מי שיש לו את הקישור – מציג".

מה כלול בגרסה הזו:

הקוד:

/**
 * Google Ads Script: ייצוא ביצועים מקיף ל-Google Sheets
 *
 * מה זה עושה:
 *   רץ בתוך Google Ads (לא דרך API חיצוני, אז אין צורך בDeveloper Token).
 *   מייצא 6 דוחות חיוניים ל-Google Sheet שאתה מציין למטה.
 *
 * איך מתקינים:
 *   1. ב-Google Ads → Tools 🔧 → Bulk Actions → Scripts → "+"
 *   2. הדבק את הקוד הזה
 *   3. במשתנה SHEET_URL למטה - הדבק URL של Google Sheet (חדש או קיים)
 *      (אם תשאיר ריק - הסקריפט ייצור Sheet חדש בריצה הראשונה)
 *   4. Authorize - אשר הרשאות גישה ל-Sheets
 *   5. Preview ואז Run
 *
 * הסקריפט מייצא ל-30 ימים אחרונים. אפשר לשנות בקבוע DATE_RANGE.
 */

// ===== הגדרות - תוכל לשנות =====
var SHEET_URL = ''; // הדבק כאן URL של Google Sheet, או השאר ריק ליצירת חדש
var DATE_RANGE = 'LAST_30_DAYS'; // אפשרויות: LAST_7_DAYS, LAST_14_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH
var MIN_IMPRESSIONS_FOR_SEARCH_TERMS = 5; // מינימום הופעות לכלול search term בדוח
// ================================

function main() {
  var sheet = getOrCreateSpreadsheet();
  Logger.log('Spreadsheet: ' + sheet.getUrl());

  exportAccountSummary(sheet);
  exportCampaigns(sheet);
  exportAdGroups(sheet);
  exportKeywords(sheet);
  exportSearchTerms(sheet);
  exportAds(sheet);
  exportNegativeCandidates(sheet);

  // Performance Max specific
  exportPMaxAssetGroups(sheet);
  exportPMaxChannelBreakdown(sheet);
  exportPMaxSearchCategories(sheet);
  exportPMaxListingGroups(sheet);
  exportPMaxAssetPerformance(sheet);
  exportBrandCannibalizationCheck(sheet);
  exportPMaxAuditChecklist(sheet);

  // Index sheet עם summary ולינקים
  buildIndexSheet(sheet);

  Logger.log('=========================================');
  Logger.log('סיום! ה-Sheet זמין כאן:');
  Logger.log(sheet.getUrl());
  Logger.log('=========================================');
}

function getOrCreateSpreadsheet() {
  if (SHEET_URL && SHEET_URL.length > 10) {
    return SpreadsheetApp.openByUrl(SHEET_URL);
  }
  var account = AdsApp.currentAccount();
  var name = 'Google Ads Export - ' + account.getName() + ' - ' + Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd');
  var ss = SpreadsheetApp.create(name);
  Logger.log('יצרתי Sheet חדש: ' + ss.getUrl());
  Logger.log('שמור את ה-URL הזה ב-SHEET_URL להרצות הבאות.');
  return ss;
}

function clearOrCreateSheet(ss, name) {
  var sh = ss.getSheetByName(name);
  if (sh) sh.clear();
  else sh = ss.insertSheet(name);
  return sh;
}

function writeRows(sheet, headers, rows) {
  if (rows.length === 0) {
    sheet.getRange(1, 1).setValue('No data for the selected date range.');
    return;
  }
  sheet.getRange(1, 1, 1, headers.length).setValues([headers]).setFontWeight('bold').setBackground('#1a73e8').setFontColor('#ffffff');
  sheet.getRange(2, 1, rows.length, headers.length).setValues(rows);
  sheet.setFrozenRows(1);
  sheet.autoResizeColumns(1, headers.length);
}

// ---------- Account Summary ----------
function exportAccountSummary(ss) {
  var sh = clearOrCreateSheet(ss, '0_Account_Summary');
  var account = AdsApp.currentAccount();
  var stats = account.getStatsFor(DATE_RANGE);

  var rows = [
    ['Account Name', account.getName()],
    ['Account ID', account.getCustomerId()],
    ['Currency', account.getCurrencyCode()],
    ['Time Zone', account.getTimeZone()],
    ['Date Range', DATE_RANGE],
    ['Report Generated', new Date().toString()],
    ['', ''],
    ['Impressions', stats.getImpressions()],
    ['Clicks', stats.getClicks()],
    ['CTR', (stats.getCtr() * 100).toFixed(2) + '%'],
    ['Cost', stats.getCost()],
    ['Average CPC', stats.getAverageCpc()],
    ['Conversions', stats.getConversions()],
    ['Conv. Rate', stats.getClicks() > 0 ? (stats.getConversions() / stats.getClicks() * 100).toFixed(2) + '%' : '0%'],
    ['Cost / Conv. (CPA)', stats.getConversions() > 0 ? (stats.getCost() / stats.getConversions()).toFixed(2) : 'N/A'],
  ];
  sh.getRange(1, 1, rows.length, 2).setValues(rows);
  sh.getRange(1, 1, rows.length, 1).setFontWeight('bold');
  sh.autoResizeColumns(1, 2);
}

// ---------- Campaigns ----------
function exportCampaigns(ss) {
  var sh = clearOrCreateSheet(ss, '1_Campaigns');
  var headers = ['Campaign', 'Status', 'Type', 'Budget (Daily)', 'Bid Strategy',
                 'Impressions', 'Clicks', 'CTR %', 'Cost', 'Avg CPC',
                 'Conversions', 'Cost/Conv', 'Conv Rate %', 'Conv Value', 'ROAS'];
  var rows = [];
  var iter = AdsApp.campaigns().forDateRange(DATE_RANGE).get();
  while (iter.hasNext()) {
    var c = iter.next();
    var s = c.getStatsFor(DATE_RANGE);
    var budget = 0;
    try { budget = c.getBudget().getAmount(); } catch (e) {}
    var convValue = 0;
    try { convValue = s.getConversionValue(); } catch (e) {}
    rows.push([
      c.getName(),
      c.isEnabled() ? 'ENABLED' : (c.isPaused() ? 'PAUSED' : 'REMOVED'),
      c.getAdvertisingChannelType(),
      budget,
      c.getBiddingStrategyType ? c.getBiddingStrategyType() : '',
      s.getImpressions(),
      s.getClicks(),
      (s.getCtr() * 100).toFixed(2),
      s.getCost(),
      s.getAverageCpc(),
      s.getConversions(),
      s.getConversions() > 0 ? (s.getCost() / s.getConversions()).toFixed(2) : '',
      s.getClicks() > 0 ? (s.getConversions() / s.getClicks() * 100).toFixed(2) : '',
      convValue,
      s.getCost() > 0 ? (convValue / s.getCost()).toFixed(2) : '',
    ]);
  }
  writeRows(sh, headers, rows);
}

// ---------- Ad Groups ----------
function exportAdGroups(ss) {
  var sh = clearOrCreateSheet(ss, '2_AdGroups');
  var headers = ['Campaign', 'Ad Group', 'Status', 'Impressions', 'Clicks',
                 'CTR %', 'Cost', 'Avg CPC', 'Conversions', 'Cost/Conv', 'Conv Rate %'];
  var rows = [];
  var iter = AdsApp.adGroups().forDateRange(DATE_RANGE).get();
  while (iter.hasNext()) {
    var ag = iter.next();
    var s = ag.getStatsFor(DATE_RANGE);
    if (s.getImpressions() === 0) continue;
    rows.push([
      ag.getCampaign().getName(),
      ag.getName(),
      ag.isEnabled() ? 'ENABLED' : 'PAUSED',
      s.getImpressions(),
      s.getClicks(),
      (s.getCtr() * 100).toFixed(2),
      s.getCost(),
      s.getAverageCpc(),
      s.getConversions(),
      s.getConversions() > 0 ? (s.getCost() / s.getConversions()).toFixed(2) : '',
      s.getClicks() > 0 ? (s.getConversions() / s.getClicks() * 100).toFixed(2) : '',
    ]);
  }
  writeRows(sh, headers, rows);
}

// ---------- Keywords ----------
function exportKeywords(ss) {
  var sh = clearOrCreateSheet(ss, '3_Keywords');
  var headers = ['Campaign', 'Ad Group', 'Keyword', 'Match Type', 'Status',
                 'Quality Score', 'Impressions', 'Clicks', 'CTR %', 'Cost',
                 'Avg CPC', 'Conversions', 'Cost/Conv', 'Conv Rate %'];
  var rows = [];
  var iter = AdsApp.keywords().forDateRange(DATE_RANGE).get();
  while (iter.hasNext()) {
    var k = iter.next();
    var s = k.getStatsFor(DATE_RANGE);
    if (s.getImpressions() === 0) continue;
    var qs = '';
    try { qs = k.getQualityScore(); } catch (e) {}
    rows.push([
      k.getCampaign().getName(),
      k.getAdGroup().getName(),
      k.getText(),
      k.getMatchType(),
      k.isEnabled() ? 'ENABLED' : 'PAUSED',
      qs,
      s.getImpressions(),
      s.getClicks(),
      (s.getCtr() * 100).toFixed(2),
      s.getCost(),
      s.getAverageCpc(),
      s.getConversions(),
      s.getConversions() > 0 ? (s.getCost() / s.getConversions()).toFixed(2) : '',
      s.getClicks() > 0 ? (s.getConversions() / s.getClicks() * 100).toFixed(2) : '',
    ]);
  }
  writeRows(sh, headers, rows);
}

// ---------- Search Terms ----------
function exportSearchTerms(ss) {
  var sh = clearOrCreateSheet(ss, '4_SearchTerms');
  var headers = ['Campaign', 'Ad Group', 'Search Term', 'Match Type',
                 'Impressions', 'Clicks', 'CTR %', 'Cost', 'Conversions',
                 'Cost/Conv', 'Conv Rate %'];
  var rows = [];
  var query =
    'SELECT campaign.name, ad_group.name, search_term_view.search_term, ' +
    'segments.search_term_match_type, metrics.impressions, metrics.clicks, ' +
    'metrics.ctr, metrics.cost_micros, metrics.conversions ' +
    'FROM search_term_view ' +
    'WHERE segments.date DURING ' + DATE_RANGE + ' ' +
    'AND metrics.impressions >= ' + MIN_IMPRESSIONS_FOR_SEARCH_TERMS + ' ' +
    'ORDER BY metrics.cost_micros DESC';
  var report = AdsApp.report(query);
  var rowsIter = report.rows();
  while (rowsIter.hasNext()) {
    var r = rowsIter.next();
    var clicks = Number(r['metrics.clicks']) || 0;
    var conv = Number(r['metrics.conversions']) || 0;
    var cost = (Number(r['metrics.cost_micros']) || 0) / 1000000;
    rows.push([
      r['campaign.name'],
      r['ad_group.name'],
      r['search_term_view.search_term'],
      r['segments.search_term_match_type'],
      Number(r['metrics.impressions']) || 0,
      clicks,
      ((Number(r['metrics.ctr']) || 0) * 100).toFixed(2),
      cost.toFixed(2),
      conv,
      conv > 0 ? (cost / conv).toFixed(2) : '',
      clicks > 0 ? (conv / clicks * 100).toFixed(2) : '',
    ]);
  }
  writeRows(sh, headers, rows);
}

// ---------- Negative Candidates ----------
function exportNegativeCandidates(ss) {
  var sh = clearOrCreateSheet(ss, '5_NegativeCandidates');
  var headers = ['Search Term', 'Campaign', 'Cost (No Conversions)', 'Clicks', 'Impressions', 'Recommendation'];
  var rows = [];
  var query =
    'SELECT campaign.name, search_term_view.search_term, ' +
    'metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions ' +
    'FROM search_term_view ' +
    'WHERE segments.date DURING ' + DATE_RANGE + ' ' +
    'AND metrics.conversions = 0 ' +
    'AND metrics.cost_micros > 0 ' +
    'ORDER BY metrics.cost_micros DESC ' +
    'LIMIT 200';
  var report = AdsApp.report(query);
  var rowsIter = report.rows();
  while (rowsIter.hasNext()) {
    var r = rowsIter.next();
    var cost = (Number(r['metrics.cost_micros']) || 0) / 1000000;
    var clicks = Number(r['metrics.clicks']) || 0;
    var imps = Number(r['metrics.impressions']) || 0;
    var rec = '';
    if (clicks >= 5 && cost >= 5) rec = 'Strong negative candidate';
    else if (clicks >= 3) rec = 'Possible negative';
    else rec = 'Monitor';
    rows.push([r['search_term_view.search_term'], r['campaign.name'], cost.toFixed(2), clicks, imps, rec]);
  }
  writeRows(sh, headers, rows);
}

// ---------- Ads ----------
function exportAds(ss) {
  var sh = clearOrCreateSheet(ss, '6_Ads');
  var headers = ['Campaign', 'Ad Group', 'Ad Type', 'Status', 'Headline 1', 'Headline 2',
                 'Description 1', 'Final URL', 'Impressions', 'Clicks', 'CTR %',
                 'Cost', 'Conversions'];
  var rows = [];
  var iter = AdsApp.ads().forDateRange(DATE_RANGE).get();
  while (iter.hasNext()) {
    var ad = iter.next();
    var s = ad.getStatsFor(DATE_RANGE);
    if (s.getImpressions() === 0) continue;
    var h1 = '', h2 = '', d1 = '';
    try {
      if (ad.getType() === 'EXPANDED_TEXT_AD') {
        h1 = ad.asType().expandedTextAd().getHeadlinePart1();
        h2 = ad.asType().expandedTextAd().getHeadlinePart2();
        d1 = ad.asType().expandedTextAd().getDescription();
      } else if (ad.getType() === 'RESPONSIVE_SEARCH_AD') {
        var rsa = ad.asType().responsiveSearchAd();
        var hs = rsa.getHeadlines();
        if (hs && hs.length) {
          h1 = hs[0] && hs[0].text ? hs[0].text : '';
          h2 = hs[1] && hs[1].text ? hs[1].text : '';
        }
        var ds = rsa.getDescriptions();
        if (ds && ds.length) d1 = ds[0] && ds[0].text ? ds[0].text : '';
      }
    } catch (e) {}
    rows.push([
      ad.getCampaign().getName(),
      ad.getAdGroup().getName(),
      ad.getType(),
      ad.isEnabled() ? 'ENABLED' : 'PAUSED',
      h1, h2, d1,
      ad.urls().getFinalUrl ? ad.urls().getFinalUrl() : '',
      s.getImpressions(),
      s.getClicks(),
      (s.getCtr() * 100).toFixed(2),
      s.getCost(),
      s.getConversions(),
    ]);
  }
  writeRows(sh, headers, rows);
}

// ============================================================
// Performance Max specific reports
// ============================================================

// ---------- pMax Asset Groups ----------
function exportPMaxAssetGroups(ss) {
  var sh = clearOrCreateSheet(ss, 'P1_PMax_AssetGroups');
  var headers = ['Campaign', 'Asset Group', 'Status', 'Impressions', 'Clicks',
                 'CTR %', 'Cost', 'Conversions', 'Conv Value', 'Cost/Conv', 'ROAS'];
  var rows = [];
  var query =
    'SELECT campaign.name, asset_group.name, asset_group.status, ' +
    'metrics.impressions, metrics.clicks, metrics.ctr, metrics.cost_micros, ' +
    'metrics.conversions, metrics.conversions_value ' +
    'FROM asset_group ' +
    'WHERE segments.date DURING ' + DATE_RANGE + ' ' +
    'ORDER BY metrics.cost_micros DESC';
  try {
    var report = AdsApp.report(query);
    var rIter = report.rows();
    while (rIter.hasNext()) {
      var r = rIter.next();
      var imps = Number(r['metrics.impressions']) || 0;
      var clicks = Number(r['metrics.clicks']) || 0;
      var cost = (Number(r['metrics.cost_micros']) || 0) / 1000000;
      var conv = Number(r['metrics.conversions']) || 0;
      var val = Number(r['metrics.conversions_value']) || 0;
      rows.push([
        r['campaign.name'],
        r['asset_group.name'],
        r['asset_group.status'],
        imps, clicks,
        ((Number(r['metrics.ctr']) || 0) * 100).toFixed(2),
        cost.toFixed(2),
        conv, val.toFixed(2),
        conv > 0 ? (cost / conv).toFixed(2) : '',
        cost > 0 ? (val / cost).toFixed(2) : '',
      ]);
    }
  } catch (e) { Logger.log('AssetGroups error: ' + e); }
  writeRows(sh, headers, rows);
}

// ---------- pMax Channel Breakdown ----------
function exportPMaxChannelBreakdown(ss) {
  var sh = clearOrCreateSheet(ss, 'P2_PMax_ChannelBreakdown');
  var headers = ['Campaign', 'Channel', 'Sub-Channel', 'Impressions', 'Clicks',
                 'CTR %', 'Cost', 'Conversions', 'Cost/Conv'];
  var rows = [];
  var query =
    'SELECT campaign.name, segments.ad_network_type, ' +
    'metrics.impressions, metrics.clicks, metrics.ctr, metrics.cost_micros, metrics.conversions ' +
    'FROM campaign ' +
    'WHERE campaign.advertising_channel_type = "PERFORMANCE_MAX" ' +
    'AND segments.date DURING ' + DATE_RANGE + ' ' +
    'ORDER BY metrics.cost_micros DESC';
  try {
    var report = AdsApp.report(query);
    var rIter = report.rows();
    while (rIter.hasNext()) {
      var r = rIter.next();
      var clicks = Number(r['metrics.clicks']) || 0;
      var conv = Number(r['metrics.conversions']) || 0;
      var cost = (Number(r['metrics.cost_micros']) || 0) / 1000000;
      rows.push([
        r['campaign.name'],
        r['segments.ad_network_type'],
        '',
        Number(r['metrics.impressions']) || 0,
        clicks,
        ((Number(r['metrics.ctr']) || 0) * 100).toFixed(2),
        cost.toFixed(2),
        conv,
        conv > 0 ? (cost / conv).toFixed(2) : '',
      ]);
    }
  } catch (e) { Logger.log('ChannelBreakdown error: ' + e); }
  writeRows(sh, headers, rows);
}

// ---------- pMax Search Categories ----------
function exportPMaxSearchCategories(ss) {
  var sh = clearOrCreateSheet(ss, 'P3_PMax_SearchCategories');
  var headers = ['Campaign', 'Category Label', 'Impressions', 'Clicks', 'Conversions', 'Note'];
  var rows = [];
  var query =
    'SELECT campaign.name, campaign_search_term_insight.category_label, ' +
    'metrics.impressions, metrics.clicks, metrics.conversions ' +
    'FROM campaign_search_term_insight ' +
    'WHERE segments.date DURING ' + DATE_RANGE + ' ' +
    'ORDER BY metrics.impressions DESC ' +
    'LIMIT 500';
  try {
    var report = AdsApp.report(query);
    var rIter = report.rows();
    while (rIter.hasNext()) {
      var r = rIter.next();
      var imps = Number(r['metrics.impressions']) || 0;
      var clicks = Number(r['metrics.clicks']) || 0;
      var conv = Number(r['metrics.conversions']) || 0;
      var note = '';
      if (clicks >= 10 && conv === 0) note = 'High clicks no conversions - candidate for negative theme';
      else if (imps >= 1000 && clicks < 5) note = 'High impressions low clicks - possibly irrelevant';
      rows.push([r['campaign.name'], r['campaign_search_term_insight.category_label'], imps, clicks, conv, note]);
    }
  } catch (e) { Logger.log('SearchCategories error: ' + e); }
  writeRows(sh, headers, rows);
}

// ---------- pMax Listing Groups (Shopping) ----------
function exportPMaxListingGroups(ss) {
  var sh = clearOrCreateSheet(ss, 'P4_PMax_Products');
  var headers = ['Campaign', 'Product Title', 'Product ID', 'Impressions',
                 'Clicks', 'Cost', 'Conversions', 'Conv Value', 'ROAS'];
  var rows = [];
  var query =
    'SELECT campaign.name, segments.product_title, segments.product_item_id, ' +
    'metrics.impressions, metrics.clicks, metrics.cost_micros, ' +
    'metrics.conversions, metrics.conversions_value ' +
    'FROM shopping_performance_view ' +
    'WHERE segments.date DURING ' + DATE_RANGE + ' ' +
    'ORDER BY metrics.cost_micros DESC ' +
    'LIMIT 500';
  try {
    var report = AdsApp.report(query);
    var rIter = report.rows();
    while (rIter.hasNext()) {
      var r = rIter.next();
      var cost = (Number(r['metrics.cost_micros']) || 0) / 1000000;
      var val = Number(r['metrics.conversions_value']) || 0;
      rows.push([
        r['campaign.name'],
        r['segments.product_title'],
        r['segments.product_item_id'],
        Number(r['metrics.impressions']) || 0,
        Number(r['metrics.clicks']) || 0,
        cost.toFixed(2),
        Number(r['metrics.conversions']) || 0,
        val.toFixed(2),
        cost > 0 ? (val / cost).toFixed(2) : '',
      ]);
    }
  } catch (e) {
    Logger.log('ProductsReport error (probably not an ecom account): ' + e);
    sh.getRange(1, 1).setValue('No shopping data (likely a non-ecom account)');
    return;
  }
  writeRows(sh, headers, rows);
}

// ---------- pMax Asset Performance ----------
function exportPMaxAssetPerformance(ss) {
  var sh = clearOrCreateSheet(ss, 'P5_PMax_Assets');
  var headers = ['Campaign', 'Asset Group', 'Asset Type', 'Performance Label',
                 'Asset Text/Source', 'Field Type'];
  var rows = [];
  var query =
    'SELECT campaign.name, asset_group.name, asset.type, ' +
    'asset_group_asset.performance_label, asset.text_asset.text, ' +
    'asset.image_asset.full_size.url, asset_group_asset.field_type ' +
    'FROM asset_group_asset ' +
    'WHERE asset_group_asset.status = "ENABLED" ' +
    'LIMIT 1000';
  try {
    var report = AdsApp.report(query);
    var rIter = report.rows();
    while (rIter.hasNext()) {
      var r = rIter.next();
      var src = r['asset.text_asset.text'] || r['asset.image_asset.full_size.url'] || '';
      rows.push([
        r['campaign.name'],
        r['asset_group.name'],
        r['asset.type'],
        r['asset_group_asset.performance_label'],
        src,
        r['asset_group_asset.field_type'],
      ]);
    }
  } catch (e) { Logger.log('AssetPerformance error: ' + e); }
  writeRows(sh, headers, rows);
}

// ---------- Brand Cannibalization Check ----------
function exportBrandCannibalizationCheck(ss) {
  var sh = clearOrCreateSheet(ss, 'P6_BrandCannibalizationCheck');
  sh.getRange(1, 1).setValue('בדיקת קניבליזציה בין pMax ל-Search Brand').setFontWeight('bold').setFontSize(14);
  sh.getRange(2, 1).setValue('פירוט: pMax עלולה "לגנוב" את הטראפיק שמלכתחילה היה מגיע מקמפיין ברנד.');
  sh.getRange(3, 1).setValue('ניתן לבדוק על ידי בידוד טראפיק שכולל את שם המותג ב-search categories.');

  var headers = ['Campaign Type', 'Campaign', 'Impressions', 'Clicks', 'Cost', 'Conversions', 'Cost/Conv'];
  var rows = [];
  var query =
    'SELECT campaign.name, campaign.advertising_channel_type, ' +
    'metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions ' +
    'FROM campaign ' +
    'WHERE campaign.status = "ENABLED" ' +
    'AND segments.date DURING ' + DATE_RANGE + ' ' +
    'AND (campaign.advertising_channel_type = "PERFORMANCE_MAX" OR campaign.advertising_channel_type = "SEARCH") ' +
    'ORDER BY metrics.cost_micros DESC';
  try {
    var report = AdsApp.report(query);
    var rIter = report.rows();
    while (rIter.hasNext()) {
      var r = rIter.next();
      var clicks = Number(r['metrics.clicks']) || 0;
      var conv = Number(r['metrics.conversions']) || 0;
      var cost = (Number(r['metrics.cost_micros']) || 0) / 1000000;
      rows.push([
        r['campaign.advertising_channel_type'],
        r['campaign.name'],
        Number(r['metrics.impressions']) || 0,
        clicks, cost.toFixed(2), conv,
        conv > 0 ? (cost / conv).toFixed(2) : '',
      ]);
    }
  } catch (e) { Logger.log('Cannibalization error: ' + e); }
  if (rows.length) {
    sh.getRange(5, 1, 1, headers.length).setValues([headers]).setFontWeight('bold').setBackground('#1a73e8').setFontColor('#ffffff');
    sh.getRange(6, 1, rows.length, headers.length).setValues(rows);
    sh.autoResizeColumns(1, headers.length);
  }
}

// ---------- pMax Audit Checklist ----------
function exportPMaxAuditChecklist(ss) {
  var sh = clearOrCreateSheet(ss, 'P7_PMax_Audit');
  var rows = [['Check', 'Campaign', 'Status', 'Recommendation']];

  var iter = AdsApp.performanceMaxCampaigns ? AdsApp.performanceMaxCampaigns().get() : null;
  if (!iter) {
    sh.getRange(1, 1).setValue('Performance Max API לא זמין בסקריפט זה. בדיקה ידנית נדרשת.');
    return;
  }
  while (iter.hasNext()) {
    var c = iter.next();
    var name = c.getName();
    var status = c.isEnabled() ? 'ENABLED' : 'PAUSED';
    if (status !== 'ENABLED') continue;

    // Asset groups count
    var agCount = 0;
    try {
      var agIter = c.assetGroups().get();
      while (agIter.hasNext()) { agIter.next(); agCount++; }
    } catch (e) {}
    if (agCount <= 1) {
      rows.push(['Asset Groups Count', name, agCount + ' asset group(s)',
                 'מומלץ פיצול לפי תמה/קהל - לפחות 2-3 asset groups']);
    }

    // Final URL Expansion
    try {
      var urlExp = c.urlExpansionOptOut ? c.urlExpansionOptOut() : null;
      if (urlExp === false) {
        rows.push(['Final URL Expansion', name, 'ENABLED',
                   'בדוק אם זה רלוונטי - אם יש דפים לא ממירים באתר, כבה או הגבל path']);
      }
    } catch (e) {}
  }

  if (rows.length === 1) {
    rows.push(['—', '—', 'אין pMax campaigns פעילים או שלא זוהו בעיות', '']);
  }
  sh.getRange(1, 1, rows.length, 4).setValues(rows);
  sh.getRange(1, 1, 1, 4).setFontWeight('bold').setBackground('#1a73e8').setFontColor('#ffffff');
  sh.autoResizeColumns(1, 4);
}

// ---------- Index ----------
function buildIndexSheet(ss) {
  var sh = clearOrCreateSheet(ss, '_Index');
  ss.setActiveSheet(sh);
  ss.moveActiveSheet(1);
  var rows = [
    ['Google Ads Export', ''],
    ['Account', AdsApp.currentAccount().getName()],
    ['Generated', new Date().toString()],
    ['Date Range', DATE_RANGE],
    ['', ''],
    ['Sheet', 'Description'],
    ['0_Account_Summary', 'KPIs ברמת חשבון'],
    ['1_Campaigns', 'ביצועים ברמת קמפיין'],
    ['2_AdGroups', 'ביצועים ברמת קבוצת מודעות'],
    ['3_Keywords', 'מילות מפתח עם Quality Score'],
    ['4_SearchTerms', 'Search terms ראשיים (סף מינימום הופעות)'],
    ['5_NegativeCandidates', 'מועמדים ל-keywords שליליות (כסף בלי המרות)'],
    ['6_Ads', 'ביצועי מודעות'],
    ['', ''],
    ['Performance Max specific:', ''],
    ['P1_PMax_AssetGroups', 'ביצועי asset groups'],
    ['P2_PMax_ChannelBreakdown', 'פירוק לפי ערוץ (Search/Display/YouTube/Shopping)'],
    ['P3_PMax_SearchCategories', 'תמות חיפוש שגוגל זיהתה (במקום search terms קלאסיים)'],
    ['P4_PMax_Products', 'ביצועי מוצרים (אם יש Shopping feed)'],
    ['P5_PMax_Assets', 'איכות נכסים (Best/Good/Low) לפי asset'],
    ['P6_BrandCannibalizationCheck', 'בדיקת קניבליזציה בין pMax לקמפיין Brand'],
    ['P7_PMax_Audit', 'Checklist אוטומטי לבעיות נפוצות'],
  ];
  sh.getRange(1, 1, rows.length, 2).setValues(rows);
  sh.getRange(1, 1, 1, 2).setFontWeight('bold').setFontSize(14);
  sh.getRange(6, 1, 1, 2).setFontWeight('bold').setBackground('#1a73e8').setFontColor('#ffffff');
  sh.autoResizeColumns(1, 2);
}

נעים להכיר,

הכירו את הכותב/ת

מנהל פרסום ממומן וביצועים

10 שנות ניסיון

יובל י., מנהל פרסום ממומן וביצועים בברנדיני. מומחה Google Ads, Meta Ads ודפי נחיתה. מתמקד ב-ROI ואופטימיזציית קמפיינים.

Meta Ads Google Ads דפי נחיתה CRO
כניסת לקוחות
ברנדיני | סוכנות פרסום דיגיטלי, מיתוג ובניית אתרים לעסקים - סוכנות פרסום ברנדיני