Output dei dati dall'API di esportazione dei dati in formato CSV

Alexander Lucas, Team API di Google Analytics – Agosto 2010


Introduzione

Questo articolo spiega come recuperare i dati di qualsiasi query effettuata all'API di esportazione dei dati di Google Analytics e generare i risultati nel formato CSV più comune. Questa è una delle attività più comuni eseguite dagli utenti con i dati di Analytics estratti dall'API di esportazione dei dati. Automatizzare il processo è un modo semplice per risparmiare tempo e denaro. Inoltre, se disponi del codice per stampare i documenti CSV dalle query, potrai integrarlo in progetti più grandi, come generatori automatici di report, mailer ed funzioni di "esportazione" per le dashboard personalizzate che hai scritto.

Prima di iniziare

Otterrai il massimo da questo articolo se disponi di quanto segue:

Panoramica del programma

Il codice descritto in questo articolo ha le seguenti caratteristiche:

  1. Abilita la scelta in fase di runtime se il codice viene stampato sulla console o in uno stream di file.
  2. Dato un oggetto DataFeed come parametro, stampa i dati in formato CSV:
    • Stampa intestazioni di riga.
    • Stampa righe di dati, dove ogni DataEntry costituisce una riga nell'output risultante.
    • Esegui ogni valore tramite un metodo di sanitizzazione per un output compatibile con CSV.
  3. Scrivi un metodo "Sanitizer" che renda tutti gli input sicuri per i file CSV.
  4. Fornirti una classe Java che possa elaborare qualsiasi query dell'API di esportazione dei dati e trasformarla in un file CSV.

Torna all'inizio

Consenti flussi di output configurabili

La prima cosa da fare è configurare uno stream di output configurabile per la stampa. In questo modo qualsiasi codice che utilizza la tua classe può decidere se l'output deve passare allo standard o direttamente a un file. Devi solo configurare il metodo getter/setter per un oggetto PrintStream. che sarà il target di tutte le stampe eseguite dalla classe.

private PrintStream printStream = System.out;

public PrintStream getPrintStream() {
  return printStream;
}

public void setPrintStream(PrintStream printStream) {
  this.printStream = printStream;
}

Anche impostare l'output su un file è molto semplice. È sufficiente il nome del file per creare un oggetto PrintStream per il file.

FileOutputStream fstream = new FileOutputStream(filename);
PrintStream stream = new PrintStream(fstream);
csvprinter.setPrintStream(stream);

Torna all'inizio

Ripetizione dei dati

La prima riga del file CSV è la riga dei nomi delle colonne. Ogni colonna rappresenta una dimensione o una metrica del feed di dati; pertanto, per stampare questa prima riga, procedi nel seguente modo.

  1. Prendi la prima voce dal feed.
  2. Esegui l'iterazione dell'elenco di dimensioni utilizzando il metodo getDimensions di tale voce.
  3. Stampa il nome di ogni dimensione utilizzando il metodo Dimension.getName(), seguito da una virgola.
  4. Ripeti l'operazione per le metriche utilizzando il metodo getMetrics(). Stampa le virgole dopo tutte le metriche tranne l'ultima.

Di seguito è riportata un'implementazione del metodo per stampare le intestazioni di riga. Tieni presente che questo codice non restituisce una stringa che rappresenta la riga completa, ma viene stampato in un flusso di output durante l'elaborazione dei valori.

public void printRowHeaders(DataFeed feed) {
    if(feed.getEntries().size() == 0) {
      return;
    }

    DataEntry firstEntry = feed.getEntries().get(0);

    Iterator<Dimension> dimensions = firstEntry.getDimensions().iterator();
    while (dimensions.hasNext()) {
      printStream.print(sanitizeForCsv(dimensions.next().getName()));
      printStream.print(",");
    }

    Iterator<Metric> metrics = firstEntry.getMetrics().iterator();
    while (metrics.hasNext()) {
      printStream.print(sanitizeForCsv(metrics.next().getName()));
      if (metrics.hasNext()) {
        printStream.print(",");
      }
    }
    printStream.println();
  }

La stampa del "corpo" del file CSV (tutto ciò che si trova sotto la riga dei nomi delle colonne) è molto simile. Ci sono solo due differenze fondamentali. Innanzitutto, non riguarda solo la prima voce oggetto di valutazione. Il codice deve scorrere tutte le voci nell'oggetto del feed. In secondo luogo, invece di utilizzare il metodo getName() per estrarre il valore da sanitizzare e stampare, usa getValue().

public void printBody(DataFeed feed) {
    if(feed.getEntries().size() == 0) {
      return;
    }

    for (DataEntry entry : feed.getEntries()) {
      printEntry(entry);
    }
  }

  public void printEntry(DataEntry entry) {
    Iterator<Dimension> dimensions = entry.getDimensions().iterator();
    while (dimensions.hasNext()) {
      printStream.print(sanitizeForCsv(dimensions.next().getValue()));
      printStream.print(",");
    }

    Iterator<Metric> metrics = entry.getMetrics().iterator();
    while (metrics.hasNext()) {
      printStream.print(sanitizeForCsv(metrics.next().getValue()));
      if (metrics.hasNext()) {
        printStream.print(",");
      }
    }
    printStream.println();
  }

Questo codice suddivide il feed in voci, mentre le voci in valori da stampare per l'output. Ma come possiamo rendere questi valori compatibili con i file CSV? Cosa succede se un valore nel file "valori separati da virgole" contiene una virgola? Questi valori devono essere bonificati.

Torna all'inizio

Come eliminare i dati per garantirne la compatibilità con i file CSV

CSV è un formato semplice. Un file CSV rappresenta una tabella di dati e ogni riga rappresenta una riga in quella tabella. I valori nella riga sono separati da virgole. Una nuova riga indica una nuova riga di dati.

Sfortunatamente, questo formato semplice consente ingannevolmente di eliminare i dati con dati errati. Che cosa succede se il valore contiene una virgola? Che cosa succede se uno dei valori include interruzioni di riga al suo interno? Cosa dovrebbe succedere con lo spazio tra virgole e valori? Tutte queste situazioni possono essere prese in considerazione con poche semplici regole.

  • Se la stringa contiene un carattere apice, inserisci l'escape di una seconda virgoletta.
  • Se la stringa contiene una virgola, racchiudi l'intera stringa tra virgolette (a meno che tu non la abbia già specificata).
  • Se la stringa contiene un'interruzione di riga, racchiudi l'intera stringa tra virgolette (a meno che non l'hai già fatto).
  • Se la stringa inizia o termina con uno spazio vuoto, racchiudi l'intera stringa tra virgolette (a meno che tu non l'abbia già fatto).

Può essere un po' difficile visualizzare i valori a questo punto, ecco alcuni esempi. Ricorda che ogni esempio rappresenta un singolo valore e come tale contiene i caratteri di escape. Per chiarezza, gli spazi saranno rappresentati come un carattere _.

Prima Dopo
invariata invariata
virgolette " casuali virgolette "" casuali
virgole,separati "virgola,separato"
Due
righe
"Due
righe"
spazio _principale e una virgola "spazio_principale e una virgola"
"citazione iniziale, virgola """citazione iniziale, virgola"
_space, virgola
seconda riga e virgolette doppie"
"_spazio, virgola
seconda riga e virgolette doppie""

Il modo più semplice per gestire tutte queste condizioni è scrivere un metodo di sanitizzazione. Vengono inseriti dati discutibili e valori CSV validi e puliti. Ecco un esempio di implementazione di questo metodo.

private String sanitizeForCsv(String cellData) {
  StringBuilder resultBuilder = new StringBuilder(cellData);

  // Look for doublequotes, escape as necessary.
  int lastIndex = 0;
  while (resultBuilder.indexOf("\"", lastIndex) >= 0) {
    int quoteIndex = resultBuilder.indexOf("\"", lastIndex);
    resultBuilder.replace(quoteIndex, quoteIndex + 1, "\"\"");
    lastIndex = quoteIndex + 2;
  }

  char firstChar = cellData.charAt(0);
  char lastChar = cellData.charAt(cellData.length() - 1);

  if (cellData.contains(",") || // Check for commas
      cellData.contains("\n") ||  // Check for line breaks
      Character.isWhitespace(firstChar) || // Check for leading whitespace.
      Character.isWhitespace(lastChar)) { // Check for trailing whitespace
      resultBuilder.insert(0, "\"").append("\""); // Wrap in doublequotes.
  }
    return resultBuilder.toString();
}

Il metodo inizia controllando la presenza di virgolette doppie esistenti. Questa operazione deve essere eseguita prima di tutti gli altri controlli, poiché prevede l'avvolgimento di una stringa tra virgolette doppie e sarebbe fastidioso determinare la differenza tra le virgolette doppie che facevano parte del valore e le virgolette doppie aggiunte in precedenza con questo metodo. Sono facili da evitare: devono solo essere raddoppiati. Ogni " diventa "", ogni "" diventa """" e così via.

Una volta soddisfatta questa condizione, puoi controllare tutte le altre condizioni (spazio vuoto non tagliato, virgole e interruzioni di riga). Se sono presenti anche altri valori, racchiudi il valore tra virgolette doppie.

Tieni presente che quanto riportato sopra utilizza un oggetto StringBuilder e non manipola mai direttamente una stringa non elaborata. Questo perché StringBuilder ti consente di manipolare liberamente la stringa senza creare copie intermedie in memoria. Poiché le stringhe in Java sono immutabili, ogni piccolo ritocco creerà una stringa completamente nuova. Quando si scorrono i dati dei fogli di lavoro, la somma può essere aggiunta molto rapidamente.

Numero di righe x valori per riga x Il valore cambia = Totale nuove stringhe create
10.000 10 3 300.000

Torna all'inizio

Cosa devo fare adesso?

Ora che ti hanno dato un martello d'oro, è naturale andare a caccia di chiodi. Ecco alcune idee per iniziare.

  • Dai un'occhiata al codice sorgente dell'applicazione di esempio che utilizza questa classe per stampare un file CSV sulla base di una query di esempio. Prende un nome file di output come parametro della riga di comando e lo visualizza come standard. Usalo come punto di partenza per creare qualcosa di fantastico.
  • Il CSV è solo uno dei tanti formati più popolari. Modifica la classe in modo che generi un output in un formato diverso, come TSV, YAML, JSON o XML.
  • Scrivi un'applicazione che generi file CSV e li invii per posta al termine dell'operazione. Creazione di report mensili automatici e facili da usare.
  • Scrivi un'applicazione che consenta di inserire le query in modo interattivo, per un'interfaccia potente per analizzare i dati.