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

Google Analytics API 團隊 Alexander Lucas - 2010 年 8 月


簡介

本文說明如何從對 Google Analytics (分析) Data Export API 執行的「任何查詢」擷取資料,並將結果輸出為常用 CSV 格式。這是使用者在使用資料匯出 API 中提取的 Analytics (分析) 資料時,最常執行的工作之一。因此,將程序自動化可輕鬆定期省下大量時間。此外,取得從查詢輸出 CSV 文件的程式碼後,您就能將這個程式碼整合至大型專案中,例如為您編寫的自訂資訊主頁自動產生報表產生器、郵件程式和「匯出」函式。

事前準備

如果符合以下情況,本文就能發揮最大效用:

計畫總覽

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

  1. 啟用在執行階段選擇是否將程式碼列印至主控台或檔案串流。
  2. DataFeed 物件做為參數,以 CSV 格式列印資料:
    • 列印列標題。
    • 列印資料列,其中每個 DataEntry 都會在產生的輸出結果中顯示一個資料列。
    • 透過清理方法執行每個值,確保輸出可安全使用 CSV。
  3. 請編寫「Sanitizer」方法,讓所有輸入資料都能使用 CSV 格式。
  4. 請提供 Java 類別,可擷取任何 Data Export API 查詢並轉換為 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 檔案代表一個資料表,每一行代表該資料表中的一個資料列。該列中的值會以半形逗號分隔。新的一行代表新的資料列。

遺憾的是,這種直接的格式容易讓人很容易處理無效資料。如果值中含有半形逗號,該怎麼辦?如果其中一個值包含換行符號,該怎麼辦?逗號和值之間如果有空格,應如何產生?只要透過幾個簡單的規則,即可涵蓋所有這些情況。

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

要以視覺化方式呈現目前的值可能有些許困難,以下提供幾個範例。請注意,每個範例都代表一個單一值,因此會逸出。為求明確,空格會顯示為 _ 字元。

完成前 完成後
未變更 未變更
隨機的「雙引號」 隨機的「」雙引號
以半形逗號分隔 "半形逗號,分隔"
兩行
「兩行
_前置空格,以及半形逗號 「_前置空格和半形逗號」
"前置引號, 逗號 """前置引號, 逗號"
_space, 半形逗號
第二行, 雙引號"
"_space, 半形逗號
第二行, 雙引號"""

處理這些條件最簡單的方法,就是編寫清理方法。系統會產生可供查詢的資料,也會產生完整且乾淨的 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,並在完成後立即寄出。輕鬆自動執行每月報表!
  • 編寫應用程式以互動輸入查詢,打造功能強大的介面,方便您在資料中瀏覽資料。