העברת תמונות לארכיון, ניתוח ויצירת דוחות Google Workspace & Google Cloud

1. סקירה כללית

ב-Codelab הזה נציג תרחיש אפשרי של תהליך עבודה בארגון: העברה לארכיון, ניתוח ויצירת דוחות של תמונות. תארו לעצמכם שיש בארגון שלכם סדרה של תמונות שתופסות מקום במשאב מוגבל. אתם רוצים להעביר את הנתונים האלה לארכיון, לנתח את התמונות, ובעיקר ליצור דוח שמסכם את המיקומים בארכיון ואת תוצאות הניתוח, כשהם מאוגדים ומוכנים לעיון של ההנהלה. ‫Google Cloud מספקת את הכלים שמאפשרים לעשות את זה, באמצעות ממשקי API משתי סדרות המוצרים שלה: Google Workspace (לשעבר G Suite או Google Apps) ו-Google Cloud (לשעבר GCP).

בתרחיש שלנו, למשתמש העסקי יהיו תמונות ב-Google Drive. כדאי לגבות אותם לאחסון "קר" וזול יותר, כמו סוגי האחסון שזמינים ב-Google Cloud Storage. ‫Google Cloud Vision מאפשר למפתחים לשלב בקלות באפליקציות שלהם פיצ'רים לזיהוי באמצעות ראייה ממוחשבת, כולל זיהוי אובייקטים וציוני דרך, זיהוי תווים אופטי (OCR) ועוד. לבסוף, גיליון אלקטרוני של Google Sheets הוא כלי שימושי להמחשה של כל הנתונים האלה עבור הבוס שלכם.

אחרי שתשלימו את ה-codelab הזה כדי לבנות פתרון שממנף את כל היכולות של Google Cloud, אנחנו מקווים שתקבלו השראה לבנות משהו שישפיע אפילו יותר על הארגון שלכם או על הלקוחות שלכם.

מה תלמדו

  • איך משתמשים ב-Cloud Shell?
  • איך מאמתים בקשות API
  • איך מתקינים את ספריית הלקוח של Google APIs ל-Python
  • איך מפעילים ממשקי API של Google
  • איך מורידים קבצים מ-Google Drive
  • איך מעלים אובייקטים או בלובים ל-Cloud Storage
  • איך מנתחים נתונים באמצעות Cloud Vision
  • איך כותבים שורות ב-Google Sheets

הדרישות

  • חשבון Google (יכול להיות שיהיה צורך באישור אדמין לחשבונות Google Workspace)
  • פרויקט ב-Google Cloud עם חשבון פעיל לחיוב ב-Google Cloud
  • היכרות עם פקודות מסוף/מעטפת של מערכת ההפעלה
  • מיומנויות בסיסיות ב-Python (גרסה 2 או 3), אבל אפשר להשתמש בכל שפה נתמכת

ניסיון עם ארבעת מוצרי Google Cloud שצוינו למעלה יכול לעזור, אבל הוא לא חובה. אם יש לכם זמן, מומלץ לעבור על כל אחד מהם בנפרד לפני שמתחילים בתרגיל הזה:

סקר

איך תשתמשו במדריך הזה?

רק לקרוא לקרוא ולבצע את התרגילים

איך היית מדרג את חוויית השימוש שלך ב-Python?

מתחילים בינוני מומחה

איזה דירוג מתאים לדעתך לחוויית השימוש שלך בשירותי Google Cloud?

מתחילים בינוני מומחה

מה הדירוג שלך לחוויית השימוש בשירותי הפיתוח של Google Workspace?

מתחילים בינוני מומחה

האם היית רוצה לראות יותר codelabs שמתמקדים בעסקים לעומת codelabs שמציגים תכונות של מוצרים?

כן לא עוד משניהם

2. הגדרה ודרישות

הגדרת סביבה בקצב אישי

  1. נכנסים ל-מסוף Google Cloud ויוצרים פרויקט חדש או משתמשים בפרויקט קיים. אם עדיין אין לכם חשבון Gmail או Google Workspace, אתם צריכים ליצור חשבון.

b35bf95b8bf3d5d8.pnga99b7ace416376c4.pngbd84a6d3004737c5.png

  • שם הפרויקט הוא השם המוצג למשתתפים בפרויקט. זו מחרוזת תווים שלא נמצאת בשימוש ב-Google APIs. אפשר לעדכן את המיקום הזה בכל שלב.
  • מזהה הפרויקט חייב להיות ייחודי לכל הפרויקטים ב-Google Cloud, והוא קבוע (אי אפשר לשנות אותו אחרי שהוא מוגדר). מסוף Cloud יוצר באופן אוטומטי מחרוזת ייחודית, ובדרך כלל לא צריך לדעת מה היא. ברוב ה-Codelabs, תצטרכו להפנות למזהה הפרויקט (בדרך כלל הוא מזוהה כ-PROJECT_ID). אם אתם לא אוהבים את המזהה שנוצר, אתם יכולים ליצור מזהה אקראי אחר. אפשר גם לנסות שם משתמש משלכם ולבדוק אם הוא זמין. אי אפשר לשנות את ההגדרה הזו אחרי השלב הזה, והיא תישאר כזו למשך הפרויקט.
  • לידיעתכם, יש ערך שלישי, מספר פרויקט, שחלק מממשקי ה-API משתמשים בו. מידע נוסף על שלושת הערכים האלה מופיע במאמרי העזרה.
  1. בשלב הבא, תצטרכו להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבי Cloud או בממשקי API של Cloud. העלות של התרגול הזה לא אמורה להיות גבוהה, ואולי לא תהיה עלות בכלל. כדי להשבית את המשאבים ולא לחייב אתכם אחרי שתסיימו את המדריך הזה, תוכלו למחוק את המשאבים שיצרתם או למחוק את כל הפרויקט. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.

הפעלת Cloud Shell

סיכום

אפשר לפתח קוד באופן מקומי במחשב הנייד, אבל מטרה משנית של ה-Codelab הזה היא ללמד אתכם איך להשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת בענן דרך דפדפן אינטרנט מודרני.

הפעלת Cloud Shell

  1. ב-Cloud Console, לוחצים על Activate Cloud Shell 853e55310c205094.png.

55efc1aaa7a4d3ad.png

אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים (מתחת לקו הקיפול) עם תיאור של הכלי. במקרה כזה, לוחצים על המשך (והמסך הזה לא יוצג לכם יותר). כך נראה המסך החד-פעמי:

9c92662c6a846a5c.png

יחלפו כמה רגעים עד שההקצאה והחיבור ל-Cloud Shell יושלמו.

9f0e51b578fecce5.png

המכונה הווירטואלית הזו כוללת את כל הכלים שדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות של הרשת. אפשר לבצע את רוב העבודה ב-codelab הזה, אם לא את כולה, באמצעות דפדפן או Chromebook.

אחרי שמתחברים ל-Cloud Shell, אפשר לראות שהאימות כבר בוצע והפרויקט כבר הוגדר לפי מזהה הפרויקט.

  1. מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שאתם מאומתים:
gcloud auth list

פלט הפקודה

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שפקודת gcloud מכירה את הפרויקט:
gcloud config list project

פלט הפקודה

[core]
project = <PROJECT_ID>

אם לא, אפשר להגדיר אותו באמצעות הפקודה הזו:

gcloud config set project <PROJECT_ID>

פלט הפקודה

Updated property [core/project].

3. אישור סביבת Python

ב-Codelab הזה נשתמש בשפת Python (אבל ספריות הלקוח של Google APIs תומכות בשפות רבות, אז אתם יכולים ליצור משהו מקביל בכלי הפיתוח המועדף עליכם ולהשתמש ב-Python כפסאודו-קוד). בפרט, שיעור ה-Codelab הזה תומך ב-Python 2 וב-Python 3, אבל מומלץ לעבור ל-3.x בהקדם האפשרי.

Cloud Shell הוא כלי נוח שזמין למשתמשים ישירות מ-Cloud Console, ולא נדרשת סביבת פיתוח מקומית, כך שאפשר לבצע את ההדרכה הזו באופן מלא בענן באמצעות דפדפן אינטרנט. במקרה הספציפי של שיעור ה-Codelab הזה, שתי הגרסאות של Python כבר מותקנות מראש ב-Cloud Shell.

ב-Cloud Shell מותקן גם IPython: זהו מתורגמן אינטראקטיבי של Python ברמה גבוהה יותר, ואנחנו ממליצים להשתמש בו, במיוחד אם אתם חלק מקהילת מדע הנתונים או למידת המכונה. אם כן, IPython הוא המפרש שמוגדר כברירת מחדל עבור Jupyter Notebooks וגם עבור Colab, ‏ Jupyter Notebooks שמתארחים ב-צוות המחקר של Google.

‫IPython מעדיף תחילה מפרש של Python 3, אבל אם גרסה 3.x לא זמינה, הוא חוזר ל-Python 2. אפשר לגשת ל-IPython מ-Cloud Shell, אבל אפשר גם להתקין אותו בסביבת פיתוח מקומית. יוצאים באמצעות ‎ ^D (Ctrl-d)‎ ומאשרים את ההצעה לצאת. פלט לדוגמה של הפקודה ipython ייראה כך:

$ ipython
Python 3.7.3 (default, Mar  4 2020, 23:11:43)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]:

אם אתם לא מעדיפים את IPython, אפשר להשתמש במתורגמן אינטראקטיבי רגיל של Python (ב-Cloud Shell או בסביבת הפיתוח המקומית). גם במקרה הזה, יוצאים באמצעות ‎ ^D:

$ python
Python 2.7.13 (default, Sep 26 2018, 18:42:22)
[GCC 6.3.0 20170516] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 
$ python3
Python 3.7.3 (default, Mar 10 2020, 02:33:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

בנוסף, מניחים ב-codelab שמותקן אצלכם pip (מנהל חבילות Python ופתרון יחסי תלות). הוא מגיע בחבילה עם גרסאות 2.7.9 ומעלה או 3.4 ומעלה. אם יש לכם גרסה ישנה יותר של Python, תוכלו לעיין במדריך הזה לקבלת הוראות התקנה. יכול להיות שתצטרכו הרשאות sudo או הרשאות סופר-משתמש, אבל בדרך כלל זה לא המצב. אפשר גם להשתמש במפורש ב-pip2 או ב-pip3 כדי להריץ את pip לגרסאות ספציפיות של Python.

בהמשך שיעור ה-Codelab הזה נניח שאתם משתמשים ב-Python 3.אם יש הבדלים משמעותיים בין Python 3 ל-Python 2, נספק הוראות ספציפיות ל-Python 2.

[אופציונלי] יצירה ושימוש בסביבות וירטואליות

הקטע הזה הוא אופציונלי ונדרש רק למי שחייב להשתמש בסביבה וירטואלית בשביל שיעור ה-Codelab הזה (כפי שמופיע באזהרה בסרגל הצד שלמעלה). אם במחשב שלכם מותקנת רק גרסת Python 3, אתם יכולים פשוט להריץ את הפקודה הבאה כדי ליצור סביבת Virtualenv בשם my_env (אפשר לבחור שם אחר אם רוצים):

virtualenv my_env

עם זאת, אם במחשב שלכם מותקנות גם Python 2 וגם Python 3, מומלץ להתקין סביבה וירטואלית של Python 3. אפשר לעשות זאת באמצעות -p flag כך:

virtualenv -p python3 my_env

נכנסים לסביבת ה-virtualenv החדשה שיצרתם על ידי 'הפעלה' שלה באופן הבא:

source my_env/bin/activate

כדי לוודא שאתם בסביבה, בודקים אם שם הסביבה מופיע לפני שורת הפקודה של המעטפת, כלומר:

(my_env) $ 

עכשיו תוכלו pip install את כל החבילות הנדרשות, להריץ קוד בסביבה הזו וכו'. יתרון נוסף הוא שאם תסתבכו ותגיעו למצב שבו התקנת Python שלכם פגומה וכו', תוכלו למחוק את כל הסביבה הזו בלי להשפיע על שאר המערכת.

4. התקנה של ספריית הלקוח של Google APIs ל-Python

בשיעור Codelab הזה נדרש שימוש בספריית הלקוח של Google APIs ל-Python, כך שתהליך ההתקנה פשוט, או שאולי לא תצטרכו לעשות כלום.

מומלץ להשתמש ב-Cloud Shell כדי להקל על התהליך. אפשר להשלים את כל ההדרכה מדפדפן אינטרנט בענן. סיבה נוספת להשתמש ב-Cloud Shell היא שרבים מכלי הפיתוח הפופולריים והספריות הנדרשות מותקנים מראש.

*התקנה של ספריות לקוח

(אופציונלי) אפשר לדלג על השלב הזה אם אתם משתמשים ב-Cloud Shell או בסביבה מקומית שבה כבר התקנתם את ספריות הלקוח. צריך לעשות את זה רק אם אתם מפתחים באופן מקומי ולא התקנתם את הכלים האלה (או שאתם לא בטוחים שהתקנתם אותם). הדרך הכי קלה היא להשתמש ב-pip (או ב-pip3) כדי לבצע את ההתקנה (כולל עדכון של pip עצמו אם צריך):

pip install -U pip google-api-python-client oauth2client

אישור ההתקנה

הפקודה הזו מתקינה את ספריית הלקוח וגם את כל החבילות שהיא תלויה בהן. בין אם אתם משתמשים ב-Cloud Shell או בסביבה משלכם, כדי לוודא שספריית הלקוח מותקנת, מייבאים את החבילות הנדרשות ומוודאים שלא מופיעות שגיאות ייבוא (או פלט):

python3 -c "import googleapiclient, httplib2, oauth2client"

אם משתמשים ב-Python 2 (מ-Cloud Shell), תוצג אזהרה שהתמיכה בה הוצאה משימוש:

*******************************************************************************
Python 2 is deprecated. Upgrade to Python 3 as soon as possible.
See https://cloud.google.com/python/docs/python2-sunset

To suppress this warning, create an empty ~/.cloudshell/no-python-warning file.
The command will automatically proceed in  seconds or on any key.
*******************************************************************************

אחרי שתצליחו להריץ את פקודת הייבוא הזו (ללא שגיאות או פלט), תוכלו להתחיל לתקשר עם ממשקי ה-API של Google.

סיכום

זהו שיעור Codelab ברמת ביניים, ולכן אנחנו מניחים שכבר יש לכם ניסיון ביצירה של פרויקטים במסוף ובשימוש בהם. אם אתם חדשים ב-Google APIs, ובמיוחד ב-Google Workspace APIs, כדאי לנסות קודם את ה-Codelab המבואי בנושא Google Workspace APIs. בנוסף, אם אתם יודעים איך ליצור (או לעשות שימוש חוזר ב) פרטי כניסה של חשבון משתמש (לא חשבון שירות), אתם יכולים להעתיק את קובץ ה-client_secret.json לספריית העבודה, לדלג על המודול הבא ולעבור אל 'הפעלת Google APIs'.

5. *הרשאה לבקשות API (הרשאת משתמש)

אפשר לדלג על הקטע הזה אם כבר יצרתם פרטי כניסה של הרשאת חשבון משתמש ואתם מכירים את התהליך. היא שונה מהרשאה של חשבון שירות, שמתבססת על טכניקה אחרת, ולכן צריך להמשיך לקרוא.

מבוא להרשאה (וגם אימות)

כדי לשלוח בקשות ל-APIs, לאפליקציה שלכם צריכה להיות הרשאה מתאימה. אימות הוא מונח דומה שמתאר פרטי כניסה – אתם מאמתים את עצמכם כשאתם נכנסים לחשבון Google באמצעות שם משתמש וסיסמה. אחרי האימות, השלב הבא הוא לבדוק אם אתם – או יותר נכון, הקוד שלכםמורשים לגשת לנתונים, כמו קובצי blob ב-Cloud Storage או קבצים אישיים של משתמש ב-Google Drive.

ממשקי Google API תומכים בכמה סוגים של הרשאות, אבל הסוג הכי נפוץ עבור משתמשי G Suite API הוא הרשאת משתמש, כי האפליקציה לדוגמה בסדנת התכנות הזו ניגשת לנתונים ששייכים למשתמשי קצה. משתמשי הקצה האלה צריכים להעניק הרשאה לאפליקציה שלכם לגשת לנתונים שלהם. המשמעות היא שהקוד צריך לקבל פרטי כניסה של OAuth2 לחשבון המשתמש.

כדי לקבל פרטי כניסה של OAuth2 להרשאת משתמש, חוזרים אל API Manager ובוחרים בכרטיסייה Credentials (פרטי כניסה) בסרגל הניווט הימני:

635af008256d323.png

כשתגיעו לשם, תראו את כל פרטי הכניסה שלכם בשלושה קטעים נפרדים:

fd2f4133b406d572.png

האפשרות הראשונה היא מפתחות API, השנייה היא מזהי לקוחות ב-OAuth 2.0 והאחרונה היא חשבונות שירות ב-OAuth2. אנחנו משתמשים באפשרות האמצעית.

יצירת פרטי כניסה

בדף Credentials (פרטי כניסה), לוחצים על הלחצן + Create Credentials (יצירת פרטי כניסה) בחלק העליון. מוצג דו-שיח שבו בוחרים באפשרות OAuth client ID (מזהה לקוח OAuth):

b17b663668e38787.png

במסך הבא יש 2 פעולות: הגדרת מסך ההסכמה של האפליקציה לבקשת הרשאה ובחירת סוג האפליקציה:

4e0b967c9d70d262.png

אם לא הגדרתם מסך הסכמה, תופיע אזהרה במסוף ותצטרכו להגדיר אותו עכשיו. (אם כבר הגדרתם את מסך ההסכמה, אפשר לדלג על השלבים הבאים).

לוחצים על 'הגדרת מסך הסכמה' ובוחרים באפשרות 'חיצונית' (או 'פנימית' אם אתם לקוחות G Suite):

f17e97b30d994b0c.png

שימו לב: לצורך התרגיל הזה, לא משנה מה תבחרו כי לא תפרסמו את הדוגמה של ה-codelab. רוב האנשים יבחרו באפשרות 'חיצוני' כדי לעבור למסך מורכב יותר, אבל בפועל צריך למלא רק את השדה 'שם האפליקציה' בחלק העליון:

b107ab81349bdad2.png

בשלב הזה צריך רק שם לאפליקציה, אז בוחרים שם שמשקף את ה-codelab שאתם מבצעים ולוחצים על שמירה.

יצירת מזהה לקוח OAuth (אימות חשבון משתמש)

עכשיו חוזרים לכרטיסייה Credentials (פרטי כניסה) כדי ליצור מזהה לקוח OAuth2. כאן תוכלו לראות מגוון של מזהי לקוח OAuth שתוכלו ליצור:

5ddd365ac0af1e34.png

אנחנו מפתחים כלי לשורת פקודה, שהוא אחר, אז בוחרים באפשרות הזו ולוחצים על הלחצן יצירה. בוחרים שם למזהה הלקוח שמשקף את האפליקציה שיוצרים, או פשוט משתמשים בשם ברירת המחדל, שהוא בדרך כלל Other client N (לקוח אחר N).

שמירת פרטי הכניסה

  1. מופיעה תיבת דו-שיח עם פרטי הכניסה החדשים. לוחצים על אישור כדי לסגור אותה.

8bec84d82cb104d7.png

  1. חוזרים לדף Credentials (פרטי כניסה), גוללים לקטע OAuth2 Client IDs (מזהי לקוח OAuth2), מאתרים את מזהה הלקוח החדש ולוחצים על סמל ההורדה f54b28417901b3aa.png בקצה השמאלי התחתון שלו. 1b4e8d248274a338.png
  2. תיבת דו-שיח תיפתח כדי לשמור קובץ בשם client_secret-LONG-HASH-STRING.apps.googleusercontent.com.json, כנראה בתיקייה Downloads. מומלץ לקצר את השם למשהו קל יותר כמו client_secret.json (זה השם שבו נעשה שימוש באפליקציה לדוגמה), ואז לשמור אותו בספרייה או בתיקייה שבה תיצרו את האפליקציה לדוגמה ב-codelab הזה.

סיכום

עכשיו אפשר להפעיל את ממשקי Google API שמשמשים בשיעור Codelab הזה. בנוסף, בחרנו את השם 'הדגמה של Vision API' לאפליקציה במסך ההסכמה ל-OAuth, ולכן השם הזה יופיע בחלק מצילומי המסך הבאים.

6. הפעלת ממשקי Google APIs

בשיעור Codelab הזה נשתמש ב-ארבעה (4) ממשקי Google Cloud API, זוג אחד מ-Google Cloud (Cloud Storage ו-Cloud Vision) וזוג נוסף מ-Google Workspace (Google Drive ו-Google Sheets). בהמשך מפורטות הוראות כלליות להפעלת ממשקי API של Google. אחרי שמבינים איך להפעיל API אחד, קל להפעיל את האחרים.

לא משנה באיזה Google API אתם רוצים להשתמש באפליקציה, אתם צריכים להפעיל אותו. אפשר להפעיל ממשקי API משורת הפקודה או ממסוף Cloud. תהליך ההפעלה של ממשקי API זהה, כך שאחרי שמפעילים ממשק API אחד, אפשר להפעיל אחרים באופן דומה.

אפשרות 1: gcloud ממשק שורת הפקודה (Cloud Shell או סביבה מקומית)

הפעלת ממשקי API דרך מסוף Cloud היא פעולה נפוצה יותר, אבל יש מפתחים שמעדיפים לבצע את כל הפעולות משורת הפקודה. כדי לעשות זאת, צריך לחפש את 'שם השירות' של ה-API. נראה שזו כתובת URL: SERVICE_NAME.googleapis.com. אפשר למצוא אותם בטבלת המוצרים הנתמכים, או שאפשר לשלוח שאילתה באופן פרוגרמטי באמצעות Google Discovery API.

אחרי שיש לכם את המידע הזה, אתם יכולים להשתמש ב-Cloud Shell (או בסביבת הפיתוח המקומית עם gcloud כלי שורת הפקודה מותקן) כדי להפעיל API או שירות, באופן הבא:

gcloud services enable SERVICE_NAME.googleapis.com

דוגמה 1: הפעלת Cloud Vision API

gcloud services enable vision.googleapis.com

דוגמה 2: הפעלת פלטפורמת מחשוב ללא שרתים Google App Engine

gcloud services enable appengine.googleapis.com

דוגמה 3: הפעלת כמה ממשקי API באמצעות בקשה אחת. לדוגמה, אם הצופים ב-Codelab הזה פורסים אפליקציה באמצעות Cloud Translation API ל-App Engine, ל-Cloud Functions ול-Cloud Run, שורת הפקודה תהיה:

gcloud services enable appengine.googleapis.com cloudfunctions.googleapis.com artifactregistry.googleapis.com run.googleapis.com translate.googleapis.com

הפקודה הזו מפעילה את App Engine, את Cloud Functions, את Cloud Run ואת Cloud Translation API. בנוסף, הוא מפעיל את Cloud Artifact Registry, כי שם מערכת Cloud Build צריכה לרשום קובצי אימג' של קונטיינרים כדי לפרוס אותם ב-Cloud Run.

יש גם כמה פקודות שאפשר להשתמש בהן כדי לשאול אילו ממשקי API צריך להפעיל או אילו ממשקי API כבר הופעלו בפרויקט.

דוגמה 4: שאילתה לגבי ממשקי Google API שזמינים להפעלה בפרויקט

gcloud services list --available --filter="name:googleapis.com"

דוגמה 5: שאילתה לממשקי Google API שמופעלים בפרויקט

gcloud services list

מידע נוסף על הפקודות שלמעלה זמין במאמרים בנושא הפעלה והשבתה של שירותים ורשימת שירותים.

אפשרות 2: Cloud Console

אפשר גם להפעיל את ממשקי ה-API של Google ב-API Manager. במסוף Cloud, עוברים אל API Manager. בדף לוח הבקרה הזה מוצג מידע על התנועה באפליקציה, גרפים שמציגים את הבקשות של האפליקציה, שגיאות שנוצרו על ידי האפליקציה וזמני התגובה של האפליקציה:

df4a0a5e00d29ffc.png

מתחת לתרשימים האלה מופיעה רשימה של ממשקי Google API שמופעלים בפרויקט:

5fcf10e5a05cfb97.png

כדי להפעיל (או להשבית) ממשקי API, לוחצים על Enable APIs and Services (הפעלת ממשקי API ושירותים) בחלק העליון:

eef4e5e863f4db66.png

אפשרות נוספת היא לעבור לסרגל הניווט הימני ולבחור באפשרות APIs & Services (ממשקי API ושירותים) ← Library (ספרייה):

6eda5ba145b30b97.png

בכל מקרה, תגיעו לדף API Library:

5d4f1c8e7cf8df28.png

מזינים שם של API כדי לחפש ולראות תוצאות תואמות:

35bc4b9cf72ce9a4.png

בוחרים את ה-API שרוצים להפעיל ולוחצים על הכפתור הפעלה:

9574a69ef8d9e8d2.png

תהליך ההפעלה של כל ממשקי ה-API דומה, לא משנה באיזה Google API רוצים להשתמש.

עלות

אפשר להשתמש ב-Google APIs רבים ללא תשלום, אבל יש עלויות כשמשתמשים ברוב המוצרים וב-APIs של Google Cloud. כשמפעילים ממשקי Cloud API, יכול להיות שתתבקשו לספק חשבון לחיוב פעיל. עם זאת, חלק ממוצרי Google Cloud כוללים תוכנית 'תמיד בחינם', שרק אם חורגים ממנה מתחילים לשלם.

משתמשים חדשים ב-Google Cloud זכאים לתקופת ניסיון בחינם, שכוללת קרדיט בסך 300 $לשימוש ב-90 הימים הראשונים. בדרך כלל לא נדרש תשלום על Codelabs, או שהתשלום נמוך מאוד, ולכן מומלץ להמתין עם תקופת הניסיון בחינם עד שתהיו מוכנים באמת לנסות אותה, במיוחד כי מדובר בהצעה חד-פעמית. המכסות של התוכנית בחינם לא פוקעות, והן חלות גם אם אתם משתמשים בתקופת הניסיון בחינם וגם אם לא.

לפני שמפעילים API כלשהו, כדאי לעיין בפרטי התמחור שלו (לדוגמה, בדף התמחור של Cloud Vision API ), ולבדוק אם יש לו רמת שירות בחינם, ואם כן, מה היא. אם תשתמשו ב-API במסגרת המגבלות היומיות או החודשיות שצוינו, לא תחויבו על השימוש. התמחור והתוכניות החינמיות משתנים בין ממשקי ה-API של קבוצת מוצרי Google. דוגמאות:

יש מוצרים שונים של Google שמתבצע בהם חיוב בדרכים שונות, ולכן חשוב לעיין במסמכים המתאימים כדי לקבל את המידע הזה.

סיכום

אחרי שמפעילים את Cloud Vision, מפעילים את שלושת ממשקי ה-API האחרים (Google Drive,‏ Cloud Storage ו-Google Sheets) באותו אופן. מ-Cloud Shell, משתמשים ב-gcloud services enable, או ממסוף Cloud:

  1. חזרה אל API Library
  2. מתחילים חיפוש בהקלדת כמה אותיות מהשם
  3. בוחרים את ה-API הרצוי, ו
  4. הפעלה

מקציפים, שוטפים וחוזרים על הפעולה. ב-Cloud Storage יש כמה אפשרויות: בוחרים באפשרות Google Cloud Storage API בפורמט JSON. ממשק Cloud Storage API יצפה גם לחשבון לחיוב פעיל.

7. שלב 0: הגדרת ייבוא וקוד הרשאה

זהו קטע קוד בינוני, ולכן כדאי לפעול לפי שיטות עבודה זריזות כדי לוודא שחלק מהתשתית יהיה משותף, יציב ופעיל לפני שמתמודדים עם האפליקציה הראשית. מוודאים ש-client_secret.json זמין בספרייה הנוכחית, מפעילים את ipython ומזינים את קטע הקוד הבא, או שומרים אותו ב-analyze_gsimg.py ומפעילים פתרונות חכמים מהמעטפת (האפשרות השנייה עדיפה כי נמשיך להוסיף לדוגמת קוד):

from __future__ import print_function

from googleapiclient import discovery, http
from httplib2 import Http
from oauth2client import file, client, tools

# process credentials for OAuth2 tokens
SCOPES = 'https://www.googleapis.com/auth/drive.readonly'
store = file.Storage('storage.json')
creds = store.get()
if not creds or creds.invalid:
    flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
    creds = tools.run_flow(flow, store)

# create API service endpoints
HTTP = creds.authorize(Http())
DRIVE  = discovery.build('drive',   'v3', http=HTTP)

רכיב הליבה הזה כולל בלוקים של קוד לייבוא מודולים או חבילות, לעיבוד פרטי אימות משתמש וליצירת נקודות קצה של שירותי API. החלקים העיקריים בקוד שכדאי לבדוק:

  • הייבוא של הפונקציה print() הופך את דוגמת הקוד הזו לתואמת ל-Python 2-3, והייבוא של ספריית Google כולל את כל הכלים שנדרשים לתקשורת עם ממשקי Google API.
  • המשתנה SCOPES מייצג את ההרשאות לבקשה מהמשתמש – יש רק אחת כרגע: ההרשאה לקרוא נתונים מ-Google Drive שלו
  • שאר הקוד לעיבוד פרטי הכניסה קורא את הטוקנים של OAuth2 שנשמרו במטמון, ויכול להיות שהוא יעדכן לטוקן גישה חדש באמצעות טוקן הרענון אם פג התוקף של טוקן הגישה המקורי.
  • אם לא נוצרו טוקנים או שאחזור של טוקן גישה תקין נכשל מסיבה אחרת, המשתמש צריך לעבור את תהליך OAuth2 3LO: ליצור את תיבת הדו-שיח עם ההרשאות המבוקשות ולבקש מהמשתמש לאשר. אם כן, האפליקציה ממשיכה לפעול. אם לא, הפונקציה tools.run_flow() גורמת לחריגה והביצוע נעצר.
  • אחרי שהמשתמש מעניק הרשאה, נוצר לקוח HTTP כדי לתקשר עם השרת, וכל הבקשות נחתמות באמצעות פרטי הכניסה של המשתמש לצורך אבטחה. לאחר מכן, נוצרת נקודת קצה של שירות ל-Google Drive API (גרסה 3) עם לקוח ה-HTTP הזה, והיא מוקצית ל-DRIVE.

הפעלת האפליקציה

בפעם הראשונה שמריצים את הסקריפט, לא תהיה לו הרשאה לגשת לקבצים של המשתמש ב-Drive (שלכם). הפלט אמור להיראות כך כשההרצה מושהית:

$ python3 ./analyze_gsimg.py
/usr/local/lib/python3.6/site-packages/oauth2client/_helpers.py:255: UserWarning: Cannot access storage.json: No such file or directory
  warnings.warn(_MISSING_FILE_MESSAGE.format(filename))

Your browser has been opened to visit:
    https://accounts.google.com/o/oauth2/auth?client_id=LONG-STRING.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.readonly&access_type=offline&response_type=code

If your browser is on a different machine then exit and re-run this
application with the command-line parameter

  --noauth_local_webserver

אם אתם מריצים את הפקודה מ-Cloud Shell, אתם יכולים לדלג לקטע 'מ-Cloud Shell' ואז לחזור אחורה כדי לעיין במסכים הרלוונטיים בקטע 'מסביבת פיתוח מקומית' כשזה מתאים.

מסביבת פיתוח מקומית

הסקריפט של שורת הפקודה מושהה כשחלון הדפדפן נפתח. יכול להיות שיוצג לכם דף אזהרה שנראה מפחיד, כמו זה:

149241d33871a141.png

זו דאגה לגיטימית, כי אתה מנסה להריץ אפליקציה שיש לה גישה לנתוני משתמשים. מכיוון שמדובר באפליקציית הדגמה בלבד, ואתם המפתחים, אנחנו מקווים שאתם סומכים על עצמכם מספיק כדי להמשיך. כדי להבין את זה טוב יותר, נסו לחשוב כמו המשתמשים שלכם: מבקשים מהם לאפשר לקוד של מישהו אחר לגשת לנתונים שלהם. אם אתם מתכוונים לפרסם אפליקציה כזו, תצטרכו לעבור את תהליך האימות כדי שהמשתמשים לא יראו את המסך הזה.

אחרי שלוחצים על הקישור 'מעבר לאפליקציה לא בטוחה', מוצגת תיבת דו-שיח של הרשאות OAuth2 שנראית בערך כמו זו שבהמשך. אנחנו כל הזמן משפרים את ממשק המשתמש, אז אל דאגה אם הוא לא זהה בדיוק:

a122eb7468d0d34e.png

בתיבת הדו-שיח של זרימת OAuth2 מוצגות ההרשאות שהמפתח מבקש (דרך המשתנה SCOPES). במקרה הזה, מדובר באפשרות לצפות ולהוריד מ-Google Drive של המשתמש. בקוד אפליקציה, היקפי ההרשאות האלה מופיעים ככתובות URI, אבל הם מתורגמים לשפה שצוינה על ידי הלוקאל של המשתמש. במקרה הזה, המשתמש חייב לתת הרשאה מפורשת להרשאות המבוקשות, אחרת תופעל חריגה והסקריפט לא ימשיך לפעול.

יכול להיות שיופיע עוד דו-שיח שבו תתבקשו לאשר את הפעולה:

bf171080dcd6ec5.png

NOTE: חלק מהמשתמשים מחוברים לכמה חשבונות בכמה דפדפני אינטרנט, ולכן יכול להיות שבקשת ההרשאה הזו תופיע בכרטיסייה בדפדפן או בחלון הלא נכונים בדפדפן. במקרה כזה, תצטרכו להעתיק את הקישור של הבקשה ולהדביק אותו בדפדפן שבו אתם מחוברים לחשבון הנכון.

מ-Cloud Shell

מ-Cloud Shell, לא מופיע חלון דפדפן קופץ, ולכן אתם לא יכולים להמשיך. הבנתם שהודעת האבחון שמופיעה בתחתית מיועדת לכם:

If your browser is on a different machine then exit and re-run this
application with the command-line parameter

  --noauth_local_webserver

תצטרכו להשתמש בצירוף המקשים ^C (או Ctrl-C או צירוף מקשים אחר כדי לעצור את הפעלת הסקריפט), ולהפעיל אותו מהמעטפת עם הדגל הנוסף. אם מפעילים פתרונות חכמים בצורה הזו, הפלט שמתקבל הוא:

$ python3 analyze_gsimg.py --noauth_local_webserver
/usr/local/lib/python3.7/site-packages/oauth2client/_helpers.py:255: UserWarning: Cannot access storage.json: No such file or directory
  warnings.warn(_MISSING_FILE_MESSAGE.format(filename))

Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?client_id=LONG-STRING.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.readonly&access_type=offline&response_type=code

Enter verification code:

(מתעלמים מהאזהרה כי יודעים שעדיין לא נוצר storage.json) פועלים לפי ההוראות בכרטיסיית דפדפן אחרת עם כתובת ה-URL הזו, ותקבלו חוויה כמעט זהה למה שמתואר למעלה לגבי סביבות פיתוח מקומיות (ראו צילומי מסך למעלה). בסוף התהליך מוצג מסך אחרון עם קוד האימות שצריך להזין ב-Cloud Shell:

40a567cda0f31cc9.png

מעתיקים את הקוד הזה ומדביקים אותו בחלון הטרמינל.

סיכום

אל תצפה לפלט נוסף מלבד "Authentication successful". חשוב לזכור שזו רק ההגדרה… עדיין לא עשיתם כלום. מה שעשית הוא להתחיל את המסע שלך לקראת משהו שיש סיכוי גבוה יותר שיפעל בצורה נכונה בפעם הראשונה. (החלק הכי טוב הוא שהתבקשת לתת הרשאה רק פעם אחת. כל ההרצות הבאות מדלגות על השלב הזה כי ההרשאות שלך נשמרו במטמון). עכשיו נגרום לקוד לבצע עבודה אמיתית שתניב פלט בפועל.

פתרון בעיות

אם מופיעה שגיאה במקום פלט, יכולות להיות לכך כמה סיבות, אולי אחת מהסיבות הבאות:

8. שלב 1: הורדת תמונה מ-Google Drive

בשלב הקודם המלצנו ליצור את הקוד כ-analyze_gsimg.py ולערוך אותו משם. אפשר גם פשוט לגזור ולהדביק את הכול ישירות ב-iPython או במעטפת Python רגילה, אבל זה מסורבל יותר כי אנחנו נמשיך לבנות את האפליקציה חלק אחר חלק.

נניח שהאפליקציה שלכם קיבלה הרשאה ונוצרה נקודת קצה של שירות API. בקוד, הוא מיוצג על ידי המשתנה DRIVE. עכשיו נחפש קובץ תמונה ב-Google Drive ו

מגדירים אותו למשתנה שנקרא NAME. מזינים את הפונקציה drive_get_img() הבאה ממש מתחת לקוד משלב 0:

FILE = 'YOUR_IMG_ON_DRIVE'  # fill-in with name of your Drive file

def drive_get_img(fname):
    'download file from Drive and return file info & binary if found'

    # search for file on Google Drive
    rsp = DRIVE.files().list(q="name='%s'" % fname,
            fields='files(id,name,mimeType,modifiedTime)'
    ).execute().get('files', [])

    # download binary & return file info if found, else return None
    if rsp:
        target = rsp[0]  # use first matching file
        fileId = target['id']
        fname = target['name']
        mtype = target['mimeType']
        binary = DRIVE.files().get_media(fileId=fileId).execute()
        return fname, mtype, target['modifiedTime'], binary

לאוסף files() ב-Drive יש שיטה list() שמבצעת שאילתה (הפרמטר q) לגבי הקובץ שצוין. הפרמטר fields משמש לציון אילו ערכים מוחזרים מעניינים אתכם. למה להחזיר את כל הערכים ולהאט את התהליך אם לא אכפת לכם מהערכים האחרים? אם אתם חדשים בשימוש בfield masks לסינון ערכי החזרה של API, כדאי לעיין בפוסט הזה בבלוג ובסרטון הזה. אחרת, השאילתה מופעלת והמאפיין files שמוחזר נלקח, ומוחזר מערך של רשימה ריקה אם אין התאמות.

אם אין תוצאות, שאר הפונקציה מדלגת ומוחזרת None (באופן מרומז). אחרת, צריך לאחזר את התגובה הראשונה התואמת (rsp[0]), להחזיר את שם הקובץ, את סוג ה-MIME שלו, את חותמת הזמן של השינוי האחרון ולבסוף את מטען הנתונים הבינארי שלו, שאוחזר באמצעות הפונקציה get_media() (דרך מזהה הקובץ שלו), גם באוסף files(). (יכול להיות ששמות השיטות יהיו שונים מעט בספריות לקוח בשפות אחרות).

החלק האחרון הוא הגוף הראשי שמפעיל את כל האפליקציה:

if __name__ == '__main__':
    # download img file & info from Drive
    rsp = drive_get_img(FILE)
    if rsp:
        fname, mtype, ftime, data = rsp
        print('Downloaded %r (%s, %s, size: %d)' % (fname, mtype, ftime, len(data)))
    else:
        print('ERROR: Cannot download %r from Drive' % fname)

בהנחה שיש תמונה בשם section-work-card-img_2x.jpg ב-Drive והיא מוגדרת כFILE, אחרי הרצת הסקריפט בהצלחה אמור להופיע פלט שמאשר שהייתה אפשרות לקרוא את הקובץ מ-Drive (אבל הוא לא נשמר במחשב):

$ python3 analyze_gsimg.py
Downloaded 'section-work-card-img_2x.jpg' (image/jpeg, 2020-02-27T09:27:22.095Z, size: 27781)

פתרון בעיות

אם לא קיבלתם פלט כמו בדוגמה שלמעלה, יכול להיות שהבעיה נובעת מאחת או יותר מהסיבות הבאות, למשל:

סיכום

בקטע הזה למדתם איך להתחבר ל-Drive API (ב-2 קריאות נפרדות ל-API), לשלוח שאילתה לגבי קובץ ספציפי ואז להוריד אותו. תרחיש שימוש עסקי: ארכוב של נתוני Drive וניתוח שלהם, למשל באמצעות כלי Google Cloud. הקוד של האפליקציה בשלב הזה צריך להיות זהה למה שמופיע במאגר בכתובתstep1-drive/analyze_gsimg.py.

כאן אפשר לקרוא מידע נוסף על הורדת קבצים ב-Google Drive, או לצפות בסרטון ובפוסט הזה בבלוג. החלק הזה של שיעור ה-Codelab כמעט זהה למבוא המלא לשיעור ה-Codelab בנושא ממשקי API של Google Workspace – במקום להוריד קובץ, הוא מציג את 100 הקבצים או התיקיות הראשונים ב-Google Drive של המשתמש ומשתמש בהיקף מוגבל יותר.

9. שלב 2: העברת קובץ לארכיון ב-Cloud Storage

השלב הבא הוא להוסיף תמיכה ב-Google Cloud Storage. לשם כך צריך לייבא חבילת Python נוספת, io. מוודאים שהחלק העליון של הקובץ המיובא נראה עכשיו כך:

from __future__ import print_function                   
import io

בנוסף לשם הקובץ ב-Drive, אנחנו צריכים מידע על המקום שבו הקובץ הזה יישמר ב-Cloud Storage, ובאופן ספציפי את השם של ה-bucket שבו הוא יישמר ואת הקידומות של תיקיות האם. עוד על הנושא הזה בהמשך:

FILE = 'YOUR_IMG_ON_DRIVE'
BUCKET = 'YOUR_BUCKET_NAME'
PARENT = ''     # YOUR IMG FILE PREFIX                  

הערה לגבי קטגוריות: Cloud Storage מספק אחסון של כתמי מידע אמורפיים. כשמעלים קבצים לשם, המערכת לא מבינה את המושג של סוגי קבצים, סיומות וכו', כמו ב-Google Drive. מבחינת Cloud Storage, הם רק 'כתמים'. בנוסף, אין ב-Cloud Storage מושג של תיקיות או תיקיות משנה.

כן, אפשר להשתמש בלוכסנים (/) בשמות של קבצים כדי לייצג הפשטה של כמה תיקיות משנה, אבל בסופו של דבר כל ה-blob שלכם נכנסים לדלי, והלוכסנים / הם רק תווים בשמות הקבצים. מידע נוסף זמין בדף בנושא מוסכמות למתן שמות לקטגוריות ולאובייקטים.

בשלב 1 למעלה נדרשת הרשאת קריאה בלבד ב-Drive. בזמנו, זה כל מה שהיה צריך. עכשיו נדרשת הרשאת העלאה (קריאה-כתיבה) ל-Cloud Storage. משנים את SCOPES ממשתנה מחרוזת יחיד למערך (tuple [או רשימה] של Python) של היקפי הרשאות, כך שזה ייראה כך:

SCOPES = (
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/devstorage.full_control',
)                  

עכשיו יוצרים נקודת קצה של שירות ל-Cloud Storage, ממש מתחת לנקודת הקצה של Drive. שימו לב ששינינו קצת את הקריאה כדי לעשות שימוש חוזר באותו אובייקט של לקוח HTTP, כי אין צורך ליצור אובייקט חדש אם אפשר להשתמש במשאב משותף.

# create API service endpoints
HTTP = creds.authorize(Http())
DRIVE  = discovery.build('drive',   'v3', http=HTTP)
GCS    = discovery.build('storage', 'v1', http=HTTP)                  

עכשיו מוסיפים את הפונקציה הזו (אחרי drive_get_img()) שמעלה ל-Cloud Storage:

def gcs_blob_upload(fname, bucket, media, mimetype):
    'upload an object to a Google Cloud Storage bucket'

    # build blob metadata and upload via GCS API
    body = {'name': fname, 'uploadType': 'multipart', 'contentType': mimetype}
    return GCS.objects().insert(bucket=bucket, body=body,
            media_body=http.MediaIoBaseUpload(io.BytesIO(media), mimetype),
            fields='bucket,name').execute()

הקריאה objects.().insert() מחייבת את שם הקטגוריה, את המטא-נתונים של הקובץ ואת ה-blob הבינארי עצמו. כדי לסנן את הערכים שמוחזרים, המשתנה fields מבקש רק את שמות הקטגוריות והאובייקטים שמוחזרים מה-API. מידע נוסף על מסכות שדות בבקשות קריאה של API זמין בפוסט ובסרטון האלה.

עכשיו משלבים את השימוש ב-gcs_blob_upload() באפליקציה הראשית:

        # upload file to GCS
        gcsname = '%s/%s'% (PARENT, fname)
        rsp = gcs_blob_upload(gcsname, BUCKET, data, mtype)
        if rsp:
            print('Uploaded %r to GCS bucket %r' % (rsp['name'], rsp['bucket']))
        else:
            print('ERROR: Cannot upload %r to Cloud Storage' % gcsname)

המשתנה gcsname ממזג את כל השמות של 'ספריית המשנה הראשית' שצורפו לשם הקובץ עצמו, וכשמוסיפים לו את שם ה-bucket, מתקבל הרושם שאתם מאחסנים את הקובץ בארכיון בנתיב /bucket/parent.../filename. מעתיקים את החלק הזה מיד אחרי הפונקציה הראשונה print(), ממש מעל הסעיף else, כך שהפונקציה הראשית כולה תיראה כך:

if __name__ == '__main__':
    # download img file & info from Drive
    rsp = drive_get_img(FILE)
    if rsp:
        fname, mtype, ftime, data = rsp
        print('Downloaded %r (%s, %s, size: %d)' % (fname, mtype, ftime, len(data)))

        # upload file to GCS
        gcsname = '%s/%s'% (PARENT, fname)
        rsp = gcs_blob_upload(gcsname, BUCKET, data, mtype)
        if rsp:
            print('Uploaded %r to GCS bucket %r' % (rsp['name'], rsp['bucket']))
        else:
            print('ERROR: Cannot upload %r to Cloud Storage' % gcsname)
    else:
        print('ERROR: Cannot download %r from Drive' % fname)

נניח שציינו קטגוריה בשם vision-demo עם analyzed_imgs כ"ספריית משנה ראשית". אחרי שמגדירים את המשתנים האלה ומריצים את הסקריפט שוב, הקובץ section-work-card-img_2x.jpg יורד מ-Drive ואז מועלה ל-Cloud Storage, נכון? לא!

$ python3 analyze_gsimg.py 
Downloaded 'section-work-card-img_2x.jpg' (image/jpeg, 2020-02-27T09:27:22.095Z, size: 27781)
Traceback (most recent call last):
  File "analyze_gsimg.py", line 85, in <module>
    io.BytesIO(data), mimetype=mtype), mtype)
  File "analyze_gsimg.py", line 72, in gcs_blob_upload
    media_body=media, fields='bucket,name').execute()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/googleapiclient/_helpers.py", line 134, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/googleapiclient/http.py", line 898, in execute
    raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://storage.googleapis.com/upload/storage/v1/b/PROJECT_ID/o?fields=bucket%2Cname&alt=json&uploadType=multipart returned "Insufficient Permission">

אפשר לראות שההורדה מ-Drive הצליחה, אבל ההעלאה ל-Cloud Storage נכשלה. למה?

הסיבה לכך היא שכשאשרנו את האפליקציה הזו במקור בשלב 1, אישרנו רק גישה לקריאה בלבד ל-Google Drive. למרות שהוספנו את היקף ההרשאות לקריאה וכתיבה ב-Cloud Storage, אף פעם לא ביקשנו מהמשתמש לאשר את הגישה הזו. כדי שהפעולה תצליח, צריך למחוק את הקובץ storage.json שחסר בו ההיקף הזה ולהפעיל מחדש.

אחרי שתאשרו מחדש את ההרשאה (אפשר לוודא את זה בתוך storage.json ולראות את שני ההיקפים), הפלט יהיה כמו שציפיתם:

$ python3 analyze_gsimg.py

    . . .

Authentication successful.
Downloaded 'section-work-card-img_2x.jpg' (image/jpeg, 2020-02-27T09:27:22.095Z, size: 27781)
Uploaded 'analyzed_imgs/section-work-card-img_2x.jpg' to GCS bucket 'vision-demo'

סיכום

זוהי דוגמה חשובה שמראה, בכמה שורות קוד יחסית, איך להעביר קבצים בין שתי מערכות אחסון מבוססות-ענן. התרחיש העסקי כאן הוא גיבוי של משאב מוגבל אפשרי לאחסון "קר" וזול יותר, כפי שצוין קודם. ב-Cloud Storage יש סוגי אחסון שונים בהתאם לתדירות שבה אתם ניגשים לנתונים – באופן קבוע, חודשי, רבעוני או שנתי.

מפתחים שואלים אותנו מדי פעם למה קיימים גם Google Drive וגם Cloud Storage. אחרי הכול, הם שניהם אחסון קבצים בענן, לא? לכן יצרנו את הסרטון הזה. הקוד בשלב הזה צריך להיות זהה למה שמופיע במאגר בכתובתstep2-gcs/analyze_gsimg.py.

10. שלב 3: ניתוח באמצעות Cloud Vision

עכשיו אנחנו יודעים שאפשר להעביר נתונים בין Google Cloud ל-Google Workspace, אבל עדיין לא ביצענו ניתוח. הגיע הזמן לשלוח את התמונה אל Cloud Vision כדי להוסיף לה תווית, כלומר זיהוי אובייקטים. לשם כך, צריך לקודד את הנתונים ב-Base64, כלומר מודול Python נוסף, base64. מוודאים שחלק היבוא העליון נראה עכשיו כך:

from __future__ import print_function
import base64
import io

כברירת מחדל, Vision API מחזיר את כל התוויות שהוא מוצא. כדי לשמור על עקביות, נבקש רק את 5 התוצאות הראשונות (המשתמש יכול לשנות את המספר הזה). נשתמש במשתנה קבוע TOP לצורך הזה. מוסיפים אותו מתחת לכל הקבועים האחרים:

FILE = 'YOUR_IMG_ON_DRIVE'
BUCKET = 'YOUR_BUCKET_NAME'
PARENT = ''   # YOUR IMG FILE PREFIX 
TOP = 5       # TOP # of VISION LABELS TO SAVE                 

כמו בשלבים הקודמים, אנחנו צריכים עוד היקף הרשאות, הפעם ל-Vision API. מעדכנים את SCOPES עם המחרוזת שלו:

SCOPES = (
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/devstorage.full_control',
    'https://www.googleapis.com/auth/cloud-vision',
)                  

עכשיו יוצרים נקודת קצה של שירות ל-Cloud Vision כדי שהיא תהיה באותו קו עם האחרות, כמו בדוגמה הבאה:

# create API service endpoints
HTTP = creds.authorize(Http())
DRIVE  = discovery.build('drive',   'v3', http=HTTP)
GCS    = discovery.build('storage', 'v1', http=HTTP)
VISION = discovery.build('vision',  'v1', http=HTTP)

עכשיו מוסיפים את הפונקציה הזו ששולחת את מטען הנתונים של התמונה אל Cloud Vision:

def vision_label_img(img, top):
    'send image to Vision API for label annotation'

    # build image metadata and call Vision API to process
    body = {'requests': [{
                'image':     {'content': img},
                'features': [{'type': 'LABEL_DETECTION', 'maxResults': top}],
    }]}
    rsp = VISION.images().annotate(body=body).execute().get('responses', [{}])[0]

    # return top labels for image as CSV for Sheet (row)
    if 'labelAnnotations' in rsp:
        return ', '.join('(%.2f%%) %s' % (
                label['score']*100., label['description']) \
                for label in rsp['labelAnnotations'])

הקריאה images().annotate() דורשת את הנתונים בתוספת התכונות הרצויות של ה-API. גם המגבלה של 5 תוויות לכל היותר היא חלק מהמטען הייעודי (אבל היא אופציונלית לחלוטין). אם הקריאה מצליחה, מטען הייעודי מחזיר את 5 התוויות המובילות של האובייקטים, בתוספת ציון מהימנות של האובייקט בתמונה. (אם לא מתקבלת תגובה, צריך להקצות מילון Python ריק כדי שההצהרה הבאה if לא תיכשל). הפונקציה הזו פשוט אוספת את הנתונים האלה למחרוזת CSV לשימוש אפשרי בדוח שלנו.

צריך להוסיף את 5 השורות האלה שקוראות ל-vision_label_img() מיד אחרי ההעלאה המוצלחת ל-Cloud Storage:

            # process w/Vision
            rsp = vision_label_img(base64.b64encode(data).decode('utf-8'), TOP)
            if rsp:
                print('Top %d labels from Vision API: %s' % (TOP, rsp))
            else:
                print('ERROR: Vision API cannot analyze %r' % fname)

אחרי ההוספה, מנהל ההתקן הראשי כולו צריך להיראות כך:

if __name__ == '__main__':
    # download img file & info from Drive
    rsp = drive_get_img(FILE)
    if rsp:
        fname, mtype, ftime, data = rsp
        print('Downloaded %r (%s, %s, size: %d)' % (fname, mtype, ftime, len(data)))

        # upload file to GCS
        gcsname = '%s/%s'% (PARENT, fname)
        rsp = gcs_blob_upload(gcsname, BUCKET, data, mtype)
        if rsp:
            print('Uploaded %r to GCS bucket %r' % (rsp['name'], rsp['bucket']))

            # process w/Vision
            rsp = vision_label_img(base64.b64encode(data).decode('utf-8'), TOP)
            if rsp:
                print('Top %d labels from Vision API: %s' % (TOP, rsp))
            else:
                print('ERROR: Vision API cannot analyze %r' % fname)
        else:
            print('ERROR: Cannot upload %r to Cloud Storage' % gcsname)
    else:
        print('ERROR: Cannot download %r from Drive' % fname)

מחיקת storage.json כדי לרענן את ההיקפים והפעלה מחדש של האפליקציה המעודכנת אמורה להניב פלט שדומה לזה שמופיע בהמשך, עם הערה לגבי הוספת ניתוח Cloud Vision:

$ python3 analyze_gsimg.py

    . . .

Authentication successful.
Downloaded 'section-work-card-img_2x.jpg' (image/jpeg, 2020-02-27T09:27:22.095Z, size: 27781)
Uploaded 'analyzed_imgs/section-work-card-img_2x.jpg' to GCS bucket 'vision-demo'
Top 5 labels from Vision API: (89.94%) Sitting, (86.09%) Interior design, (82.08%) Furniture, (81.52%) Table, (80.85%) Room

סיכום

לא לכולם יש את המומחיות בלמידת מכונה שנדרשת כדי ליצור ולאמן מודלים משלהם של למידת מכונה לצורך ניתוח הנתונים שלהם. צוות Google Cloud פרסם כמה מהמודלים המאומנים מראש של Google לשימוש כללי, והטמיע אותם בממשקי API, כדי להנגיש את ה-AI ואת ה-ML לכולם.

אם אתם מפתחים ויש לכם אפשרות להפעיל API, אתם יכולים להשתמש בלמידת מכונה. ‫Cloud Vision הוא רק אחד משירותי ה-API שבהם אפשר להשתמש כדי לנתח את הנתונים. מידע על שאר האפשרויות זמין כאן. הקוד שלכם צריך להיות זהה לקוד שמופיע במאגר בכתובתstep3-vision/analyze_gsimg.py.

11. שלב 4: יצירת דוח באמצעות Google Sheets

בשלב הזה, הצלחתם לארכב נתונים ארגוניים ולנתח אותם, אבל חסר סיכום של העבודה הזו. בואו נאגד את כל התוצאות בדוח אחד שאפשר להעביר לבוס. מה יותר נוח להצגה להנהלה מגיליון אלקטרוני?

אין צורך בייבוא נוסף ל-Google Sheets API, והמידע החדש היחיד שנדרש הוא מזהה הקובץ של גיליון אלקטרוני קיים שכבר עבר עיצוב וממתין לשורה חדשה של נתונים, ולכן מדובר בקבוע SHEET. מומלץ ליצור גיליון אלקטרוני חדש שדומה לזה:

4def78583d05300.png

כתובת ה-URL של הגיליון האלקטרוני תיראה כך: https://docs.google.com/spreadsheets/d/FILE_ID/edit. בוחרים את FILE_ID ומקצים אותו כג'ינגל ל-SHEET.

הוספנו גם פונקציה קטנה בשם k_ize() שממירה בייטים לקילובייטים, והגדרנו אותה כ-lambda של Python כי היא פונקציה פשוטה של שורה אחת. השילוב של שתי הקבועים האלה נראה כך:

k_ize =  lambda b: '%6.2fK' % (b/1000.)  # bytes to kBs
FILE = 'YOUR_IMG_ON_DRIVE'
BUCKET = 'YOUR_BUCKET_NAME'
PARENT = ''     # YOUR IMG FILE PREFIX
SHEET = 'YOUR_SHEET_ID'
TOP = 5       # TOP # of VISION LABELS TO SAVE                 

כמו בשלבים הקודמים, אנחנו צריכים עוד היקף הרשאות, הפעם קריאה וכתיבה עבור Sheets API. ל-SCOPES יש עכשיו את כל 4 המאפיינים הנדרשים:

SCOPES = (
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/devstorage.full_control',
    'https://www.googleapis.com/auth/cloud-vision',
    'https://www.googleapis.com/auth/spreadsheets',
)                  

עכשיו יוצרים נקודת קצה של שירות ל-Google Sheets ליד האחרות, כך:

# create API service endpoints
HTTP = creds.authorize(Http())
DRIVE  = discovery.build('drive',   'v3', http=HTTP)
GCS    = discovery.build('storage', 'v1', http=HTTP)
VISION = discovery.build('vision',  'v1', http=HTTP)
SHEETS = discovery.build('sheets',  'v4', http=HTTP)

הפונקציונליות של sheet_append_row() פשוטה: לוקחים שורה של נתונים ומזהה של גיליון אלקטרוני, ואז מוסיפים את השורה לגיליון הזה:

def sheet_append_row(sheet, row):
    'append row to a Google Sheet, return #cells added'

    # call Sheets API to write row to Sheet (via its ID)
    rsp = SHEETS.spreadsheets().values().append(
            spreadsheetId=sheet, range='Sheet1',
            valueInputOption='USER_ENTERED', body={'values': [row]}
    ).execute()
    if rsp:
        return rsp.get('updates').get('updatedCells')

הקריאה ל-spreadsheets().values().append() מחייבת את מזהה הקובץ של הגיליון, טווח של תאים, איך הנתונים צריכים להיות מוזנים והנתונים עצמם. מזהה הקובץ הוא פשוט, וטווח התאים מופיע בסימון A1. טווח של Sheet1 מציין את כל הגיליון – זהו האות ל-API להוסיף את השורה אחרי כל הנתונים בגיליון. יש שתי אפשרויות לגבי הוספת הנתונים לגיליון האלקטרוני: RAW (הזנת נתוני המחרוזת בדיוק כמו שהם) או USER_ENTERED (כתיבת הנתונים כאילו משתמש הזין אותם במקלדת באמצעות אפליקציית Google Sheets, תוך שמירה על תכונות העיצוב של התאים).

אם הקריאה מצליחה, ערך ההחזרה לא ממש שימושי, ולכן בחרנו לקבל את מספר התאים שעודכנו על ידי בקשת ה-API. בהמשך מופיע הקוד שקורא לפונקציה הזו:

                # push results to Sheet, get cells-saved count
                fsize = k_ize(len(data))
                row = [PARENT,
                        '=HYPERLINK("storage.cloud.google.com/%s/%s", "%s")' % (
                        BUCKET, gcsname, fname), mtype, ftime, fsize, rsp
                ]
                rsp = sheet_append_row(SHEET, row)
                if rsp:
                    print('Updated %d cells in Google Sheet' % rsp)
                else:
                    print('ERROR: Cannot write row to Google Sheets')

בגיליון Google Sheets יש עמודות שמייצגות נתונים כמו 'תיקיית משנה' (אם יש), המיקום של הקובץ בארכיון ב-Cloud Storage (קטגוריה + שם הקובץ), סוג ה-MIME של הקובץ, גודל הקובץ (במקור בבייטים, אבל מומר לקילובייטים באמצעות k_ize()) ומחרוזת התוויות של Cloud Vision. שימו לב גם שהמיקום בארכיון הוא היפר-קישור, כך שהמנהל יוכל ללחוץ עליו כדי לוודא שהגיבוי בוצע בצורה בטוחה.

אחרי שמוסיפים את בלוק הקוד שלמעלה מיד אחרי הצגת התוצאות מ-Cloud Vision, החלק העיקרי שמפעיל את האפליקציה הושלם, למרות שהמבנה שלו קצת מורכב:

if __name__ == '__main__':
    # download img file & info from Drive
    rsp = drive_get_img(FILE)
    if rsp:
        fname, mtype, ftime, data = rsp
        print('Downloaded %r (%s, %s, size: %d)' % (fname, mtype, ftime, len(data)))

        # upload file to GCS
        gcsname = '%s/%s'% (PARENT, fname)
        rsp = gcs_blob_upload(gcsname, BUCKET, data, mtype)
        if rsp:
            print('Uploaded %r to GCS bucket %r' % (rsp['name'], rsp['bucket']))

            # process w/Vision
            rsp = vision_label_img(base64.b64encode(data).decode('utf-8'))
            if rsp:
                print('Top %d labels from Vision API: %s' % (TOP, rsp))

                # push results to Sheet, get cells-saved count
                fsize = k_ize(len(data))
                row = [PARENT,
                        '=HYPERLINK("storage.cloud.google.com/%s/%s", "%s")' % (
                        BUCKET, gcsname, fname), mtype, ftime, fsize, rsp
                ]
                rsp = sheet_append_row(SHEET, row)
                if rsp:
                    print('Updated %d cells in Google Sheet' % rsp)
                else:
                    print('ERROR: Cannot write row to Google Sheets')
            else:
                print('ERROR: Vision API cannot analyze %r' % fname)
        else:
            print('ERROR: Cannot upload %r to Cloud Storage' % gcsname)
    else:
        print('ERROR: Cannot download %r from Drive' % fname)

אם תמחקו את storage.json בפעם האחרונה ותפעילו מחדש את האפליקציה המעודכנת, תקבלו פלט דומה לזה שמופיע בהמשך, עם הערה על הוספת ניתוח של Cloud Vision:

$ python3 analyze_gsimg.py

    . . .

Authentication successful.
Downloaded 'section-work-card-img_2x.jpg' (image/jpeg, 2020-02-27T09:27:22.095Z, size: 27781)
Uploaded 'analyzed_imgs/section-work-card-img_2x.jpg' to GCS bucket 'vision-demo'
Top 5 labels from Vision API: (89.94%) Sitting, (86.09%) Interior design, (82.08%) Furniture, (81.52%) Table, (80.85%) Room
Updated 6 cells in Google Sheet

השורה הנוספת של הפלט שימושית, אבל כדי לראות אותה בצורה טובה יותר, כדאי להציץ בגיליון האלקטרוני המעודכן ב-Google Sheets, שבו השורה האחרונה (שורה 7 בדוגמה שלמטה) נוספה למערך הנתונים הקיים שהוסף קודם:

b53a5bc944734652.png

סיכום

ב-3 השלבים הראשונים של המדריך הזה, התחברתם לממשקי API של Google Workspace ו-Google Cloud כדי להעביר נתונים ולנתח אותם. זה מייצג 80% מכל העבודה. אבל בסופו של דבר, כל זה לא שווה אם אתם לא יכולים להציג להנהלה את כל מה שהשגתם. כדי להמחיש את התוצאות בצורה טובה יותר, כדאי לסכם את כל התוצאות בדוח שנוצר.

כדי לשפר עוד יותר את התועלת של הניתוח, בנוסף לכתיבת התוצאות בגיליון אלקטרוני, אפשר להוסיף לאינדקס את 5 התוויות המובילות האלה לכל תמונה, כדי ליצור מסד נתונים פנימי שיאפשר לעובדים מורשים לשלוח שאילתות לתמונות לפי צוות חיפוש. אבל אנחנו משאירים את זה כתרגיל לקוראים.

בשלב הזה, התוצאות שלנו נמצאות בגיליון אלקטרוני וזמינות להנהלה. הקוד של האפליקציה בשלב הזה צריך להיות זהה למה שמופיע במאגר בכתובתstep4-sheets/analyze_gsimg.py. השלב האחרון הוא לנקות את הקוד ולהפוך אותו לסקריפט שניתן לשימוש.

12. *השלב האחרון: שינוי מבנה הקוד

(אופציונלי) טוב שיש אפליקציה שעובדת, אבל אפשר לשפר אותה? כן, במיוחד האפליקציה הראשית שנראית כמו בלגן אחד גדול. נכניס את זה לפונקציה משלו ונאפשר קלט של משתמשים במקום קבועים קבועים. נעשה זאת באמצעות המודול argparse. בנוסף, נפתח כרטיסייה בדפדפן אינטרנט כדי להציג את הגיליון האלקטרוני אחרי שנכתוב בו את שורת הנתונים. אפשר לעשות את זה באמצעות מודול webbrowser. משלבים את הייבוא הזה עם הייבוא האחר, כך שהייבוא העליון ייראה כך:

from __future__ import print_function
import argparse
import base64
import io
import webbrowser

כדי להשתמש בקוד הזה באפליקציות אחרות, אנחנו צריכים אפשרות להשבית את הפלט. לשם כך, נוסיף את הדגל DEBUG כדי שזה יקרה, ונוסיף את השורה הזו לסוף הקטע של הקבועים בחלק העליון:

DEBUG = False

ועכשיו, לגבי הגוף העיקרי. במהלך בניית הדוגמה הזו, הייתם אמורים להתחיל להרגיש 'אי נוחות' כשהקוד שלנו מוסיף עוד רמה של קינון עם כל שירות שנוסף. אם הרגשתם כך, אתם לא לבד. התוסף הזה מגדיל את מורכבות הקוד, כפי שמתואר בפוסט הזה בבלוג של Google Testing.

בהתאם לשיטה המומלצת הזו, נארגן מחדש את החלק העיקרי של האפליקציה לפונקציה ונשתמש ב-return בכל 'נקודת עצירה' במקום להשתמש בקינון (החזרת None אם אחד מהשלבים נכשל והחזרת True אם כולם מצליחים):

def main(fname, bucket, sheet_id, folder, top, debug):
    '"main()" drives process from image download through report generation'

    # download img file & info from Drive
    rsp = drive_get_img(fname)
    if not rsp:
        return
    fname, mtype, ftime, data = rsp
    if debug:
        print('Downloaded %r (%s, %s, size: %d)' % (fname, mtype, ftime, len(data)))

    # upload file to GCS
    gcsname = '%s/%s'% (folder, fname)
    rsp = gcs_blob_upload(gcsname, bucket, data, mtype)
    if not rsp:
        return
    if debug:
        print('Uploaded %r to GCS bucket %r' % (rsp['name'], rsp['bucket']))

    # process w/Vision
    rsp = vision_label_img(base64.b64encode(data).decode('utf-8'))
    if not rsp:
        return
    if debug:
        print('Top %d labels from Vision API: %s' % (top, rsp))

    # push results to Sheet, get cells-saved count
    fsize = k_ize(len(data))
    row = [folder,
            '=HYPERLINK("storage.cloud.google.com/%s/%s", "%s")' % (
            bucket, gcsname, fname), mtype, ftime, fsize, rsp
    ]
    rsp = sheet_append_row(sheet_id, row)
    if not rsp:
        return
    if debug:
        print('Added %d cells to Google Sheet' % rsp)
    return True

הוא מסודר יותר ומאורגן יותר, ולא משאיר אחריו את התחושה של שרשרת if-else רקורסיבית, וגם מפחית את מורכבות הקוד כפי שמתואר למעלה. החלק האחרון בפאזל הוא ליצור נהג ראשי 'אמיתי', שיאפשר התאמה אישית של המשתמש ויצמצם את הפלט (אלא אם רוצים אחרת):

if __name__ == '__main__':
    # args: [-hv] [-i imgfile] [-b bucket] [-f folder] [-s Sheet ID] [-t top labels]
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--imgfile", action="store_true",
            default=FILE, help="image file filename")
    parser.add_argument("-b", "--bucket_id", action="store_true",
            default=BUCKET, help="Google Cloud Storage bucket name")
    parser.add_argument("-f", "--folder", action="store_true",
            default=PARENT, help="Google Cloud Storage image folder")
    parser.add_argument("-s", "--sheet_id", action="store_true",
            default=SHEET, help="Google Sheet Drive file ID (44-char str)")
    parser.add_argument("-t", "--viz_top", action="store_true",
            default=TOP, help="return top N (default %d) Vision API labels" % TOP)
    parser.add_argument("-v", "--verbose", action="store_true",
            default=DEBUG, help="verbose display output")
    args = parser.parse_args()

    print('Processing file %r... please wait' % args.imgfile)
    rsp = main(args.imgfile, args.bucket_id,
            args.sheet_id, args.folder, args.viz_top, args.verbose)
    if rsp:
        sheet_url = 'https://docs.google.com/spreadsheets/d/%s/edit' % args.sheet_id
        print('DONE: opening web browser to it, or see %s' % sheet_url)
        webbrowser.open(sheet_url, new=1, autoraise=True)
    else:
        print('ERROR: could not process %r' % args.imgfile)

אם כל השלבים יסתיימו בהצלחה, הסקריפט יפעיל דפדפן אינטרנט לגיליון האלקטרוני שצוין, שבו נוספה שורת הנתונים החדשה.

סיכום

אין צורך למחוק את storage.json כי לא בוצעו שינויים בהיקף. אם מריצים מחדש את האפליקציה המעודכנת, נפתח חלון דפדפן חדש עם הגיליון האלקטרוני שעבר שינוי, פחות שורות של פלט, ואם מזינים את האפשרות -h, המשתמשים יכולים לראות את האפשרויות שלהם, כולל -v כדי לשחזר את שורות הפלט שהוסתרו קודם:

$ python3 analyze_gsimg.py
Processing file 'section-work-card-img_2x.jpg'... please wait
DONE: opening web browser to it, or see https://docs.google.com/spreadsheets/d/SHEET_ID/edit

$ python3 analyze_gsimg.py -h
usage: analyze_gsimg.py [-h] [-i] [-t] [-f] [-b] [-s] [-v]

optional arguments:
  -h, --help       show this help message and exit
  -i, --imgfile    image file filename
  -t, --viz_top    return top N (default 5) Vision API labels
  -f, --folder     Google Cloud Storage image folder
  -b, --bucket_id  Google Cloud Storage bucket name
  -s, --sheet_id   Google Sheet Drive file ID (44-char str)
  -v, --verbose    verbose display output

האפשרויות האחרות מאפשרות למשתמשים לבחור שמות שונים של קובצי Drive, שמות של 'תיקיות משנה' וקטגוריות ב-Cloud Storage, את התוצאות הראשונות מתוך N התוצאות מ-Cloud Vision ומזהים של קובצי גיליון אלקטרוני (Sheets). אחרי העדכונים האחרונים, הגרסה הסופית של הקוד צריכה להיות זהה לגרסה שמופיעה ב-repo בכתובתfinal/analyze_gsimg.py וגם כאן, במלואה:

'''
analyze_gsimg.py - analyze Google Workspace image processing workflow

Download image from Google Drive, archive to Google Cloud Storage, send
to Google Cloud Vision for processing, add results row to Google Sheet.
'''

from __future__ import print_function
import argparse
import base64
import io
import webbrowser

from googleapiclient import discovery, http
from httplib2 import Http
from oauth2client import file, client, tools

k_ize = lambda b: '%6.2fK' % (b/1000.) # bytes to kBs
FILE = 'YOUR_IMG_ON_DRIVE'
BUCKET = 'YOUR_BUCKET_NAME'
PARENT = ''     # YOUR IMG FILE PREFIX
SHEET = 'YOUR_SHEET_ID'
TOP = 5       # TOP # of VISION LABELS TO SAVE
DEBUG = False

# process credentials for OAuth2 tokens
SCOPES = (
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/devstorage.full_control',
    'https://www.googleapis.com/auth/cloud-vision',
    'https://www.googleapis.com/auth/spreadsheets',
)
store = file.Storage('storage.json')
creds = store.get()
if not creds or creds.invalid:
    flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
    creds = tools.run_flow(flow, store)

# create API service endpoints
HTTP = creds.authorize(Http())
DRIVE  = discovery.build('drive',   'v3', http=HTTP)
GCS    = discovery.build('storage', 'v1', http=HTTP)
VISION = discovery.build('vision',  'v1', http=HTTP)
SHEETS = discovery.build('sheets',  'v4', http=HTTP)


def drive_get_img(fname):
    'download file from Drive and return file info & binary if found'

    # search for file on Google Drive
    rsp = DRIVE.files().list(q="name='%s'" % fname,
            fields='files(id,name,mimeType,modifiedTime)'
    ).execute().get('files', [])

    # download binary & return file info if found, else return None
    if rsp:
        target = rsp[0]  # use first matching file
        fileId = target['id']
        fname = target['name']
        mtype = target['mimeType']
        binary = DRIVE.files().get_media(fileId=fileId).execute()
        return fname, mtype, target['modifiedTime'], binary


def gcs_blob_upload(fname, bucket, media, mimetype):
    'upload an object to a Google Cloud Storage bucket'

    # build blob metadata and upload via GCS API
    body = {'name': fname, 'uploadType': 'multipart', 'contentType': mimetype}
    return GCS.objects().insert(bucket=bucket, body=body,
            media_body=http.MediaIoBaseUpload(io.BytesIO(media), mimetype),
            fields='bucket,name').execute()


def vision_label_img(img, top):
    'send image to Vision API for label annotation'

    # build image metadata and call Vision API to process
    body = {'requests': [{
                'image':     {'content': img},
                'features': [{'type': 'LABEL_DETECTION', 'maxResults': top}],
    }]}
    rsp = VISION.images().annotate(body=body).execute().get('responses', [{}])[0]

    # return top labels for image as CSV for Sheet (row)
    if 'labelAnnotations' in rsp:
        return ', '.join('(%.2f%%) %s' % (
                label['score']*100., label['description']) \
                for label in rsp['labelAnnotations'])


def sheet_append_row(sheet, row):
    'append row to a Google Sheet, return #cells added'

    # call Sheets API to write row to Sheet (via its ID)
    rsp = SHEETS.spreadsheets().values().append(
            spreadsheetId=sheet, range='Sheet1',
            valueInputOption='USER_ENTERED', body={'values': [row]}
    ).execute()
    if rsp:
        return rsp.get('updates').get('updatedCells')


def main(fname, bucket, sheet_id, folder, top, debug):
    '"main()" drives process from image download through report generation'

    # download img file & info from Drive
    rsp = drive_get_img(fname)
    if not rsp:
        return
    fname, mtype, ftime, data = rsp
    if debug:
        print('Downloaded %r (%s, %s, size: %d)' % (fname, mtype, ftime, len(data)))

    # upload file to GCS
    gcsname = '%s/%s'% (folder, fname)
    rsp = gcs_blob_upload(gcsname, bucket, data, mtype)
    if not rsp:
        return
    if debug:
        print('Uploaded %r to GCS bucket %r' % (rsp['name'], rsp['bucket']))

    # process w/Vision
    rsp = vision_label_img(base64.b64encode(data).decode('utf-8'), top)
    if not rsp:
        return
    if debug:
        print('Top %d labels from Vision API: %s' % (top, rsp))

    # push results to Sheet, get cells-saved count
    fsize = k_ize(len(data))
    row = [folder,
            '=HYPERLINK("storage.cloud.google.com/%s/%s", "%s")' % (
            bucket, gcsname, fname), mtype, ftime, fsize, rsp
    ]
    rsp = sheet_append_row(sheet_id, row)
    if not rsp:
        return
    if debug:
        print('Added %d cells to Google Sheet' % rsp)
    return True


if __name__ == '__main__':
    # args: [-hv] [-i imgfile] [-b bucket] [-f folder] [-s Sheet ID] [-t top labels]
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--imgfile", action="store_true",
            default=FILE, help="image file filename")
    parser.add_argument("-b", "--bucket_id", action="store_true",
            default=BUCKET, help="Google Cloud Storage bucket name")
    parser.add_argument("-f", "--folder", action="store_true",
            default=PARENT, help="Google Cloud Storage image folder")
    parser.add_argument("-s", "--sheet_id", action="store_true",
            default=SHEET, help="Google Sheet Drive file ID (44-char str)")
    parser.add_argument("-t", "--viz_top", action="store_true",
            default=TOP, help="return top N (default %d) Vision API labels" % TOP)
    parser.add_argument("-v", "--verbose", action="store_true",
            default=DEBUG, help="verbose display output")
    args = parser.parse_args()

    print('Processing file %r... please wait' % args.imgfile)
    rsp = main(args.imgfile, args.bucket_id,
            args.sheet_id, args.folder, args.viz_top, args.verbose)
    if rsp:
        sheet_url = 'https://docs.google.com/spreadsheets/d/%s/edit' % args.sheet_id
        print('DONE: opening web browser to it, or see %s' % sheet_url)
        webbrowser.open(sheet_url, new=1, autoraise=True)
    else:
        print('ERROR: could not process %r' % args.imgfile)

אנחנו עושים כל מאמץ כדי שהתוכן של המדריך הזה יהיה עדכני, אבל יהיו מקרים שבהם הגרסה העדכנית ביותר של הקוד תהיה במאגר.

13. מעולה!

ב-Codelab הזה יש הרבה מה ללמוד, ואתם הצלחתם לעבור אותו – אחד משיעורי ה-Codelab הארוכים יותר. כתוצאה מכך, הצלחתם להתמודד עם תרחיש אפשרי בארגון באמצעות כ-130 שורות של Python, תוך שימוש בכל היכולות של Google Cloud ו-Google Workspace, והעברת נתונים ביניהם כדי ליצור פתרון שעובד. מומלץ לעיין במאגר הקוד הפתוח של כל הגרסאות של האפליקציה הזו (מידע נוסף בהמשך).

הסרת המשאבים

  1. השימוש בממשקי API של Google Cloud כרוך בתשלום, אבל השימוש בממשקי API של Google Workspace כלול בדמי המינוי החודשיים ל-Google Workspace (משתמשים ב-Gmail לצרכנים לא משלמים דמי מינוי חודשיים). לכן, משתמשי Google Workspace לא צריכים לבצע ניקוי או השבתה של ממשקי API. ב-Google Cloud, אפשר להיכנס ללוח הבקרה במסוף Cloud ולבדוק את הכרטיס 'חיוב' כדי לראות את העלויות המשוערות.
  2. ב-Cloud Vision, מותר לבצע מספר קבוע של קריאות ל-API בחודש בחינם. לכן, כל עוד לא חורגים מהמגבלות האלה, אין צורך להשבית שום דבר וגם לא להשבית או למחוק את הפרויקט. מידע נוסף על החיוב והמכסה החינמית של Vision API זמין בדף התמחור שלו.
  3. חלק מהמשתמשים באחסון בענן מקבלים נפח אחסון חודשי בחינם. אם התמונות שאתם מאחסנים בארכיון באמצעות ה-codelab הזה לא גורמות לכם לחרוג מהמכסה הזו, לא תחויבו. מידע נוסף על החיוב ב-GCS ועל המכסה בחינם זמין בדף התמחור. אתם יכולים לראות ולמחוק בקלות בלובים מדפדפן Cloud Storage.
  4. יכול להיות שגם לשימוש שלכם ב-Google Drive יש מכסת אחסון, ואם חרגתם ממנה (או שאתם קרובים לחריגה), כדאי לכם להשתמש בכלי שיצרתם ב-codelab הזה כדי לארכב את התמונות ב-Cloud Storage, וכך לפנות יותר מקום ב-Drive. מידע נוסף על נפח האחסון ב-Google Drive זמין בדף המחירים המתאים למשתמשי Google Workspace Basic או למשתמשי Gmail/צרכנים.

לרוב התוכניות של Google Workspace Business ו-Enterprise יש נפח אחסון ללא הגבלה, אבל זה עלול לגרום לתיקיות ב-Drive להיות עמוסות מדי או אפילו להציף אתכם. האפליקציה שבניתם במדריך הזה היא דרך מצוינת לארכב קבצים מיותרים ולנקות את Google Drive.

גרסאות חלופיות

אמנם final/analyze_gsimg.py היא הגרסה הרשמית האחרונה שבה אנחנו עובדים במדריך הזה, אבל זה לא הסוף. בעיה אחת בגרסה הסופית של האפליקציה היא שהיא משתמשת בספריות אימות ישנות יותר שהוצאו משימוש. בחרנו בדרך הזו כי בזמן כתיבת המאמר הזה, ספריות האימות החדשות לא תמכו בכמה רכיבים מרכזיים: ניהול אחסון של טוקנים של OAuth ובטיחות שרשורים.

ספריות אימות עדכניות (חדשות יותר)

עם זאת, בשלב מסוים לא תהיה יותר תמיכה בספריות האימות הישנות, ולכן מומלץ לבדוק גרסאות שמשתמשות בספריות האימות החדשות (הנוכחיות) בתיקייה alt במאגר, גם אם הן לא בטוחות לשימוש עם שרשורים (אבל אפשר ליצור פתרון משלכם שכן בטוח לשימוש עם שרשורים). מחפשים קבצים עם *newauth* בשם שלהם.

ספריות לקוח של מוצרים ב-Google Cloud

‫Google Cloud ממליצה לכל המפתחים להשתמש בספריות הלקוח של המוצר כשמשתמשים ב-Google Cloud APIs. לצערנו, אין כרגע ספריות כאלה לממשקי API שאינם של Google Cloud. השימוש בספריות ברמה נמוכה יותר מאפשר שימוש עקבי ב-API ומשפר את הקריאות. בדומה להמלצה שלמעלה, גרסאות חלופיות שמשתמשות בספריות לקוח של מוצרי Google Cloud זמינות לעיון בתיקייה alt במאגר. מחפשים קבצים עם *-gcp* בשם שלהם.

הרשאה באמצעות חשבון שירות

כשעובדים בענן בלבד, בדרך כלל אין נתונים בבעלות משתמשים (אנושיים), ולכן חשבונות שירות והרשאות לחשבונות שירות משמשים בעיקר ב-Google Cloud. עם זאת, בדרך כלל משתמשים (בני אדם) הם הבעלים של מסמכי Google Workspace, ולכן במדריך הזה נעשה שימוש בהרשאת חשבון משתמש. זה לא אומר שאי אפשר להשתמש בממשקי API של Google Workspace עם חשבונות שירות. כל עוד לחשבונות האלה יש את רמת הגישה המתאימה, אפשר להשתמש בהם באפליקציות. בדומה לאמור למעלה, גרסאות חלופיות שמשתמשות בהרשאה של חשבון שירות זמינות לעיון בתיקייה alt במאגר. מחפשים קבצים עם *-svc* בשם שלהם.

קטלוג גרסאות חלופיות

בהמשך מפורטות כל הגרסאות החלופיות של final/analyze_gsimg.py, שלכל אחת מהן יש מאפיין אחד או יותר מהמאפיינים שצוינו למעלה. בשם הקובץ של כל גרסה, מחפשים את:

  • ‫"oldauth" לגרסאות שמשתמשות בספריות אימות ישנות יותר (בנוסף ל-final/analyze_gsimg.py)
  • ‫"newauth" למי שמשתמש בספריות האימות הנוכחיות או החדשות יותר
  • gcp" למשתמשים בספריות לקוח של מוצרי Google Cloud, כלומר google-cloud-storage וכו'.
  • svc למי שמשתמש באימות של חשבון שירות (svc acct) במקום בחשבון משתמש

אלה כל הגרסאות:

שם קובץ

תיאור

final/analyze_gsimg.py

הדוגמה הראשית; משתמשת בספריות האימות הישנות

alt/analyze_gsimg-newauth.py

זהה ל-final/analyze_gsimg.py אבל משתמש בספריות האימות החדשות יותר

alt/analyze_gsimg-oldauth-gcp.py

זהה ל-final/analyze_gsimg.py אבל משתמש בספריות הלקוח של מוצרי Google Cloud

alt/analyze_gsimg-newauth-gcp.py

זהה ל-alt/analyze_gsimg-newauth.py אבל משתמש בספריות הלקוח של מוצרי Google Cloud

alt/analyze_gsimg-oldauth-svc.py

זהה ל-final/analyze_gsimg.py אבל משתמש בחשבון שירות במקום בחשבון משתמש

alt/analyze_gsimg-newauth-svc.py

זהה ל-alt/analyze_gsimg-newauth.py אבל משתמש באימות של חשבון שירות במקום בחשבון משתמש

alt/analyze_gsimg-oldauth-svc-gcp.py

אותו דבר כמו alt/analyze_gsimg-oldauth-svc.py אבל משתמש בספריות הלקוח של מוצר Google Cloud, ואותו דבר כמו alt/analyze_gsimg-oldauth-gcp.py אבל משתמש באימות של חשבון שירות במקום בחשבון משתמש

alt/analyze_gsimg-newauth-svc-gcp.py

זהה ל-alt/analyze_gsimg-oldauth-svc-gcp.py אבל משתמש בספריות האימות החדשות יותר

יחד עם final/analyze_gsimg.py המקורי , יש לכם את כל השילובים האפשריים של הפתרון הסופי, בלי קשר לסביבת הפיתוח של Google API, ואתם יכולים לבחור את השילוב שהכי מתאים לצרכים שלכם. אפשר לקרוא הסבר דומה גם במאמר alt/README.md.

מחקר נוסף

ריכזנו כאן כמה רעיונות שיעזרו לכם להרחיב את התרגיל הזה. אפשר להרחיב את קבוצת הבעיות שהפתרון הנוכחי יכול לטפל בהן, וכך לבצע את השיפורים הבאים:

  1. (כמה תמונות בתיקיות) במקום לעבד תמונה אחת, נניח שיש לכם תמונות בתיקיות ב-Google Drive.
  2. (מספר תמונות בקובצי ZIP) במקום תיקייה של תמונות, אפשר להשתמש בארכיוני ZIP שמכילים קובצי תמונות. אם משתמשים ב-Python, כדאי להשתמש במודול zipfile.
  3. ‫(analyze Vision labels) קיבוץ תמונות דומות, אפשר להתחיל בחיפוש התוויות הנפוצות ביותר, אחר כך התוויות הנפוצות פחות וכן הלאה.
  4. (יצירת תרשימים) המשך השיחה מספר 3, יצירת תרשימים באמצעות Sheets API על סמך הניתוח והסיווג של Vision API
  5. (סיווג מסמכים) במקום לנתח תמונות באמצעות Cloud Vision API, נניח שיש לכם קובצי PDF שאתם רוצים לסווג באמצעות Cloud Natural Language API. באמצעות הפתרונות שלמעלה, קובצי ה-PDF האלה יכולים להיות בתיקיות ב-Drive או בארכיוני ZIP ב-Drive.
  6. (יצירת מצגות) שימוש ב-Slides API כדי ליצור מצגת מהתוכן של דוח Google Sheets. כדי לקבל רעיונות, אפשר לקרוא את הפוסט הזה בבלוג ולצפות בסרטון בנושא יצירת שקפים מנתונים בגיליון אלקטרוני.
  7. (ייצוא כ-PDF) ייצוא של הגיליון האלקטרוני או של המצגת כ-PDF, אבל זו לא תכונה של ממשקי ה-API של Sheets או של Slides. הערה: Google Drive API. נקודות בונוס: אפשר למזג את קובצי ה-PDF של Sheets ו-Slides לקובץ PDF ראשי אחד באמצעות כלים כמו Ghostscript ‏ (Linux‏, Windows) או Combine PDF Pages.action ‏ (Mac OS X).

מידע נוסף

Codelabs

כללי

Google Workspace

Google Cloud

רישיון

העבודה הזו בשימוש במסגרת רישיון Creative Commons שמותנה בייחוס כללי מגרסה 2.0.