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

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

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

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

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

מה תלמדו

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

למה תזדקק?

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

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

סקר

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

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

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

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

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

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

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

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

האם ברצונך לראות יותר מודעות 'עסקיות' Codelabs לעומת אלה מהם מבוא לפיצ'רים של המוצר?

כן לא יותר משניהם

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

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

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Project name הוא השם המוצג של המשתתפים בפרויקט. זו מחרוזת תווים שלא משמשת את Google APIs. אפשר לעדכן אותו בכל שלב.
  • Project ID חייב להיות ייחודי בכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר מחרוזת ייחודית באופן אוטומטי; בדרך כלל לא מעניין אותך מה זה. ברוב ה-Codelabs תצטרכו להפנות אל מזהה הפרויקט (בדרך כלל הוא מזוהה כ-PROJECT_ID). אם המזהה שנוצר לא מוצא חן בעיניך, יש לך אפשרות ליצור מזהה אקראי אחר. לחלופין, אפשר לנסות תבנית משלך ולבדוק אם היא זמינה. לא ניתן לשנות אותו אחרי השלב הזה, והוא יישאר למשך הפרויקט.
  • לידיעתך, יש ערך שלישי – Project Number (מספר פרויקט), שחלק מממשקי ה-API משתמשים בו. מידע נוסף על כל שלושת הערכים האלה זמין במסמכי התיעוד.
  1. בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי API של Cloud. מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. כדי להשבית את המשאבים ולא לצבור חיובים מעבר למדריך הזה, אתם יכולים למחוק את המשאבים שיצרתם או למחוק את הפרויקט כולו. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.

הפעלת Cloud Shell

סיכום

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

הפעלת Cloud Shell

  1. במסוף Cloud, לוחצים על 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 כ-pseudocode). באופן ספציפי, ה-Codelab הזה תומך ב-Python 2 וב-Python 2, אבל אנחנו ממליצים לעבור ל-3.x בהקדם האפשרי.

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

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

ב-IPython יש עדיפות למפענח Python 3, אבל הוא יחזור ל-Python 2 אם אין גרסה 3.x. אפשר לגשת ל-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 אם הן שונות באופן משמעותי מ- 3.x.

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

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

virtualenv my_env

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

virtualenv -p python3 my_env

צריך להיכנס ל-Virtualenv החדש שנוצר על ידי "Activate" (הפעלה) כך:

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

ברגע שניתן להריץ את הייבוא "test" הפקודה הושלמה בהצלחה (ללא שגיאות/פלט), אתם מוכנים להתחיל לדבר עם ממשקי ה-API של Google!

סיכום

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

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

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

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

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

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

כדי לקבל פרטי כניסה של OAuth2 להרשאות משתמש, חוזרים למנהל ה-API ובוחרים באפשרות 'פרטי כניסה' שבתפריט הניווט הימני:

635af008256d323.png

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

fd2f4133b406d572.png

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

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

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

b17b663668e38787.png

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

4e0b967c9d70d262.png

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

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

f17e97b30d994b0c.png

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

b107ab81349bdad2.png

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

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

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

5ddd365ac0af1e34.png

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

המערכת שומרת את פרטי הכניסה שלך

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

8bec84d82cb104d7.png

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

סיכום

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

6. הפעלת Google APIs

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

ללא קשר ל-Google API שבו אתם רוצים להשתמש באפליקציה, עליכם להפעיל אותם. את ממשקי ה-API אפשר להפעיל משורת הפקודה או ממסוף Cloud. תהליך ההפעלה של ממשקי API זהה, לכן לאחר שמפעילים ממשק 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: הפעלה של פלטפורמת המחשוב ללא שרת (serverless) של 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: שאילתה לגבי ממשקי ה-API של Google שאפשר להפעיל בפרויקט שלכם

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

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

gcloud services list

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

אפשרות 2: מסוף Cloud

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

df4a0a5e00d29ffc.png

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

5fcf10e5a05cfb97.png

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

eef4e5e863f4db66.png

לחלופין, עוברים לסרגל הניווט הימני ובוחרים באפשרות APIs & שירותיםספרייה:

6eda5ba145b30b97.png

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

5d4f1c8e7cf8df28.png

צריך להזין שם API שרוצים לחפש ולראות תוצאות תואמות:

35bc4b9cf72ce9a4.png

בוחרים את ממשק ה-API שרוצים להפעיל ולוחצים על הלחצן Enable (הפעלה):

9574a69ef8d9e8d2.png

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

עלות

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

משתמשים חדשים ב-Google Cloud זכאים לתקופת הניסיון בחינם, שנכון לעכשיו היא 1,200 ש"ח למשך 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
  2. אפשר להתחיל חיפוש על ידי הקלדת כמה אותיות מהשם שלו
  3. בוחרים את ה-API הרצוי.
  4. הפעלה

מחלצים, מנקים וחוזרים על הפעולה. ב-Cloud Storage יש כמה אפשרויות: בחירה ב-'Google Cloud Storage JSON API'. בנוסף, ב-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 תואמת ל-Python 2-3, והייבוא של ספריית Google כולל את כל הכלים הדרושים לתקשורת עם Google APIs.
  • המשתנה 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

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

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

a122eb7468d0d34e.png

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

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

bf171080dcd6ec5.png

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

מ-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 רגילה, אבל זה דבר מסורבל יותר כי אנחנו נמשיך לפתח את האפליקציה שלב אחרי שלב.

נניח שהאפליקציה שלכם קיבלה הרשאה ונוצרה נקודת קצה (endpoint) של שירות 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 משמש כדי לציין את הערכים המוחזרים שמעניינים אתכם – למה לטרוח להחזיר את הכול ולהאט אם לא חשובים לכם הערכים האחרים? אם זו הפעם הראשונה שאתם משתמשים במסכות שדות לסינון ערכי החזרה של API, עיינו בפוסט הזה בבלוג וידאו. אחרת, מריצים את השאילתה ומוצאים את המאפיין files שהוחזר, כשברירת המחדל היא מערך רשימה ריק אם אין התאמות.

אם אין תוצאות, המערכת מדלגת על שאר הפונקציה ומוחזרת None (באופן מרומז). לחלופין, אפשר לשלוף את התגובה התואמת הראשונה (rsp[0]), להחזיר את שם הקובץ, את סוג ה-MIME שלו, את חותמת הזמן של השינוי האחרון, ולבסוף את המטען הייעודי (payload) הבינארי, שאוחזר על ידי הפונקציה 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)

פתרון בעיות

אם לא הצלחתם להשיג את התוצאות שצוינו למעלה, יכולות להיות לכך כמה סיבות, למשל:

סיכום

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

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

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

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

from __future__ import print_function                   
import io

בנוסף לשם הקובץ ב-Drive, אנחנו זקוקים למידע על מיקום הקובץ הזה ב-Cloud Storage, ובמיוחד לשם ה"קטגוריה" אתם תכניסו אותה לכל "תיקיית הורה", קידומות. עוד רגע בנושא הזה:

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

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

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

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

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

עכשיו אפשר ליצור ל-Cloud Storage נקודת קצה (endpoint) של שירות, מתחת לנקודת הקצה של 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/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',
)                  

עכשיו יוצרים נקודת קצה (endpoint) של שירות ל-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)

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

אם אתם מפתחים ויכולים לשלוח קריאה ל-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() שממירה בייטים לקילובייט (KB), ומגדירה אותה כ-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',
)                  

עכשיו יוצרים נקודת קצה (endpoint) של שירות ל-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() מחייבת את מזהה הקובץ של Sheets, טווח של תאים, אופן הזנת הנתונים והנתונים עצמם. מזהה הקובץ הוא פשוט, טווח התאים מצוין בסימון 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). בעקבות העדכונים האחרונים האלה, הגרסה הסופית של הקוד אמורה עכשיו להתאים במלואה למאגר שבמאגר 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 הזה, והצלחתם להשיג את זה, גם אחרי ששרדתם את אחת משיעורי ה-Codelabs הארוכים יותר. כתוצאה מכך, התמודדתם עם תרחיש ארגוני אפשרי שכולל כ-130 שורות של Python, תוך שימוש בכל Google Cloud ו-Google Workspace והעברת נתונים ביניהן כדי לפתח פתרון עבודה. אפשר לעיין במאגר הקוד הפתוח שמיועד לכל הגרסאות של האפליקציה הזו (מידע נוסף מופיע בהמשך).

הסרת המשאבים

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

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

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

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

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

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

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

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

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

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

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

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

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

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

שם קובץ

תיאור

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, אבל נעשה שימוש בחשבון svc במקום בחשבון משתמש

alt/analyze_gsimg-newauth-svc.py

כמו alt/analyze_gsimg-newauth.py, אבל נעשה שימוש באימות svc לחשבון במקום בחשבון משתמש

alt/analyze_gsimg-oldauth-svc-gcp.py

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

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. (ניתוח תוויות חזותיות) ניתן לקבץ יחד תמונות דומות, אולי כדאי להתחיל בחיפוש של התוויות הנפוצות ביותר, לאחר מכן התווית השנייה הנפוצה ביותר וכן הלאה.
  4. (יצירת תרשימים) מעקב מס' 3, יצירת תרשימים באמצעות Sheets API על סמך הניתוח והסיווג של Vision API
  5. (סיווג מסמכים) במקום לנתח תמונות באמצעות Cloud Vision API, נניח שיש לכם קובצי PDF לסווג באמצעות Cloud Natural Language API. בעזרת הפתרונות שלמעלה, קובצי ה-PDF האלה יכולים להיות בתיקיות Drive או בארכיוני ZIP ב-Drive.
  6. (ליצור מצגות) משתמשים ב-API של Slides כדי ליצור מצגת מכירות מהתוכן של הדוח ב-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 Attribution 2.0 גנרי.