Data Export API에서 CSV 형식으로 데이터 출력

Alexander Lucas, Google 애널리틱스 API팀 – 2010년 8월


소개

이 도움말에서는 Google 애널리틱스 Data Export API에 대한 모든 쿼리에서 데이터를 가져와서 널리 사용되는 CSV 형식으로 출력하는 방법을 설명합니다. 이는 사용자가 Data Export API에서 가져온 애널리틱스 데이터로 수행하는 가장 일반적인 작업 중 하나이므로 프로세스를 자동화하면 정기적으로 많은 시간을 절약할 수 있습니다. 또한 쿼리에서 CSV 문서를 출력하기 위한 코드가 있으면 이를 더 큰 프로젝트(예: 자동 보고서 생성기, 메일러, 작성한 맞춤 대시보드의 '내보내기' 함수)에 통합할 수 있습니다.

시작하기 전에

다음과 같은 경우 이 도움말을 최대한 활용할 수 있습니다.

  • 자바에 관한 실무 지식
  • 측정기준 및 측정항목에 대한 이해를 포함한 Google 애널리틱스에 대한 실무 지식
  • 실제 데이터가 있는 활성 Google 애널리틱스 계정에 대한 액세스 권한
  • Data Export API 자바 시작 가이드에 관한 지식 이 문서에서는 사용자가 해당 가이드에서 다루는 모든 내용을 이미 알고 있다고 가정합니다.
  • 전체 소스 코드의 로컬 사본은 AnalyticsCvsPrinter.java에서 다운로드할 수 있습니다. 이 코드를 사용하는 샘플 애플리케이션은 AnalyticsCsvDemo.java에 있습니다.

프로그램 개요

이 도움말에서 다루는 코드는 다음을 실행합니다.

  1. 코드를 콘솔에 인쇄할지 또는 파일 스트림에 인쇄할지 런타임 시 선택할 수 있습니다.
  2. DataFeed 객체를 매개변수로 지정하여 데이터를 CSV 형식으로 출력합니다.
    • 행 헤더를 인쇄합니다.
    • 데이터 행을 인쇄합니다. 여기서 각 DataEntry는 결과 출력의 한 행을 구성합니다.
    • CSV에서 안전한 출력을 위해 제거 방법을 통해 각 값을 실행합니다.
  3. 모든 입력을 CSV로 안전하게 만드는 'Sanitizer' 메서드를 작성합니다.
  4. 모든 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 파일의 '본문'(열 이름 행 아래의 모든 항목) 출력은 매우 유사합니다. 두 가지 주요 차이점만 있습니다. 첫째, 평가되는 첫 번째 항목만이 아닙니다. 코드는 피드 객체의 모든 항목을 반복해야 합니다. 둘째, 정리 및 출력할 값을 가져오는 데 getName() 메서드를 사용하는 대신 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();
  }

이 코드는 피드를 항목으로 분할하고 항목을 출력할 값으로 나눕니다. 그렇다면 이러한 가치를 CSV 형식으로 만들려면 어떻게 해야 할까요? '쉼표로 구분된 값' 파일의 값에 쉼표가 있으면 어떻게 될까요? 이러한 값은 정리해야 합니다.

맨 위로

CSV 호환성을 위해 데이터를 정리하는 방법

CSV는 간단한 형식입니다. CSV 파일은 데이터 테이블을 나타내고 각 줄은 해당 테이블의 행을 나타냅니다. 해당 행의 값은 쉼표로 구분됩니다. 새 줄은 새 데이터 행을 의미합니다.

안타깝게도 이 간단한 형식을 사용하면 잘못된 데이터로 인해 잘못된 결과가 발생할 수 있습니다. 값에 쉼표가 있으면 어떻게 될까요? 값 중 하나에 줄바꿈이 있으면 어떻게 해야 할까요? 쉼표와 값 사이의 공백은 어떻게 해야 할까요? 이러한 모든 상황은 몇 가지 간단한 규칙을 사용하여 설명할 수 있습니다.

  • 문자열에 큰따옴표 문자가 포함된 경우 두 번째 큰따옴표 문자로 이스케이프 처리합니다.
  • 문자열에 쉼표가 있으면 전체 문자열을 큰따옴표로 묶습니다 (이미 쉼표가 있는 경우는 제외).
  • 문자열에 줄바꿈이 있으면 전체 문자열을 큰따옴표로 묶습니다 (이미 줄바꿈이 있는 경우는 제외).
  • 문자열이 어떤 종류의 공백으로 시작하거나 끝나면 (이미 가지고 있지 않은 한) 전체 문자열을 큰따옴표로 묶습니다.

이 시점에서 값이 어떻게 표시되어야 하는지 시각화하기가 약간 까다로울 수 있으므로 다음과 같은 몇 가지 예를 소개합니다. 각 예는 단일 값을 나타내며 그처럼 이스케이프 처리됩니다. 명확성을 위해 공백은 _ 문자로 표시됩니다.

변경 전 변경 후
변경 안됨 변경 안됨
임의의 " 큰따옴표 임의의 "" 큰따옴표
쉼표로 구분 '쉼표,분리'

2개
'두
줄'
_앞 공백과 쉼표 '_앞 공백 및 쉼표'
"선행 따옴표, 쉼표 """선행 따옴표, 쉼표"
_공백, 쉼표
두 번째 줄, 큰따옴표"
"_공백, 쉼표
두 번째 줄, 큰따옴표"""

이러한 모든 조건을 처리하는 가장 쉬운 방법은 정리 메서드를 작성하는 것입니다. 문제의 소지가 있는 데이터가 들어오면 적절하고 깔끔한 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를 사용하면 메모리에 임시 복사본을 만들지 않고도 문자열을 자유롭게 조작할 수 있기 때문입니다. 자바의 문자열은 변경할 수 없기 때문에 미세하게 조정할 때마다 새로운 문자열이 생성됩니다. 스프레드시트 데이터를 처리하면 매우 빠르게 증가할 수 있습니다.

행 수 x 행당 값 x 값의 변화 = Total New Strings Created(총 새 문자열 생성 수)
10,000 10 3 300,000

맨 위로

다음 단계

금색 망치를 받았으니 발톱을 사냥하는 것은 자연스러운 일입니다. 다음은 시작하는 데 도움이 될 몇 가지 아이디어입니다.

  • 이 클래스를 사용하여 샘플 쿼리를 기반으로 CSV 파일을 출력하는 샘플 애플리케이션 소스 코드를 살펴보세요. 출력 파일 이름을 명령줄 매개변수로 사용하고 기본적으로 표준으로 출력합니다. 이를 시작점으로 삼아 멋진 작품을 만들어 보세요!
  • CSV는 많이 사용되는 형식 중 하나일 뿐입니다. 클래스를 조정하여 TSV, YAML, JSON 또는 XML과 같은 다른 형식으로 출력합니다.
  • CSV를 생성하고 완료되면 이를 이메일로 전송하는 애플리케이션을 작성합니다. 간편한 월별 자동 보고 기능
  • 대화형으로 쿼리를 입력할 수 있는 애플리케이션을 작성하여 데이터 내부를 살펴볼 수 있는 강력한 인터페이스를 만듭니다.