· 7 min di lettura

Labelizer v4 — prodotti con periodo di tempo variabile

Ho aggiornato il Labelizer: ora puoi scegliere il periodo di analisi (7, 30, 60, 90 giorni) e classificare automaticamente i tuoi prodotti in 6 cluster di performance.

Labelizer v4 — prodotti con periodo di tempo variabile

Cos'è il Labelizer?

Il Labelizer è uno strumento che uso per etichettare automaticamente i prodotti in base alle loro performance nelle campagne Shopping e Performance Max.

Con questa quarta versione ho aggiunto la possibilità di scegliere il periodo di analisi: puoi analizzare gli ultimi 7, 30, 60 o 90 giorni in base alle tue esigenze.

Come classifica i prodotti

Lo script divide i prodotti in sei categorie:

ClusterCriterio
Zero clicImpressioni ma nessun click
Zero conversioniClick ma nessuna conversione
Costoso100+ click con ROAS sotto obiettivo
Redditizio100+ click con ROAS sopra obiettivo
Partito beneMeno di 100 click, ROAS sopra obiettivo
Partito maleMeno di 100 click, ROAS sotto obiettivo

Come si usa, in tre passaggi

Tre passaggi:

  1. Copia il template dello sheet (link disponibile su richiesta)
  2. Compila il foglio "inserimento dati" con il periodo, l'etichetta da valorizzare e il tuo ROAS obiettivo
  3. Carica il file come feed supplementare in Merchant Center

Il Labelizer non elimina né modifica i prodotti — aggiunge solo un'etichetta personalizzata che puoi usare per strutturare le tue campagne.

Cosa fare con le etichette una volta assegnate

Alcune indicazioni pratiche:

  • Zero clic: verifica titoli e immagini dei prodotti. Spesso il problema è nella scheda, non nella campagna.
  • Zero conversioni: non eliminarli subito. Potrebbero essere prodotti che l'algoritmo sta ancora esplorando.
  • Costosi: candidati all'esclusione o a una campagna separata con bid più bassi.
  • Redditizi: prodotti da scalare. Considera di spostarli in una campagna dedicata con più budget.

Creare campagne separate ha senso solo quando hai volumi significativi di conversioni. Con pochi dati, la segmentazione spezza il segnale e peggiora le performance.

Il codice dello script

/**
 * Script per etichettare in modo automatico i prodotti in base alla performance con periodo di tempo variabile. Potete tornare indietro del numero di giorni che volete.
 *
 * Autore Giuseppe Scollo. Per maggiori informazioni visitare Giuseppescollo.it 
 *
 * Istruzioni
 * 1) Copiare questo template e inserire l'URL in riga 14: https://docs.google.com/spreadsheets/d/1GJyuVM0JhPz5A3YM9ALWUKi4nFYtl6XD2rguDO9f7UY/copy
 * 2) Compilare il foglio "inserimento dati", scegliendo il numero di clic minimo per valutare un prodotto,  il roas obiettivo, l'etichetta personalizzata e il numero di giorni da analizzare
 * 3) Caricare lo sheet come feed supplementare e aggiungere la regola in merchant per i prodotti che non presentano un valore.
 */

function main() {
  // URL di default dello sheet, può essere modificato come richiesto
  const SHEET_URL = '';
  const TAB = 'Prodotti';
  
  // Apertura del foglio di lavoro e recupero del valore days
  let ss;
  let DAYS_LOOKBACK = 30; // Valore predefinito in caso di errore
  
  try {
    ss = SpreadsheetApp.openByUrl(SHEET_URL);
    // Recupero del valore dal range nominato "days"
    const daysRange = ss.getRangeByName("days");
    if (daysRange) {
      const daysValue = daysRange.getValue();
      if (!isNaN(daysValue) && daysValue > 0) {
        DAYS_LOOKBACK = daysValue;
        Logger.log(`Recuperato valore DAYS_LOOKBACK da range nominato "days": ${DAYS_LOOKBACK}`);
      } else {
        Logger.log("Valore DAYS_LOOKBACK non valido nel range nominato 'days', utilizzo valore predefinito: 90");
      }
    } else {
      Logger.log("Range nominato 'days' non trovato, utilizzo valore predefinito: 90");
    }
  } catch (e) {
    Logger.log("Errore durante il recupero del valore DAYS_LOOKBACK: " + e);
    // Il valore predefinito è già impostato, quindi continuiamo con l'esecuzione
  }
  
  // Funzione per creare un intervallo di date dinamico
function getDateRange(numDays) {
    let endDate = new Date(); // assume today is fine
    let startDate = new Date();
    startDate.setDate(endDate.getDate() - numDays);
    
    let timezone = AdsApp.currentAccount().getTimeZone(); // use the account timezone
    let formattedStartDate = Utilities.formatDate(startDate, timezone, 'yyyyMMdd'); // format date in a way that google allows
    let formattedEndDate = Utilities.formatDate(endDate, timezone, 'yyyyMMdd');
    
    return ` segments.date BETWEEN "${formattedStartDate}" AND "${formattedEndDate}" `; // create a string we can insert into the SQL
}
  
  // Query per ottenere i dati dei prodotti con intervallo di date dinamico
  const QUERY = `
  SELECT
    segments.product_title,
    segments.product_item_id,
    metrics.impressions,
    metrics.clicks,
    metrics.cost_micros,
    metrics.conversions,
    metrics.conversions_value
  FROM shopping_performance_view
  WHERE ${getDateRange(DAYS_LOOKBACK)}
  `;
  
  try {
    // Apertura del foglio di lavoro
    let ss = SpreadsheetApp.openByUrl(SHEET_URL);
    let sheet = ss.getSheetByName(TAB);
    
    // Creazione del foglio se non esiste
    if (!sheet) {
      Logger.log("Foglio '" + TAB + "' non trovato. Creazione di un nuovo foglio...");
      sheet = ss.insertSheet(TAB);
    } else {
      // Rimuovo eventuali filtri esistenti prima di pulire il foglio
      try {
        const filter = sheet.getFilter();
        if (filter) {
          filter.remove();
        }
      } catch (filterError) {
        Logger.log("Nessun filtro trovato o impossibile rimuovere: " + filterError);
      }
      
      // Pulizia del foglio esistente prima di aggiungere nuovi dati
      sheet.clear();
      Logger.log("Foglio '" + TAB + "' ripulito per i nuovi dati.");
    }
    
    // Aggiornamento dell'intestazione con l'intervallo di date
    let endDate = new Date();
    let startDate = new Date();
    startDate.setDate(endDate.getDate() - DAYS_LOOKBACK);
    let timezone = AdsApp.currentAccount().getTimeZone();
    let formattedStartDate = Utilities.formatDate(startDate, timezone, 'dd/MM/yyyy');
    let formattedEndDate = Utilities.formatDate(endDate, timezone, 'dd/MM/yyyy');
    
    Logger.log(`Esportazione dati dal ${formattedStartDate} al ${formattedEndDate}`);
    
    // Esecuzione della query 
    let report = AdsApp.report(QUERY);
    
    // Inizializzazione dell'array per i dati elaborati
    let data = [];
    let rowCount = 0;
    
    // Elaborazione dati
    let rows = report.rows();
    while (rows.hasNext()) {
      const row = rows.next();
      rowCount++;
      
      if (rowCount === 1) {
        Logger.log("Struttura primo risultato:");
        Logger.log(JSON.stringify(row));
      }
      
      // Accesso ai dati nel formato corretto
      const productTitle = row['segments.product_title'] || 'N/A';
      const productId = row['segments.product_item_id'] || 'N/A';
      const impressions = Number(row['metrics.impressions']) || 0;
      const clicks = Number(row['metrics.clicks']) || 0;
      const costMicros = Number(row['metrics.cost_micros']) || 0;
      const conversions = Number(row['metrics.conversions']) || 0;
      const convValue = Number(row['metrics.conversions_value']) || 0;
      
      // Calcolo metriche derivate
      const cost = costMicros / 1000000;
      const ctr = impressions > 0 ? (clicks / impressions) : 0;
      const cpc = clicks > 0 ? cost / clicks : 0;
      const convRate = clicks > 0 ? (conversions / clicks) : 0;
      const cpa = conversions > 0 ? cost / conversions : 0;
      const roas = cost > 0 ? convValue / cost : 0;
      
      data.push([
        productTitle,
        productId,
        impressions,
        clicks,
        ctr,
        cost,
        cpc,
        conversions,
        convRate,
        cpa,
        convValue,
        roas
      ]);
    }
    
    Logger.log(`Totale prodotti trovati: ${rowCount}`);
    
    // Scrittura dati
    if (data.length > 0) {
      // Aggiungiamo l'intervallo di date come prima riga
      sheet.getRange(1, 1).setValue(`Dati prodotti dal ${formattedStartDate} al ${formattedEndDate}`);
      sheet.getRange(1, 1, 1, 12).merge();
      sheet.getRange(1, 1).setFontWeight('bold').setHorizontalAlignment('center');
      
      const headers = [
        'Titolo Prodotto',
        'ID Prodotto',
        'Impressioni',
        'Click',
        'CTR (%)',
        'Costo (€)',
        'CPC (€)',
        'Conversioni',
        'Tasso Conv. (%)',
        'Costo/Conv. (€)',
        'Valore Conv. (€)',
        'ROAS'
      ];
      
      sheet.getRange(2, 1, 1, headers.length).setValues([headers]);
      sheet.getRange(3, 1, data.length, headers.length).setValues(data);
      
      // Formattazione numeri
      if (data.length > 0) {
        // Formato numerico per metriche intere
        sheet.getRange(3, 3, data.length, 2).setNumberFormat('#,##0');  // Impressioni, Click
        sheet.getRange(3, 8, data.length, 1).setNumberFormat('#,##0.00');  // Conversioni
        
        // Formato percentuale
        sheet.getRange(3, 5, data.length, 1).setNumberFormat('0.00%');  // CTR
        sheet.getRange(3, 9, data.length, 1).setNumberFormat('0.00%');  // Tasso Conv.
        
        // Formato valuta per costi
        sheet.getRange(3, 6, data.length, 1).setNumberFormat('€#,##0.00');  // Costo
        sheet.getRange(3, 7, data.length, 1).setNumberFormat('€#,##0.00');  // CPC
        sheet.getRange(3, 10, data.length, 1).setNumberFormat('€#,##0.00');  // Costo/Conv
        sheet.getRange(3, 11, data.length, 1).setNumberFormat('€#,##0.00');  // Valore Conv.
        
        // Formato ROAS
        sheet.getRange(3, 12, data.length, 1).setNumberFormat('0.00');
        
        // Formattazione intestazione
        sheet.getRange(2, 1, 1, headers.length)
          .setBackground('#4285F4')
          .setFontColor('white')
          .setFontWeight('bold');
          
        // Auto-dimensionamento delle colonne per leggibilità
        sheet.autoResizeColumns(1, headers.length);
        
        // Congelamento delle righe di intestazione
        sheet.setFrozenRows(2);
        
        // Aggiunta di filtri con controllo preventivo
        try {
          sheet.getRange(2, 1, 1, headers.length).createFilter();
        } catch (filterError) {
          Logger.log("Impossibile creare un filtro, probabile che ne esista già uno: " + filterError);
        }
      }
      
      Logger.log("Dati elaborati e formattati con successo");
    } else {
      sheet.getRange(1, 1).setValue(`Nessun dato di prodotto trovato dal ${formattedStartDate} al ${formattedEndDate}`);
      Logger.log("Nessun dato trovato");
    }
    
  } catch (e) {
    Logger.log("Errore durante l'esecuzione:");
    Logger.log(e);
    Logger.log("Stack trace:");
    Logger.log(e.stack);
  }
} 

Scrivimi se vuoi accedere al template o hai domande sul setup. Trovi anche la raccolta completa dei miei script.

Domande frequenti

Cos'è il Labelizer?

Uno script che etichetta automaticamente i prodotti in base alle loro performance nelle campagne Shopping e Performance Max, dividendoli in sei cluster che incrociano volume di clic e ROAS rispetto all'obiettivo.

Come ottengo il template del foglio Google?

Il template da usare con lo script lo trovi nel codice stesso, al punto 1 delle istruzioni in cima.

Che periodo di analisi posso scegliere?

7, 30, 60 o 90 giorni. È la novità di questa quarta versione: prima il periodo era fisso.

Il Labelizer modifica o elimina i prodotti?

No, non tocca nulla: aggiunge solo un'etichetta personalizzata che poi usi per strutturare le campagne.

Cosa faccio con i prodotti a zero conversioni?

Non eliminarli subito: potrebbero essere prodotti che l'algoritmo sta ancora esplorando. Sui prodotti a zero clic, invece, si guardano titolo e immagine: spesso il problema è nella scheda, non nella campagna.

← Torna al blog