Xuất dữ liệu từ API xuất dữ liệu sang định dạng CSV

Alexander Lucas, Nhóm Google Analytics API – Tháng 8 năm 2010


Giới thiệu

Bài viết này hướng dẫn bạn cách lấy dữ liệu từ bất kỳ truy vấn nào được gửi đến API Xuất dữ liệu của Google Analytics và xuất kết quả sang định dạng CSV phổ biến. Đây là một trong những công việc phổ biến nhất mà mọi người thực hiện khi dữ liệu Analytics được lấy từ API Xuất dữ liệu. Vì vậy, việc tự động hoá quy trình này là một cách dễ dàng để thường xuyên tiết kiệm rất nhiều thời gian. Ngoài ra, sau khi có một số mã để in tài liệu CSV từ truy vấn, bạn sẽ có thể tích hợp tài liệu này vào các dự án lớn hơn, như trình tạo báo cáo tự động, trình gửi thư và chức năng "xuất" cho trang tổng quan tuỳ chỉnh bạn đã viết.

Trước khi bắt đầu

Bạn sẽ khai thác tối đa bài viết này nếu có:

Tổng quan về chương trình

Mã được đề cập trong bài viết này sẽ thực hiện những việc sau:

  1. Cho phép chọn trong thời gian chạy để in mã vào bảng điều khiển hay vào luồng tệp.
  2. Với đối tượng DataFeed dưới dạng tham số, hãy in dữ liệu ra ở định dạng CSV:
    • In tiêu đề hàng.
    • In các hàng dữ liệu, trong đó mỗi DataEntry tạo thành một hàng trong dữ liệu đầu ra.
    • Chạy từng giá trị thông qua phương pháp dọn dẹp để có đầu ra an toàn cho tệp CSV.
  3. Viết phương thức "Sanitizer" để đảm bảo tất cả tệp CSV đầu vào đều an toàn.
  4. Cung cấp cho bạn một lớp Java có thể nhận bất kỳ truy vấn API Xuất dữ liệu nào và chuyển thành tệp CSV.

Trở lại đầu trang

Cho phép các luồng đầu ra có thể định cấu hình

Điều đầu tiên cần làm là thiết lập một luồng đầu ra có thể định cấu hình để lớp của bạn có thể in. Bằng cách này, bất kỳ mã nào sử dụng lớp đều có thể quyết định xem dữ liệu đầu ra nên chuyển sang dạng chuẩn hay chuyển trực tiếp vào một tệp. Bạn chỉ cần thiết lập phương thức getter/setter cho đối tượng PrintStream. Đó sẽ là mục tiêu của tất cả hoạt động in mà lớp thực hiện.

private PrintStream printStream = System.out;

public PrintStream getPrintStream() {
  return printStream;
}

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

Việc đặt đầu ra thành một tệp cũng rất dễ dàng. Người dùng chỉ cần tên tệp để tạo đối tượng PrintStream cho tệp đó.

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

Trở lại đầu trang

Lặp lại theo dữ liệu

Hàng đầu tiên của tệp CSV là hàng chứa các tên cột. Mỗi cột đại diện cho một phương diện hoặc chỉ số từ nguồn cấp dữ liệu. Vì vậy, để in hàng đầu tiên này, hãy làm như sau.

  1. Chọn mục nhập đầu tiên từ nguồn cấp dữ liệu.
  2. Lặp lại thông qua danh sách phương diện bằng phương thức getDimensions của mục nhập đó.
  3. In tên từng phương diện bằng cách sử dụng phương thức Dimension.getName(), theo sau là dấu phẩy.
  4. Làm tương tự đối với các chỉ số bằng phương thức getMetrics(). In dấu phẩy sau tất cả trừ chỉ số cuối cùng.

Dưới đây là một cách triển khai phương thức in tiêu đề hàng. Lưu ý rằng mã này không trả về một chuỗi đại diện cho một hàng hoàn chỉnh: mã này in ra một luồng đầu ra khi xử lý các giá trị.

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

Việc in "phần nội dung" của tệp CSV (mọi nội dung bên dưới hàng tên cột) sẽ rất giống nhau. Chỉ có hai điểm khác biệt chính. Thứ nhất, đây không chỉ là mục nhập đầu tiên được đánh giá. Mã cần lặp lại thông qua tất cả các mục nhập trong đối tượng nguồn cấp dữ liệu. Thứ hai, thay vì sử dụng phương thức getName() để lấy giá trị cần dọn dẹp và in, hãy sử dụng 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();
  }

Mã này chia nguồn cấp dữ liệu của bạn thành các mục nhập và các mục nhập của bạn thành các giá trị cần in ra. Nhưng làm cách nào để làm cho những giá trị đó phù hợp với tệp CSV? Nếu một giá trị trong tệp "giá trị được phân tách bằng dấu phẩy" có chứa dấu phẩy thì sao? Bạn phải dọn dẹp các giá trị đó.

Trở lại đầu trang

Cách dọn dẹp dữ liệu để đảm bảo khả năng tương thích với tệp CSV

CSV là một định dạng đơn giản. Tệp CSV đại diện cho một bảng dữ liệu và mỗi dòng đại diện cho một hàng trong bảng đó. Các giá trị trong hàng đó được phân tách bằng dấu phẩy. Một dòng mới nghĩa là một hàng dữ liệu mới.

Thật không may, định dạng đơn giản này khiến dữ liệu không hợp lệ có thể bị bỏ qua dễ dàng. Nếu giá trị của bạn chứa dấu phẩy thì sao? Nếu một trong các giá trị của bạn có ngắt dòng thì sao? Điều gì sẽ xảy ra với khoảng cách giữa dấu phẩy và giá trị? Tất cả các tình huống này có thể được giải thích bằng cách sử dụng một vài quy tắc đơn giản.

  • Nếu chuỗi chứa một ký tự dấu ngoặc kép, hãy thoát chuỗi đó bằng một ký tự dấu ngoặc kép thứ hai.
  • Nếu có dấu phẩy trong chuỗi, hãy gói toàn bộ chuỗi trong dấu ngoặc kép (trừ phi bạn đã có).
  • Nếu có dấu ngắt dòng trong chuỗi, hãy gói toàn bộ chuỗi trong dấu ngoặc kép (trừ phi bạn đã có).
  • Nếu chuỗi bắt đầu hoặc kết thúc bằng bất kỳ khoảng trắng nào, hãy gói toàn bộ chuỗi trong dấu ngoặc kép (trừ phi bạn đã có).

Tại thời điểm này, có thể bạn sẽ thấy hơi phức tạp để hình dung các giá trị của mình. Vì vậy, dưới đây là một số ví dụ. Hãy nhớ rằng mỗi ví dụ đại diện cho một giá trị duy nhất và được thoát như vậy. Để cho rõ ràng, dấu cách sẽ được hiển thị dưới dạng ký tự _.

Trước Sau
không thay đổi không thay đổi
dấu ngoặc kép ngẫu nhiên " dấu ngoặc kép "" ngẫu nhiên
dấu phẩy,phân tách "dấu phẩy,phân tách"
Hai
dòng
"Hai
dòng"
_dấu cách ở đầu và một dấu phẩy "_headline và dấu phẩy"
"dấu ngoặc kép ở đầu, dấu phẩy """dấu ngoặc kép ở đầu, dấu phẩy"
_space, dấu phẩy
dòng thứ hai và dấu ngoặc kép"
"_space, dấu phẩy
dòng thứ hai và dấu ngoặc kép"""

Cách dễ nhất để xử lý tất cả các điều kiện này là viết phương thức dọn dẹp. Dữ liệu có vấn đề sẽ xuất hiện, sau đó xuất hiện các giá trị CSV tốt, rõ ràng. Dưới đây là một cách triển khai mẫu hay cho phương thức như vậy.

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

Phương thức này bắt đầu bằng cách kiểm tra xem có dấu ngoặc kép hiện tại hay không. Bạn nên thực hiện việc này trước tất cả các bước kiểm tra khác, vì các bước này liên quan đến việc gói một chuỗi có dấu ngoặc kép. Đồng thời, bạn nên xác định sự khác biệt giữa dấu ngoặc kép là một phần của giá trị và dấu ngoặc kép được thêm trước đó bằng phương thức này. Chúng rất dễ thoát – chỉ cần được nhân lên hai lần. Mỗi " trở thành "", mọi "" sẽ trở thành """", v.v.

Sau khi đáp ứng điều kiện đó, bạn có thể kiểm tra tất cả các điều kiện khác (khoảng trắng không giới hạn, dấu phẩy và dấu ngắt dòng). Nếu có bất kỳ giá trị nào trong số đó, bạn chỉ cần đặt giá trị đó trong dấu ngoặc kép.

Lưu ý rằng thuộc tính trên sử dụng đối tượng StringBuilder, không bao giờ trực tiếp thao tác với chuỗi thô. Lý do là StringBuilder cho phép bạn tuỳ ý thao tác trên chuỗi mà không cần tạo các bản sao tạm thời trong bộ nhớ. Vì các chuỗi trong Java là bất biến, nên mỗi lần chỉnh sửa nhỏ bạn thực hiện sẽ tạo ra một chuỗi hoàn toàn mới. Khi tải dữ liệu bảng tính, quá trình này có thể tăng lên rất nhanh.

Số lượng hàng x giá trị trên mỗi hàng x Thay đổi về giá trị = Tổng số chuỗi mới đã tạo
10.000 10 3 300.000

Trở lại đầu trang

Tiếp theo là gì?

Giờ bạn đã được tặng một chiếc búa vàng, việc đi săn chém đinh là bình thường. Dưới đây là một số ý tưởng để giúp bạn bắt đầu.

  • Hãy xem mã nguồn ứng dụng mẫu sử dụng lớp này để in tệp CSV dựa trên truy vấn mẫu. Phương thức này lấy tên tệp đầu ra làm tham số dòng lệnh và in ra để chuẩn hoá theo mặc định. Hãy sử dụng cơ hội này làm điểm bắt đầu, tạo ra một cái gì đó tuyệt vời!
  • CSV chỉ là một trong nhiều định dạng phổ biến. Chỉnh sửa lớp để xuất sang một định dạng khác, chẳng hạn như TSV, YAML, JSON hoặc XML.
  • Viết một ứng dụng tạo tệp CSV và gửi qua thư khi hoàn tất. Báo cáo tự động hằng tháng thật dễ dàng!
  • Viết một ứng dụng cho phép bạn nhập các truy vấn theo cách tương tác, để có một giao diện mạnh mẽ để tìm hiểu sâu hơn về bên trong dữ liệu của bạn.