Saída de dados da API Data Export para o formato CSV

Alexander Lucas, Equipe de API do Google Analytics – agosto de 2010


Introdução

Este artigo mostra como extrair dados de qualquer consulta feita à API de exportação de dados do Google Analytics e enviar os resultados para o formato popular CSV. Essa é uma das tarefas mais realizadas com dados do Google Analytics extraídos da API de exportação de dados. Desse modo, regularmente, a automatização do processo se torna uma maneira fácil de economizar muito tempo. Além disso, quando se tem um código para salvar documentos em formato CSV a partir de consultas, você pode integrar esse recurso em projetos maiores, como geradores automáticos de relatórios, correspondências e funções de "exportação" para os painéis personalizados criados por você.

Antes de começar

Você aproveitará ao máximo este artigo caso tenha o seguinte:

Informações gerais do programa

O código abordado neste artigo terá as funções a seguir:

  1. Ativar a possibilidade de escolha, no tempo de execução, entre enviar gravações do código para o console ou para um stream de arquivos.
  2. Dado um objeto DataFeed como parâmetro, mostre os dados no formato CSV:
    • Salvar os cabeçalhos das linhas.
    • Mostre linhas de dados, em que cada DataEntry forma uma linha na saída resultante.
    • Executar cada valor por meio de um método de adequação para propiciar uma saída segura no formato CSV.
  3. Criar um método "Adequador" que torna todas as entradas seguras para CSV.
  4. Fornecer uma classe de Java que possa transformar qualquer consulta da API de exportação de dados em um arquivo CSV.

Voltar ao início

Permitir streams de saída configuráveis

O primeiro passo a ser feito é configurar um stream de saída configurável para que sua classe envie impressões. Dessa forma, qualquer código que use sua classe pode decidir se o formato deve ir para uma saída padrão ou direto para um arquivo. Tudo o que você precisa fazer aqui é configurar o método getter/setter para um objeto PrintStream. Esse será o destino de todas as impressões feita pela classe.

private PrintStream printStream = System.out;

public PrintStream getPrintStream() {
  return printStream;
}

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

A configuração do formato de um arquivo também é muito fácil. Só é necessário ter o nome do arquivo para criar um objeto PrintStream para esse arquivo.

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

Voltar ao início

Repetição entre os dados

A primeira linha do arquivo CSV é a linha de nomes de colunas. Cada coluna representa uma dimensão ou métrica do feed de dados. Sendo assim, para salvar essa primeira linha, faça o seguinte.

  1. Capture a primeira entrada do feed.
  2. Itere uma lista de dimensões usando o método getDimensions dessa entrada.
  3. Mostre o nome de cada dimensão usando o método Dimension.getName(), seguido por uma vírgula.
  4. Faça o mesmo para métricas que usam o método getMetrics(). Insira vírgulas após todas as métricas, exceto na última.

Veja aqui uma implementação do método para salvar cabeçalhos de linhas. Este código não retorna uma string representando a linha completa: ele envia gravações para um streade saída ao processar os 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();
  }

A impressão do "corpo" do arquivo CSV (tudo o que estiver abaixo da linha dos nomes de colunas) é muito semelhante. Há apenas duas diferenças principais. Primeira: não se avalia apenas a primeira entrada. O código precisa fazer um loop entre todos as entradas no objeto do feed. Segundo, em vez de usar o método getName() para extrair o valor a ser limpo e impresso, use 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();
  }

Esse código divide seu feed em entradas, e suas entradas em valores a serem impressos no formato final. Mas como tornar esses valores compatíveis com CSV? E o que acontece se um valor no arquivo "com valores separados por vírgula" tiver uma vírgula nele? Esses valores precisam sofrer adequação.

Voltar ao início

Como adequar os dados para serem compatíveis com o formato CSV

O formato CSV é simples. Um arquivo CSV representa uma tabela de dados, e cada linha representa um linha da tabela. Os valores das linhas são separados por vírgulas. Uma nova linha significa uma nova linha de dados.

Infelizmente, esse formato simples faz com que seja fácil a confusão com dados inválidos. E o que acontece se um valor tiver uma vírgula? E o que acontece se um dos valores tiver quebras de linha? O que se deve fazer com um espaço entre as vírgulas e os valores? Todas essas situações podem ser levadas em conta usando algumas regras simples.

  • Se a string tiver um caractere de aspas duplas, ela deverá ser precedida por um segundo caractere de aspas duplas.
  • Se houver uma vírgula na string, coloque a string inteira entre aspas duplas (se ainda não o tiver feito).
  • Se houver uma quebra de linha na string, coloque a string inteira entre aspas duplas (se ainda não o tiver feito).
  • Se a string começar ou terminar com um tipo de espaço em branco, coloque a string inteira entre aspas duplas (se ainda não o tiver feito).

Pode ser um pouco difícil visualizar a aparência dos valores neste momento. Por isso, veja alguns exemplos. Lembre-se de que cada exemplo representa um único valor, e é tratado dessa forma. Para ser mais claro, os espaços serão exibidos como um caractere "_".

Antes Depois
sem alterações sem alterações
aspas duplas " aleatórias aspas duplas "" aleatórias
separado,por,vírgula "separado,por,vírgula"
Duas
linhas
"Duas
linhas"
_espaço inicial, e uma vírgula "_espaço inicial, e uma vírgula"
"aspa inicial, vírgula """aspa inicial, vírgula"
_espaço, vírgula
segundo linha e aspas dupla"
"_espaço, vírgula
segundo linha e aspas dupla"""

A maneira mais fácil de lidar com todas essas condições é criando um método de adequação. Os dados questionáveis entram, e os valores CSV bons e limpos saem. Veja aqui um exemplo de boa implementação desse 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();
}

O método inicia fazendo uma verificação das aspas duplas existentes. Isso deve ser feito antes de todas as outras verificações, pois envolve a colocação de uma string entre aspas duplas, e seria complicado determinar a diferença entre aspas duplas que eram parte do valor e aspas duplas que foram adicionadas previamente por meio deste método. É fácil preceder essas com algum caractere. E elas apenas precisam ser duplicadas. Qualquer " se torna um "", qualquer "" se torna um """", e assim por diante.

Quando a condição for alcançada, todas as outras condições (espaço em branco não cortado, vírgulas e quebras de linha) podem ser verificadas. Se nenhuma dessas condições estiver presente, apenas coloque o valor entre aspas duplas.

A instrução acima usa um objeto StringBuilder, nunca manipula diretamente uma string bruta. Isso ocorre porque o StringBuilder permite que você manipule livremente a string sem fazer cópias temporárias na memória. Como as strings com Java são imutáveis, qualquer aprimoramento mínimo que você fizer criaria uma string totalmente nova. Quando a alteração vai se propagando pela planilha, pode crescer muito rapidamente.

Número de linhas x Valores por linha x Alterações no valor = Total de novas strings criadas
10.000 10 3 300.000

Voltar ao início

O acontece depois?

Agora que você tem as ferramentas necessárias, é hora de usá-las. Veja algumas ideias para começar.

  • Consulte o código fonte do exemplo de aplicativo que usa essa classe para salvar um arquivo CSV com base em um exemplo de consulta. Ele utiliza um nome de arquivo de formato como um parâmetro de linha de comando e salva na saída padrão automaticamente. Use-o como ponto de partida, crie algo impressionante.
  • O CSV é apenas um dos vários formatos amplamente utilizados. Aprimore a classe para usar um formato diferente, como TSV, YAML, JSON ou XML.
  • Crie um aplicativo que gere arquivos CSV e envie-os por e-mail quando terminar. Facilidade para geração automatizada de relatórios mensais.
  • Crie um aplicativo que permita inserir consultas de maneira interativa, criando uma interface poderosa para analisar seus dados.