Cómo enviar datos de la API de Data Export al formato CSV

Alexander Lucas, equipo de la API de Google Analytics, agosto de 2010


Introducción

En este artículo, se explica cómo tomar datos de cualquier consulta realizada en la API de Data Export de Google Analytics y generar los resultados en formato CSV popular. Esta es una de las tareas más comunes que las personas realizan con los datos de Analytics extraídos de la API de Data Export, por lo que automatizar el proceso es una forma fácil de ahorrar mucho tiempo de manera frecuente. Además, una vez que tengas código para imprimir documentos CSV a partir de consultas, podrás integrarlo en proyectos más grandes, como generadores de informes automáticos, anuncios por correo y funciones de “exportación” para los paneles personalizados que escribiste.

Antes de comenzar

Sacarás el máximo provecho de este artículo si tienes lo siguiente:

Descripción general del programa

El código que se incluye en este artículo hará lo siguiente:

  1. Habilita la elección en el entorno de ejecución si el código se imprimirá en la consola o en un flujo de archivos.
  2. Con un objeto DataFeed como parámetro, imprime los datos en formato CSV:
    • Imprime los encabezados de filas.
    • Imprime filas de datos, en las que cada DataEntry constituye una fila en el resultado.
    • Ejecuta cada valor a través de un método de limpieza para obtener un resultado seguro de CSV.
  3. Escribe un método "Sanitizer" que haga que todas las entradas sean seguras para CSV.
  4. Proporcionarte una clase de Java que puede tomar cualquier consulta de la API de Data Export y convertirla en un archivo CSV.

Volver al principio

Permitir transmisiones de salida configurables

Lo primero que debes hacer es configurar una transmisión de salida configurable para que tu clase se imprima en ella. De esta manera, cualquier código que use tu clase puede decidir si el resultado debe pasar a una salida estándar o directamente a un archivo. Lo único que debes hacer aquí es configurar el método get/set para un objeto PrintStream. Ese será el objetivo de todas las impresiones que realice la clase.

private PrintStream printStream = System.out;

public PrintStream getPrintStream() {
  return printStream;
}

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

Configurar el resultado en un archivo también es muy fácil. Se necesita solo el nombre de archivo para crear un objeto PrintStream para ese archivo.

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

Volver al principio

Iterar a través de los datos

La primera fila del archivo CSV es la fila de nombres de columna. Cada columna representa una dimensión o métrica del feed de datos. Por lo tanto, para imprimir esta primera fila, haz lo siguiente:

  1. Toma la primera entrada del feed.
  2. Itera a través de una lista de dimensiones con el método getDimensions de esa entrada.
  3. Imprime el nombre de cada dimensión con el método Dimension.getName() seguido de una coma.
  4. Haz lo mismo para las métricas con el método getMetrics(). Imprime comas después de todas las métricas, excepto la última.

Esta es una implementación del método para imprimir encabezados de filas. Ten en cuenta que este código no muestra una string que represente la fila completa, sino que se imprime en un flujo de salida a medida que procesa los valores.

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();
  }

Imprimir el “cuerpo” del archivo CSV (todo lo que se encuentra debajo de la fila de nombres de columna) es muy similar. Solo hay dos diferencias clave. En primer lugar, no se trata solo de la primera entrada que se evalúa. El código debe recorrer en bucle todas las entradas del objeto del feed. En segundo lugar, en lugar de usar el método getName() para extraer el valor que se va a limpiar e imprimir, utiliza 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();
  }

Este código divide tu feed en entradas y tus entradas en valores que se imprimirán en el resultado. Pero ¿cómo podemos hacer que esos valores sean compatibles con los archivos CSV? ¿Qué pasa si un valor en el archivo "valores separados por comas" tiene una coma? Esos valores se deben limpiar.

Volver al principio

Cómo limpiar datos para que sean compatibles con CSV

CSV es un formato sencillo. Un archivo CSV representa una tabla de datos, y cada línea representa una fila de esa tabla. Los valores en esa fila están separados por comas. Una nueva línea significa una nueva fila de datos.

Lamentablemente, este formato sencillo hace que sea falsamente fácil deshacerse de los datos incorrectos. ¿Qué pasa si el valor está escrito con una coma? ¿Qué pasa si uno de tus valores tiene saltos de línea? ¿Qué debería suceder con el espacio entre comas y valores? Todas estas situaciones pueden justificarse con algunas reglas simples.

  • Si la string contiene un carácter de comillas dobles, escápalo con un segundo carácter de comillas dobles.
  • Si hay una coma en la string, encierra toda la string entre comillas dobles (a menos que ya lo hayas hecho).
  • Si hay un salto de línea en la string, encierra toda la string entre comillas dobles (a menos que ya lo hayas hecho).
  • Si la string comienza o termina con algún tipo de espacio en blanco, envuelve toda la string entre comillas dobles (a menos que ya lo hayas hecho).

Puede ser un poco complicado visualizar cómo deberían verse tus valores en este punto, así que aquí tienes algunos ejemplos. Recuerda que cada ejemplo representa un valor único y se escapa como tal. Para mayor claridad, los espacios se mostrarán como un carácter _.

Antes Después
sin modificar sin modificar
comillas dobles aleatorias comillas dobles aleatorias “”
coma,separado "coma,separado por comas"
Dos
líneas
"Dos
líneas"
_inicial y una coma "_cambio de espacio y una coma"
"comilla inicial, coma """comilla inicial, coma"
_espacio, coma
segunda línea y comillas dobles"
“_space, coma
segunda línea y comillas dobles””

La forma más fácil de manejar todas estas condiciones es escribir un método de limpieza. Ingresan datos cuestionables y salen valores CSV buenos y limpios. A continuación, se incluye un buen ejemplo de implementación de ese método.

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();
}

El método comienza por buscar comillas dobles existentes. Esto debe hacerse antes que todas las demás verificaciones, ya que implican unir una string con comillas dobles, y sería molesto determinar la diferencia entre las comillas dobles que formaban parte del valor y las comillas dobles que este método agregó antes. Es fácil escapar de ellas: solo hay que duplicarlas. Cada " se convierte en "", cada "" se convierte en """", y así sucesivamente.

Una vez que se cumple esa condición, se pueden verificar todas las demás condiciones (espacio en blanco sin cortar, comas y saltos de línea). Si alguno de ellos está presente, simplemente encierra el valor entre comillas dobles.

Ten en cuenta que lo anterior usa un objeto StringBuilder y nunca manipula directamente una cadena sin procesar. Esto se debe a que StringBuilder te permite manipular libremente la cadena sin hacer copias provisionales en la memoria. Como las cadenas en Java son inmutables, cada ajuste menor que hagas creará una cadena nueva. Al arrastrar datos de las hojas de cálculo, esto puede sumarse muy rápido.

Cantidad de filas x valores por fila x Cambios en el valor = Total de nuevas cadenas creadas
10,000 10 3 300,000

Volver al principio

Próximos pasos

Ahora que recibiste un martillo dorado, es natural que vayas a buscar uñas. Estas son algunas ideas para comenzar.

  • Consulta el código fuente de la aplicación de muestra que usa esta clase para imprimir un archivo CSV basado en una consulta de muestra. Toma un nombre de archivo de salida como parámetro de la línea de comandos y, de forma predeterminada, lo imprime como estándar. Úsalo como punto de partida para crear algo increíble.
  • CSV es solo uno de muchos formatos populares. Ajusta la clase para que se muestre en un formato diferente, como TSV, YAML, JSON o XML.
  • Escribe una aplicación que genere CSV y los envíe por correo cuando termine. Informes mensuales automatizados y sencillos
  • Escribe una aplicación que te permita ingresar consultas de manera interactiva para tener una interfaz potente que te permita explorar tus datos.