העברת תמונות לארכיון, ניתוח ויצירת דוחות 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 עם חשבון פעיל לחיוב ב-Google Cloud
  • היכרות עם פקודות מסוף/מעטפת של מערכת הפעלה
  • מיומנויות בסיסיות ב-Python (גרסה 2 או 3), אבל אפשר להשתמש בכל שפה נתמכת

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

סקר

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

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

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

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

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

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

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

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

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

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

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

הגדרת סביבה בקצב עצמי

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.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 2 ל-Python 3.x, נספק הוראות ספציפיות ל-Python 2.

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

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

virtualenv my_env

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

virtualenv -p python3 my_env

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

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.
*******************************************************************************

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

סיכום

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

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

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

מבוא להרשאה (בתוספת אימות)

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

ממשקי Google API תומכים בכמה סוגים של הרשאות, אבל הסוג הכי נפוץ עבור משתמשי G Suite API הוא הרשאת משתמש, כי האפליקציה לדוגמה ב-codelab הזה ניגשת לנתונים ששייכים למשתמשי קצה. משתמשי הקצה האלה צריכים להעניק הרשאה לאפליקציה שלכם לגשת לנתונים שלהם. המשמעות היא שהקוד צריך לקבל פרטי כניסה של 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

אנחנו מפתחים כלי לשורת פקודה, שהוא אחר, אז בוחרים באפשרות הזו ולוחצים על הלחצן יצירה. בוחרים שם למזהה הלקוח שמשקף את האפליקציה שיוצרים, או פשוט משתמשים בשם ברירת המחדל, שהוא בדרך כלל 'לקוח אחר 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 הזה.

סיכום

עכשיו אפשר להפעיל את ממשקי ה-API של Google שבהם נעשה שימוש ב-codelab הזה. בנוסף, בחרנו את השם 'Vision API demo' לאפליקציה במסך ההסכמה ל-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 Console היא פעולה נפוצה יותר, אבל יש מפתחים שמעדיפים לעשות הכול משורת הפקודה. כדי לעשות זאת, צריך לחפש את 'שם השירות' של ה-API. נראה שזו כתובת URL: SERVICE_NAME.googleapis.com. אפשר למצוא אותם בטבלת המוצרים הנתמכים, או לבצע שאילתה לגביהם באופן פרוגרמטי באמצעות Google Discovery API.

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

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

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

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

לפני שמפעילים API כלשהו, חשוב לעיין בפרטי התמחור שלו (לדוגמה, בדף התמחור של Cloud Vision 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 JSON API. ממשק Cloud Storage API יצפה גם לחשבון לחיוב פעיל.

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

זהו קטע קוד בינוני, ולכן כדאי לפעול לפי שיטות Agile כדי לוודא שיש תשתית משותפת, יציבה ופעילה לפני שמתחילים לעבוד על האפליקציה הראשית. מוודאים ש-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 מספק אחסון של כתובות Blob אמורפיות. כשמעלים לשם קבצים, המערכת לא מבינה את המושג של סוגי קבצים, סיומות וכו', כמו ב-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, כך שהפונקציה 'main' כולה תיראה כך:

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 שבהם אפשר להשתמש כדי לנתח את הנתונים. מידע על שאר האפשרויות זמין כאן. הקוד שלכם צריך להיות זהה לקוד שמופיע ב-repo בכתובת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() שממירה בייטים לקילובייטים, והגדרנו אותה כ-Python lambda כי היא פשוטה וכוללת רק שורה אחת. השילוב של שתי הקבועים האלה נראה כך:

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 ו-Cloud APIs כדי להעביר נתונים ולנתח אותם, וזה מייצג 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). אחרי העדכונים האחרונים האלה, הגרסה הסופית של הקוד צריכה להיות זהה למה שמופיע במאגר בכתובת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. חלק מהמשתמשים ב-Cloud Storage מקבלים נפח אחסון חינם מדי חודש. אם התמונות שאתם מאחסנים בארכיון באמצעות ה-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. (ניתוח תוויות של Vision) קיבוץ תמונות דומות, אולי כדאי להתחיל בחיפוש התוויות הנפוצות ביותר, ואז התוויות הנפוצות הבאות, וכן הלאה.
  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 כללי.