אלכסנדר לוקאס, צוות Google Analytics API – אוגוסט 2010
מבוא
במאמר הזה נסביר איך לקחת נתונים מכל שאילתה שנשלחה ל-Google Analytics Data Export API, וליצור פלט של התוצאות לפורמט CSV פופולרי. זו אחת מהמשימות הנפוצות ביותר שאנשים מבצעים עם נתוני Analytics שנשלפו מ-Data Export API, ולכן אוטומציה של התהליך היא דרך קלה לחסוך זמן רב על בסיס קבוע. בנוסף, אחרי שיהיה לכם קוד להדפסת מסמכי CSV משאילתות, תוכלו לשלב אותו בפרויקטים גדולים יותר, כמו מחוללי דוחות אוטומטיים, שירותי דואר ופונקציות 'ייצוא' למרכזי בקרה מותאמים אישית שכתבתם.
לפני שמתחילים
תוכלו להפיק את המרב ממאמר זה אם יש לכם:
- ידע בעבודה של Java.
- ידע בעבודה של Google Analytics, כולל הבנה של מאפיינים וערכים.
- גישה לחשבון Google Analytics פעיל עם נתונים אמיתיים.
- היכרות עם המדריך לתחילת העבודה ב-API לייצוא נתונים ב-Java. המאמר הזה מיועד למצבים שבהם אתם יודעים את כל המידע שמפורט במדריך.
- עותק מקומי של קוד המקור המלא, הזמין בכתובת AnalyticsCvsPrinter.java. ניתן למצוא אפליקציה לדוגמה שמשתמשת בקוד הזה בכתובת AnalyticsCsvDemo.java.
סקירה כללית של התוכנית
הקוד שמתואר במאמר הזה מבצע את הפעולות הבאות:
- הפעלת האפשרות לבחור בזמן ריצה אם הקוד יודפס במסוף או בסטרימינג של קובץ.
- בהינתן אובייקט
DataFeed
כפרמטר, מדפיסים את הנתונים בפורמט CSV:- הדפסת כותרות של שורות.
- הדפסה של שורות נתונים, כאשר כל
DataEntry
מהווה שורה אחת בפלט שמתקבל. - הפעילו כל ערך באמצעות שיטת חיטוי לקבלת פלט בטוח ל-CSV.
- כתוב שיטת Sanitizer שהופכת את כל קובצי ה-CSV לבטוחים.
- לתת לכם מחלקת Java שיכולה לקבל כל שאילתה ב-Data Export 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 היא השורה של שמות העמודות. כל עמודה מייצגת מאפיין או מדד מפיד הנתונים, כך שעל מנת להדפיס את השורה הראשונה, צריך לבצע את הפעולות הבאות.
- תופסים את הרשומה הראשונה מהפיד.
- חוזרים על רשימת המאפיינים באמצעות השיטה
getDimensions
של הרשומה הזו. - מדפיסים את השם של כל מאפיין באמצעות השיטה
Dimension.getName()
ואחריו פסיק. - חוזרים על הפעולות האלה לגבי מדדים באמצעות השיטה
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(); }
הדפסת ה-"body" של קובץ ה-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 מייצג טבלת נתונים, וכל שורה מייצגת שורה בטבלה. הערכים בשורה הזו מופרדים בפסיקים. שורה חדשה פירושה שורה חדשה של נתונים.
למרבה הצער, הפורמט הפשוט הזה מאפשר להטעות בקלות את הנתונים הבעייתיים. מה קורה אם יש פסיק בתוך הערך? מה קורה אם באחד מהערכים יש מעברי שורה? מה צריך לקרות כשיש רווחים בין פסיקים וערכים? אפשר להתחשב בכל המצבים האלה באמצעות כמה כללים פשוטים.
- אם המחרוזת מכילה תו של מירכאות כפולות, תו יציאה ממנו באמצעות תו שני במירכאות כפולות.
- אם יש פסיק במחרוזת, עוטפים את כל המחרוזת במירכאות כפולות (אלא אם כבר יש לכם).
- אם יש מעבר שורה במחרוזת, עטוף את כל המחרוזת במירכאות כפולות (אלא אם כבר עשית זאת).
- אם המחרוזת מתחילה או מסתיימת ברווח לבן כלשהו, עוטפים את כל המחרוזת במירכאות כפולות (אלא אם כבר יש לכם).
יכול להיות קצת מסובך להמחיש איך הערכים אמורים להיראות בשלב הזה, אז הנה כמה דוגמאות. חשוב לזכור שכל דוגמה מייצגת ערך יחיד, ולכן המערכת תסמן בתו מילוט (escape) אותה. לשם הבהרה, רווחים יוצגו כתו _.
לפני | אחרי |
---|---|
ללא שינוי | ללא שינוי |
מירכאות כפולות אקראיות | מירכאות כפולות אקראיות |
פסיק,מופרד | "פסיק,מופרד" |
שני שורות |
"שתי שורות" |
_רווח, ופסיק | "_רווח ופסיק" |
"ציטוט מוביל, פסיק | """ציטוט מוביל, פסיק" |
_רווח, פסיק שורה שנייה ומירכאות כפולות" |
"_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 ושולחת אותם בסיום התהליך. דיווח חודשי אוטומטי בקלות!
- כתוב אפליקציה שמאפשרת להזין שאילתות באופן אינטראקטיבי, וכך ליצור ממשק עשיר להתעמקות בנתונים.