خروجی داده از API صادرات داده به فرمت CSV

الکساندر لوکاس، تیم Google Analytics API – آگوست 2010


معرفی

این مقاله به شما نشان می‌دهد که چگونه می‌توانید داده‌ها را از هر درخواستی که به API صادرات داده‌های گوگل آنالیتیکس داده شده است بگیرید و نتایج را به فرمت محبوب CSV خروجی بگیرید. این یکی از متداول‌ترین کارهایی است که افراد با داده‌های Analytics استخراج‌شده از Data Export API انجام می‌دهند، بنابراین خودکار کردن فرآیند یک راه آسان برای صرفه‌جویی در زمان به طور منظم است. علاوه بر این، هنگامی که مقداری کد برای چاپ اسناد CSV از پرس و جوها دارید، می‌توانید این کد را در پروژه‌های بزرگ‌تر، مانند تولیدکننده گزارش خودکار، پست‌کننده‌ها و توابع «صادرات» برای داشبوردهای سفارشی که نوشته‌اید، ادغام کنید.

قبل از اینکه تو شروع کنی

اگر موارد زیر را داشته باشید، از این مقاله بیشترین بهره را خواهید برد:

  • دانش کاری جاوا.
  • دانش کاری Google Analytics، از جمله درک ابعاد و معیارها.
  • دسترسی به یک حساب Google Analytics فعال با داده های واقعی.
  • آشنایی با Data Export API Java Getting Started Guide, . این مقاله فرض می کند که شما از قبل همه چیزهایی که در آن راهنما پوشش داده شده است را می دانید.
  • یک کپی محلی از کد منبع کامل، که می توانید آن را در AnalyticsCvsPrinter.java دریافت کنید. یک برنامه نمونه با استفاده از این کد را می توان در AnalyticsCsvDemo.java یافت.

نمای کلی برنامه

کد پوشش داده شده در این مقاله موارد زیر را انجام می دهد:

  1. انتخاب در زمان اجرا را فعال کنید که آیا کد در کنسول چاپ می شود یا در جریان فایل.
  2. با توجه به یک شی DataFeed به عنوان پارامتر، داده ها را در قالب CSV چاپ کنید:
    • چاپ سرصفحه های ردیف
    • سطرهای داده را چاپ کنید، جایی که هر DataEntry یک ردیف در خروجی حاصل را تشکیل می دهد.
    • هر مقدار را از طریق یک روش ضدعفونی کننده برای خروجی ایمن CSV اجرا کنید.
  3. یک روش «Sanitizer» بنویسید که همه ورودی‌ها را CSV-Safe می‌کند.
  4. یک کلاس جاوا در اختیار شما قرار می دهد که می تواند هر درخواست API صادرات داده را بگیرد و آن را به یک فایل CSV تبدیل کند.

بازگشت به بالا

اجازه دادن به جریان های خروجی قابل تنظیم

اولین کاری که باید انجام دهید این است که یک جریان خروجی قابل تنظیم برای کلاس خود تنظیم کنید تا در آن چاپ شود. به این ترتیب هر کدی که از کلاس شما استفاده می کند می تواند تصمیم بگیرد که خروجی باید به حالت استاندارد برود یا مستقیماً به یک فایل. تنها کاری که در اینجا باید انجام دهید این است که متد getter/setter را برای یک شی PrintStream تنظیم کنید. این هدف تمام چاپ های انجام شده توسط کلاس خواهد بود.

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 یک جدول داده را نشان می دهد و هر خط نشان دهنده یک ردیف در آن جدول است. مقادیر در آن سطر با کاما از هم جدا می شوند. یک خط جدید به معنای یک ردیف جدید از داده ها است.

متأسفانه، این فرمت ساده باعث می‌شود که به طرز فریبنده‌ای به راحتی بتوان چیزها را با داده‌های بد حذف کرد. اگر مقدار شما یک کاما در آن باشد چه؟ اگر یکی از مقادیر شما دارای خطوط شکسته باشد چه؟ با فاصله بین کاما و مقادیر چه اتفاقی باید بیفتد؟ همه این موقعیت ها را می توان با استفاده از چند قانون ساده توضیح داد.

  • اگر رشته حاوی یک کاراکتر doublequote است، با یک کاراکتر دوقلوی دوم از آن فرار کنید.
  • اگر یک کاما در رشته وجود دارد، کل رشته را در دو گیومه قرار دهید (مگر اینکه قبلاً داشته باشید).
  • اگر خط شکسته ای در رشته وجود دارد، کل رشته را در دو گیومه بپیچید (مگر اینکه قبلاً داشته باشید).
  • اگر رشته با هر نوع فضای سفید شروع یا به پایان می رسد، کل رشته را در دو گیومه بپیچید (مگر اینکه قبلاً داشته باشید).

تجسم اینکه ارزش‌های شما در این مرحله چگونه باید باشد، می‌تواند کمی مشکل باشد، بنابراین در اینجا چند نمونه آورده شده است. به یاد داشته باشید، هر مثال یک مقدار واحد را نشان می دهد و به این ترتیب از آن خارج می شود. برای وضوح، فاصله ها به عنوان یک کاراکتر _ نشان داده می شوند.

قبل از بعد از
بدون تغییر بدون تغییر
تصادفی " doublequote دو نقل قول تصادفی ""
جدا شده با ویرگول "جدا شده با ویرگول"
دو
خطوط
"دو
خطوط"
_فضای پیشرو و کاما "_فضای پیشرو و کاما"
"نقل قول اصلی، کاما """نقل قول اصلی، کاما"
_فاصله، کاما
خط دوم و دو نقل قول"
"_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 به شما امکان می دهد آزادانه رشته را بدون ایجاد کپی موقت در حافظه دستکاری کنید. از آنجایی که رشته‌ها در جاوا تغییر ناپذیر هستند، هر ترفند کوچکی که انجام دهید یک رشته کاملاً جدید ایجاد می‌کند. هنگام بررسی داده های صفحه گسترده، این می تواند خیلی سریع جمع شود.

تعداد ردیف ها x مقادیر در هر ردیف x به مقدار تغییر می کند = مجموع رشته های جدید ایجاد شده است
10000 10 3 300000

بازگشت به بالا

بعدش چی؟

اکنون که به شما یک چکش طلایی داده اند، طبیعی است که به شکار میخ بروید. در اینجا چند ایده برای شروع شما وجود دارد.

  • به کد منبع برنامه نمونه نگاهی بیندازید که از این کلاس برای چاپ یک فایل CSV بر اساس یک جستجوی نمونه استفاده می کند. نام فایل خروجی را به عنوان پارامتر خط فرمان می گیرد و به طور پیش فرض به صورت استاندارد چاپ می کند. از آن به عنوان نقطه شروع استفاده کنید، چیزی عالی بسازید!
  • CSV تنها یکی از بسیاری از فرمت های محبوب است. کلاس را برای خروجی به فرمت دیگری مانند TSV، YAML، JSON یا XML تغییر دهید.
  • برنامه ای بنویسید که CSV ها را تولید کند و پس از اتمام آن ها را ایمیل کند. گزارش گیری ماهانه خودکار آسان!
  • برنامه ای بنویسید که به شما امکان می دهد پرس و جوها را به صورت تعاملی وارد کنید، تا یک رابط قدرتمند برای جستجو در داخل داده های خود داشته باشید.