從資料匯出 API 將資料輸出成 CSV 格式

Google Analytics (分析) API 團隊 Alexander Lucas - 2010 年 8 月


簡介

本文說明如何從對 Google Analytics (分析) 資料匯出 API 提出的任何查詢擷取資料,並將結果輸出為熱門的 CSV 格式。這是使用者最常利用資料匯出 API 提取的 Analytics (分析) 資料執行的工作之一,因此自動化處理過程是定期節省大量時間的簡便方法。此外,一旦有用來列印查詢 CSV 文件的程式碼後,就能將這項資訊整合至大型專案,例如,針對您編寫的自訂資訊主頁,使用自動報表產生器、郵件工具和「匯出」函式。

事前準備

如果您有下列需求,即可充分運用這篇文章:

計畫總覽

本文涵蓋的程式碼將執行下列操作:

  1. 在執行階段選擇要將程式碼輸出至主控台或檔案串流。
  2. DataFeed 物件做為參數,以 CSV 格式輸出資料:
    • 列印列標題。
    • 列印資料列,每個 DataEntry 都構成輸出結果的一列。
    • 透過經過清理的方法執行每個值,確保輸出安全無虞。
  3. 編寫「Sanitizer」方法,讓所有輸入資料安全無虞。
  4. 提供可接收任何資料匯出 API 查詢的 Java 類別,並將其轉換為 CSV 檔案。

返回開頭

允許可設定的輸出串流

首先,你需要設定類別的列印輸出串流。如此一來,使用類別的任何程式碼都能判斷輸出內容應進入標準輸出模式,或直接輸出至檔案。你只需要為 PrintStream 物件設定 getter/setter 方法即可。這將成為該類別完成所有列印的目標。

private PrintStream printStream = System.out;

public PrintStream getPrintStream() {
  return printStream;
}

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

將輸出設為檔案也非常簡單。只需檔案名稱,就能為該檔案建立 PrintStream 物件。

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

返回開頭

透過資料疊代

CSV 檔案的第一列是資料欄名稱列。每個資料欄分別代表資料動態饋給中的維度或指標,因此如要列印這個第一列,請按照下列步驟操作。

  1. 從動態饋給中取得第一個項目。
  2. 使用該項目的 getDimensions 方法,透過維度清單疊代。
  3. 使用 Dimension.getName() 方法並加上半形逗號,即可輸出每個維度的名稱。
  4. 使用 getMetrics() 方法執行相同操作。請列印最後一個指標以外的半形逗號。

以下提供一種列印列標題的方法實作。請注意,這個程式碼不會傳回代表完整資料列的字串:會在處理值時列印至輸出串流。

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

列印 CSV 檔案的「內文」(位於資料欄名稱列下方的所有內容) 非常類似。請注意,有兩個主要差異。第一,要評估的不是第一個項目程式碼需要循環執行動態饋給物件中的所有項目。接著,請改用 getValue(),而不要使用 getName() 方法提取要清理及列印的值。

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

這個程式碼會將動態饋給分成多個項目,然後將項目轉換為要輸出的值。但我們要怎麼讓這些值符合 CSV 格式?如果「逗號分隔值」檔案中的值有半形逗號,該怎麼辦?這些值必須經過處理。

返回開頭

如何清理資料,確保資料與 CSV 的相容性

CSV 格式很簡單,CSV 檔案代表一個資料表,每一行都代表該資料表的一個資料列。該列中的值會以半形逗號分隔。換行表示新的資料列。

但這種簡單明瞭的格式 很容易在資料無效時不小心丟失如果你的值中含有半形逗號,該怎麼辦?如果其中一個值有換行符號,該怎麼辦?逗號和價值之間應有什麼空格?這些情況可以使用幾個簡單的規則。

  • 如果字串含有雙引號字元,請使用第二個雙引號字元逸出。
  • 如果字串中有半形逗號,請以雙引號括住整個字串 (除非您已含有)。
  • 如果字串中有換行符號,請以雙引號括住整個字串 (除非您已經取得)。
  • 如果字串的開頭或結尾是任何空白的空格,請使用雙引號括住整個字串 (除非您已取得)。

您目前以視覺化方式呈現值看起來可能有些困難,請參考以下範例。請記住,每個範例都代表單一值,且會像這樣逸出。為求明確,空格會顯示為 _ 字元。

使用前 使用後
未變更 未變更
隨機 " 雙引號 隨機 "" 雙引號
逗號分隔 「逗號分隔」

「兩
行」
_前置字元和半形逗號 "_前置字元和半形逗號"
"前置引號, 逗號 """前置引號, 逗號"
_空格、半形逗號
第二行、雙引號"
"_空格,逗號
第二行,雙引號"""

如要處理上述所有條件,最簡單的方法就是編寫清理方法。使用有良好、清爽且乾淨的 CSV 值提供給有問題的資料。以下是這類方法的良好實作範例。

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

此方法一開始會檢查現有的雙引號。這項作業應在所有其他檢查之前完成,因為它們牽涉到雙引號包裝字串,而要判斷屬於值中的雙引號與此方法先前新增的雙引號之間,會很麻煩。這些很容易逃離,只要加倍每個 " 都會變成 "",每個 " 都會變成 """,依此類推。

符合該條件後,就可以檢查其他所有條件 (無邊框的空白字元、逗號和換行符號)。如果有上述任一種參數,只要將該值置於雙引號中即可。

請注意,上述情況會使用 StringBuilder 物件,絕對不能直接操控原始字串。這是因為 StringBuilder 可讓您自由操控字串,無須在記憶體中製作臨時副本。由於 Java 中的字串無法變更,因此每次微幅調整都會建立一個全新的字串。瀏覽試算表資料時,資料可能快速增加。

列數 x 每列的值 x 值的變化 = 新建字串總數
10,000 10 3 300,000

返回開頭

接下來呢?

既然您已經贏得了金鎚,自然是純粹想獵蝸牛的方法。以下建議可協助你踏出第一步。

  • 查看範例應用程式原始碼,使用此類別列印範例查詢的 CSV 檔案。這會將輸出檔案名稱做為指令列參數,並根據預設輸出至標準輸出。把它做為起點,製作精彩的內容!
  • CSV 只是眾多熱門格式之一。調整類別,使其輸出為其他格式,例如 TSV、YAML、JSON 或 XML。
  • 編寫可產生 CSV 檔案的應用程式,並在完成後寄出。輕鬆自動產生每月報表!
  • 編寫應用程式,讓您以互動方式輸入查詢,打造出功能強大的介面,方便使用者瀏覽資料。