封存、分析及生成 Google Workspace 的圖片Google Cloud

1. 總覽

本程式碼研究室設想了可能的企業工作流程:圖片歸檔、分析及報表生成。假設貴機構有一系列圖片,佔用受限資源的空間。您想要封存這些資料、分析這些圖片,最重要的是,產生一份報告,彙整封存的位置資訊和分析結果,供管理階層使用。Google Cloud 提供相關工具,可運用 Google Workspace (舊稱 G Suite 或 Google 應用程式) 和 Google Cloud (舊稱 GCP) 這兩大產品線的 API,達成上述目標。

在本情境中,商家使用者會在 Google 雲端硬碟中存放圖片。因此,建議將這些資料備份到「較冷」的低價儲存空間,例如 Google Cloud Storage 提供的儲存空間級別Google Cloud Vision 可讓開發人員輕鬆在應用程式中整合視覺偵測功能,包括偵測物件和地標、光學字元辨識 (OCR) 等。最後,Google 試算表是實用的視覺化工具,可將所有資訊彙整成摘要,方便您向主管匯報。

完成本程式碼研究室,建構充分運用 Google Cloud 的解決方案後,我們希望您能受到啟發,為貴機構或客戶打造更具影響力的產品。

課程內容

  • 如何使用 Cloud Shell
  • 如何驗證 API 要求
  • 如何安裝 Python 適用的 Google API 用戶端程式庫
  • 如何啟用 Google API
  • 如何從 Google 雲端硬碟下載檔案
  • 如何將物件/blob 上傳至 Cloud Storage
  • 如何使用 Cloud Vision 分析資料
  • 如何將資料列寫入 Google 試算表

軟硬體需求

  • Google 帳戶 (Google Workspace 帳戶可能需要管理員核准)
  • 具備有效 Google Cloud 帳單帳戶的 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 API 未使用的字元字串。你隨時可以更新該位置資訊。
  • 專案 ID 在所有 Google Cloud 專案中不得重複,且設定後即無法變更。Cloud 控制台會自動產生不重複的字串,通常您不需要在意這個字串。在大多數程式碼研究室中,您需要參照專案 ID (通常會標示為 PROJECT_ID)。如果您不喜歡產生的 ID,可以產生另一個隨機 ID。你也可以嘗試自訂名稱,看看是否可用。完成這個步驟後就無法變更,且專案期間都會維持這個設定。
  • 請注意,部分 API 會使用第三個值,也就是「專案編號」。如要進一步瞭解這三種值,請參閱說明文件
  1. 接著,您需要在 Cloud 控制台中啟用帳單,才能使用 Cloud 資源/API。完成本程式碼研究室的費用應該不高,甚至完全免費。如要關閉資源,避免產生本教學課程以外的費用,您可以刪除自己建立的資源,或刪除整個專案。Google Cloud 新使用者可參加價值$300 美元的免費試用計畫。

啟動 Cloud Shell

摘要

您可以在筆電上開發程式碼,但本程式碼研究室的次要目標是教您如何使用 Google Cloud Shell。Cloud Shell 是透過新式網路瀏覽器在雲端執行的指令列環境。

啟用 Cloud Shell

  1. 在 Cloud 控制台,點選「啟用 Cloud Shell」 圖示 853e55310c205094.png

55efc1aaa7a4d3ad.png

如果您是首次啟動 Cloud Shell,系統會顯示中繼畫面 (位於摺疊式選單下方),說明這個指令列環境。點選「繼續」後,這則訊息日後就不會再出現。以下是這個初次畫面的樣子:

9c92662c6a846a5c.png

佈建並連至 Cloud Shell 預計只需要幾分鐘。

9f0e51b578fecce5.png

這部虛擬機器搭載您需要的所有開發工具,並提供永久的 5GB 主目錄,而且可在 Google Cloud 運作,大幅提升網路效能並強化驗證功能。本程式碼研究室幾乎所有工作都可在瀏覽器或 Chromebook 上完成。

連線至 Cloud Shell 後,您應會發現自己通過驗證,且專案已設為您的專案 ID。

  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 環境

本程式碼研究室要求您使用 Python 語言 (不過 Google API 用戶端程式庫支援多種語言,因此您可以使用偏好的開發工具建構對等項目,並將 Python 視為虛擬程式碼)。具體來說,本程式碼研究室支援 Python 2 和 3,但我們建議盡快改用 3.x 版。

Cloud Shell 是使用者可直接透過 Cloud 控制台使用的便利工具,不需要本機開發環境,因此本教學課程完全可以在雲端透過網路瀏覽器完成。具體來說,Cloud Shell 已預先安裝這兩個版本的 Python,因此您不必擔心。

Cloud Shell 也安裝了 IPython,這是高階互動式 Python 解譯器,我們建議使用這個解譯器,尤其是數據資料學或機器學習社群的成員。如果是,IPython 是 Jupyter NotebooksColab (Google Research 代管的 Jupyter Notebooks) 的預設解譯器。

IPython 會優先使用 Python 3 解譯器,但如果沒有 3.x 版,就會改用 Python 2。您可以從 Cloud Shell 存取 IPython,也可以在本機開發環境中安裝。按下 ^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.
>>>

本程式碼研究室也假設您已安裝 pip 安裝工具 (Python 套件管理員和依附元件解析器)。這個套件會與 2.7.9 以上或 3.4 以上版本一併安裝。如果您的 Python 版本較舊,請參閱這篇指南瞭解安裝說明。視權限而定,您可能需要 sudo 或超級使用者存取權,但一般來說並非如此。您也可以明確使用 pip2pip3,針對特定 Python 版本執行 pip

本程式碼研究室的其餘部分會假設您使用 Python 3,如果 Python 2 與 3.x 的差異很大,我們會提供 Python 2 的具體操作說明。

[選用] 建立及使用虛擬環境

本節為選用內容,只有必須使用虛擬環境完成本程式碼研究室的學員 (如上方警告側欄所述),才需要閱讀本節。如果電腦上只有 Python 3,只要發出這個指令,即可建立名為 my_env 的 virtualenv (您也可以選擇其他名稱):

virtualenv my_env

不過,如果電腦上同時有 Python 2 和 3,建議您安裝 Python 3 virtualenv,方法是使用 -p flag,如下所示:

virtualenv -p python3 my_env

「啟用」新建立的 virtualenv,如下所示:

source my_env/bin/activate

確認您位於環境中,方法是觀察殼層提示是否已加上環境名稱,例如:

(my_env) $ 

現在您應該可以 pip install 任何必要套件,在這個環境中執行程式碼等。另一個好處是,如果您完全搞砸了,或遇到 Python 安裝毀損等情況,可以清除整個環境,不會影響系統的其他部分。

4. 安裝 Python 適用的 Google API 用戶端程式庫

本程式碼研究室需要使用 Python 適用的 Google API 用戶端程式庫,因此安裝程序很簡單,甚至可能完全不需要執行任何操作。

我們先前建議您考慮使用 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 API 通訊!

摘要

本程式碼研究室為中級課程,因此假設您已具備在主控台中建立及使用專案的經驗。如果您是 Google API 和 Google Workspace API 的新手,請先試試 Google Workspace API 入門程式碼研究室。此外,如果您知道如何建立 (或重複使用現有) 使用者帳戶 (不是 服務帳戶) 憑證,請將 client_secret.json 檔案放入工作目錄,略過下一個模組,然後跳到「啟用 Google API」。

5. *授權 API 要求 (使用者授權)

如果您已建立使用者帳戶授權憑證,且熟悉相關程序,可以略過這個部分。這與服務帳戶授權不同,後者的技術不同,請繼續閱讀下文。

授權簡介 (以及一些驗證)

如要向 API 發出要求,應用程式必須具備適當的授權驗證是類似的字詞,用來描述登入憑證。使用登入資訊和密碼登入 Google 帳戶時,您會驗證自己的身分。通過驗證後,下一步是判斷您 (更確切地說,是您的程式碼) 是否有權存取資料,例如 Cloud Storage 中的 Blob 檔案,或是 Google 雲端硬碟中的使用者個人檔案。

Google API 支援多種授權類型,但G Suite API 使用者最常使用使用者授權,因為本程式碼研究室中的範例應用程式會存取屬於使用者的資料。這些使用者必須授權您的應用程式存取他們的資料。也就是說,程式碼必須取得使用者帳戶的 OAuth2 憑證。

如要取得使用者授權的 OAuth2 憑證,請返回 API 管理工具,然後選取左側導覽列的「憑證」分頁:

635af008256d323.png

進入該頁面後,您會看到所有憑證,並分為三個部分:

fd2f4133b406d572.png

第一個是 API 金鑰,第二個是 OAuth 2.0 用戶端 ID,最後一個是 OAuth2 服務帳戶,我們使用的是中間那個。

建立憑證

在「憑證」頁面中,按一下頂端的「+ 建立憑證」按鈕,然後在隨即顯示的對話方塊中選擇「OAuth 用戶端 ID:」。

b17b663668e38787.png

在下一個畫面中,您可以執行 2 項操作:設定應用程式的授權「同意畫面」,以及選擇應用程式類型:

4e0b967c9d70d262.png

如果您尚未設定同意畫面,主控台會顯示警告,請立即設定。(如果已設定同意畫面,請略過後續步驟)。

點選「設定同意畫面」,然後選取「外部」應用程式 (如果您是 G Suite 客戶,請選取「內部」):

f17e97b30d994b0c.png

請注意,在本練習中,您選擇哪個選項都沒關係,因為您不會發布程式碼研究室範例。大多數人會選取「外部」,然後前往較複雜的畫面,但您只需要完成頂端的「應用程式名稱」欄位:

b107ab81349bdad2.png

目前您只需要應用程式名稱,因此請選擇反映您所進行程式碼研究室的名稱,然後按一下「儲存」

建立 OAuth 用戶端 ID (使用者帳戶授權)

現在返回「憑證」分頁,建立 OAuth2 用戶端 ID。您可以在這裡建立各種 OAuth 用戶端 ID:

5ddd365ac0af1e34.png

我們正在開發指令列工具,也就是「Other」(其他),因此請選擇該選項,然後按一下「Create」(建立) 按鈕。選擇反映您要建立應用程式的用戶端 ID 名稱,或直接使用預設名稱 (通常是「其他用戶端 N」)。

儲存憑證

  1. 系統會顯示含有新憑證的對話方塊,按一下「確定」即可關閉

8bec84d82cb104d7.png

  1. 返回「憑證」頁面,向下捲動至「OAuth 2. 0 用戶端 ID」部分,找到並點選新建立用戶端 ID 最右下方的下載圖示 f54b28417901b3aa.png1b4e8d248274a338.png
  2. 系統會開啟對話方塊,將名為 client_secret-LONG-HASH-STRING.apps.googleusercontent.com.json 的檔案儲存到「下載」資料夾。建議您將名稱縮短為較簡單的名稱 (例如 client_secret.json,這是範例應用程式使用的名稱),然後儲存到您要在本程式碼研究室中建立範例應用程式的目錄/資料夾。

摘要

現在,您已準備好啟用本程式碼研究室中使用的 Google API。此外,我們在 OAuth 同意畫面中選取「Vision API demo」做為應用程式名稱,因此您會在接下來的螢幕截圖中看到這個名稱。

6. 啟用 Google API

本程式碼研究室會使用 四個 (4) Google Cloud API,分別是 Google Cloud 的 Cloud Storage 和 Cloud Vision,以及 Google Workspace 的 Google 雲端硬碟和 Google 試算表。以下是啟用 Google API 的一般操作說明。瞭解如何啟用一個 API 後,其他 API 的啟用方式也大同小異。

無論您要在應用程式中使用哪個 Google API,都必須啟用。您可以從指令列或 Cloud 控制台啟用 API。啟用 API 的程序相同,因此啟用一個 API 後,您就能以類似方式啟用其他 API。

選項 1:gcloud 指令列介面 (Cloud Shell 或本機環境)

雖然透過 Cloud 控制台啟用 API 是較常見的做法,但有些開發人員偏好透過指令列完成所有作業。如要這麼做,您需要查詢 API 的「服務名稱」。這看起來像是網址:SERVICE_NAME.googleapis.com。您可以在「支援的產品圖表」中找到這些項目,也可以使用 Google Discovery API 以程式輔助方式查詢。

取得這項資訊後,您可以使用 Cloud Shell (或已安裝 gcloud 指令列工具的本機開發環境),按照下列步驟啟用 API 或服務:

gcloud services enable SERVICE_NAME.googleapis.com

範例 1:啟用 Cloud Vision API

gcloud services enable vision.googleapis.com

範例 2:啟用 Google App Engine 無伺服器運算平台

gcloud services enable appengine.googleapis.com

範例 3:透過單一要求啟用多個 API。舉例來說,如果本程式碼研究室的觀眾要使用 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 控制台

您也可以在 API 管理工具中啟用 Google API。在 Cloud Console 中,前往 API 管理工具。在這個資訊主頁頁面中,您會看到應用程式的一些流量資訊、顯示應用程式要求和應用程式產生錯誤的圖表,以及應用程式的回應時間:

df4a0a5e00d29ffc.png

這些圖表下方會列出專案已啟用的 Google API:

5fcf10e5a05cfb97.png

如要啟用 (或停用) API,請點選頂端的「啟用 API 和服務」

eef4e5e863f4db66.png

或者,前往左側導覽列,依序選取「API 和服務」→「程式庫」

6eda5ba145b30b97.png

無論採用哪種方式,都會前往「API 程式庫」頁面:

5d4f1c8e7cf8df28.png

輸入 API 名稱進行搜尋,並查看相符的結果:

35bc4b9cf72ce9a4.png

選取要啟用的 API,然後按一下「啟用」按鈕:

9574a69ef8d9e8d2.png

無論要使用哪個 Google API,啟用所有 API 的程序都差不多。

費用

許多 Google API 都能免費使用,但使用大部分 Google Cloud 產品和 API 時,則會產生費用。啟用 Cloud API 時,系統可能會要求您提供有效的帳單帳戶。不過,部分 Google Cloud 產品提供「一律免費」方案,您必須超出方案限制,才會產生帳單費用。

Google Cloud 新使用者可享有免費試用,目前提供價值 $300 美元的抵免額,有效期限為 90 天。Codelabs 通常不會產生任何費用,因此建議您先不要使用免費試用方案,等到真正準備好要試用時再啟用,因為這是一次性優惠。免費方案配額不會過期,無論是否使用免費試用方案,都適用這項配額。

啟用任何 API 前,使用者應先參考價格資訊 (例如:Cloud Vision API 價格 頁面),特別是注意是否有免費層級,以及免費層級的內容。只要您在指定每日或每月總計限制內,就不會產生任何費用。Google 產品群組 API 的價格和免費層級各不相同。範例:

不同 Google 產品的計費方式不同,請務必參閱相關說明文件瞭解詳情。

摘要

啟用 Cloud Vision 後,請以相同方式開啟其他三個 API (Google 雲端硬碟、Cloud Storage、Google 試算表)。在 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 相容,而匯入 Google 程式庫則會帶入與 Google API 通訊所需的所有工具。
  • SCOPES 變數代表要向使用者要求的權限,目前只有一個:讀取使用者 Google 雲端硬碟資料的權限
  • 憑證處理程式碼的其餘部分會讀取快取的 OAuth2 權杖,如果原始存取權杖已過期,可能會使用更新權杖更新為新的存取權杖。
  • 如果沒有建立權杖,或因其他原因無法擷取有效存取權杖,使用者必須完成 OAuth2 3LO 流程:建立對話方塊,要求權限並提示使用者接受。如果條件成立,應用程式會繼續執行;否則 tools.run_flow() 會擲回例外狀況,並停止執行。
  • 使用者授予權限後,系統會建立 HTTP 用戶端與伺服器通訊,並使用使用者的憑證簽署所有要求,確保安全無虞。接著,系統會使用該 HTTP 用戶端建立 Google Drive API (第 3 版) 的服務端點,然後指派給 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 雲端硬碟中的檔案。在應用程式程式碼中,這些權限範圍會顯示為 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 尚未建立),按照另一個瀏覽器分頁中該網址的指示操作,您會獲得與上述本機開發環境幾乎相同的體驗 (請參閱上方的螢幕截圖)。最後一個畫面會顯示驗證碼,請在 Cloud Shell 中輸入:

40a567cda0f31cc9.png

複製這段程式碼並貼到終端機視窗。

摘要

除了「Authentication successful」以外,您不會看到任何其他輸出內容。請注意,這只是設定,您尚未執行任何動作。您成功踏上旅程,可望在第一次執行時就獲得正確結果。(最棒的是,您只需要授權一次,後續執行都會略過這個步驟,因為系統已快取您的權限)。現在,我們讓程式碼執行一些實際工作,產生實際輸出內容。

疑難排解

如果收到錯誤訊息而非沒有輸出,可能是下列一或多個原因所致,例如:

8. 步驟 1:從 Google 雲端硬碟下載圖片

在上一個步驟中,我們建議您以 analyze_gsimg.py 形式建立程式碼,然後從該處編輯。您也可以直接將所有內容剪下並貼到 iPython 或標準 Python Shell 中,但這樣會比較麻煩,因為我們將繼續逐步建構應用程式。

假設您的應用程式已獲得授權,且已建立 API 服務端點。在程式碼中,這會以 DRIVE 變數表示。現在請在 Google 雲端硬碟中找出圖片檔案,然後

並設為名為 NAME 的變數。輸入該函式,以及步驟 0 中程式碼正下方的下列 drive_get_img() 函式:

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

Drive files() 集合具有 list() 方法,可對指定檔案執行查詢 (q 參數)。fields 參數用於指定您感興趣的回傳值,如果您不關心其他值,何必傳回所有值並拖慢速度?如果您不熟悉用於篩選 API 回傳值的欄位遮罩,請參閱這篇網誌文章和影片。否則請執行查詢並擷取傳回的 files 屬性,如果沒有相符項目,則預設為空白清單陣列。

如果沒有結果,系統會略過函式的其餘部分,並傳回 None (隱含)。否則,請擷取第一個相符的回應 (rsp[0]),傳回檔案名稱、MIME 類型、上次修改時間戳記,以及最後透過 get_media() 函式 (透過檔案 ID) 擷取的二進位酬載,同樣位於 files() 集合中。(其他語言用戶端程式庫的方法名稱可能略有不同)。

最後一部分是「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)))
    else:
        print('ERROR: Cannot download %r from Drive' % fname)

假設雲端硬碟中有一個名為 section-work-card-img_2x.jpg 的圖片,且已設為 FILE,成功執行指令碼後,您應該會看到輸出內容,確認指令碼能夠從雲端硬碟讀取檔案 (但不會儲存到電腦):

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

疑難排解

如果沒有看到上述成功輸出內容,可能是因為一或多個原因,例如:

摘要

在本節中,您學會如何 (透過 2 個不同的 API 呼叫) 連線至雲端硬碟 API,查詢特定檔案並下載。業務用途:封存雲端硬碟資料,並可能使用 Google Cloud 工具等分析資料。此階段的應用程式程式碼應與存放區step1-drive/analyze_gsimg.py中的程式碼相符。

如要進一步瞭解如何下載 Google 雲端硬碟中的檔案,請參閱這篇文章,或觀看這篇網誌文章和影片。本程式碼研究室的這部分與Google Workspace API 簡介程式碼研究室幾乎完全相同,但不會下載檔案,而是顯示使用者 Google 雲端硬碟中的前 100 個檔案/資料夾,並使用限制較嚴格的範圍。

9. 步驟 2:將檔案封存至 Cloud Storage

下一步是新增 Google Cloud Storage 支援。為此,我們需要匯入另一個 Python 套件 io。確認匯入內容的頂端部分如下所示:

from __future__ import print_function                   
import io

除了雲端硬碟檔案名稱,我們還需要一些資訊,瞭解要在 Cloud Storage 的哪個位置儲存這個檔案,具體來說,就是您要將檔案放入的「bucket」名稱,以及任何「父項資料夾」前置字串。稍後將詳細說明:

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

關於值區:Cloud Storage 提供非結構化 Blob 儲存空間。上傳檔案時,系統不會像 Google 雲端硬碟一樣,瞭解檔案類型、副檔名等概念。對 Cloud Storage 而言,這就只是一堆「blob」而已。此外,Cloud Storage 中沒有資料夾或子目錄的概念。

可以,您可以在檔案名稱中使用斜線 (/) 代表多個子資料夾的抽象概念,但最終所有 Blob 都會進入 Bucket,「/」只是檔案名稱中的字元。詳情請參閱值區和物件命名慣例頁面

上述步驟 1 要求的是雲端硬碟唯讀範圍。當時,這就是您需要的全部資訊。現在必須具備 Cloud Storage 的上傳 (讀取/寫入) 權限。將 SCOPES 從單一字串變數改為權限範圍的陣列 (Python 元組 [或清單]),如下所示:

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

現在請在 Drive 的服務端點下方,建立 Cloud Storage 的服務端點。請注意,我們稍微變更了呼叫,以重複使用相同的 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() 呼叫需要值區名稱、檔案中繼資料和二進位大型物件本身。如要篩除傳回值,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」的 bucket,並將「analyzed_imgs」做為「父項子目錄」。設定這些變數並再次執行指令碼後,section-work-card-img_2x.jpg 就會從雲端硬碟下載,然後上傳至 Cloud Storage,對吧?NOT!

$ 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">

仔細查看,雖然雲端硬碟下載成功,但上傳至 Cloud Storage 失敗。為什麼?

這是因為我們在步驟 1 授權這個應用程式時,只授權了 Google 雲端硬碟的唯讀存取權。雖然我們新增了 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 雲端硬碟和 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。使用 its 字串更新 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 和機器學習技術。

如果您是開發人員,且可以呼叫 API,就能使用機器學習技術。Cloud Vision 只是其中一項 API 服務,您還能使用其他服務分析資料。如要瞭解其他功能,請參閱這篇文章。您的程式碼現在應該與存放區step3-vision/analyze_gsimg.py中的程式碼相符。

11. 步驟 4:使用 Google 試算表生成報表

到目前為止,您已封存並分析公司資料,但還缺少這項工作的摘要。現在,我們將所有結果整理成一份報表,方便您交給主管。還有什麼比試算表更適合向管理階層展示?

Google 試算表 API 無須額外匯入資料,唯一需要的新資訊是現有試算表的檔案 ID,該試算表已格式化並等待新的資料列,因此需要 SHEET 常數。建議您建立類似下方的全新試算表:

4def78583d05300.png

該試算表的網址如下:https://docs.google.com/spreadsheets/d/FILE_ID/edit。抓取該 FILE_ID,並將其指派為 SHEET 的字串。

我們也偷偷加入名為 k_ize() 的微小函式,可將位元組轉換為 KB,並將其定義為 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 試算表服務端點,如下所示:

# 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() 的功能很簡單:取得資料列和試算表 ID,然後將該資料列新增至試算表:

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() 時,需要提供試算表的檔案 ID、儲存格範圍、資料輸入方式和資料本身。檔案 ID 很簡單,儲存格範圍以 A1 標記表示。「Sheet1」範圍代表整份試算表,這會向 API 發出信號,在試算表中的所有資料後附加資料列。您可以選擇將資料新增至試算表的方式:「RAW」(逐字輸入字串資料) 或「USER_ENTERED」(以使用者透過 Google 試算表應用程式在鍵盤上輸入資料的方式寫入資料,並保留所有儲存格格式功能)。

如果呼叫成功,回傳值不會有任何實用資訊,因此我們選擇取得 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 試算表中的各欄代表資料,例如任何父項「子目錄」、封存檔案在 Cloud Storage 中的位置 (bucket + 檔案名稱)、檔案的 MIME 類型、檔案大小 (原始單位為位元組,但已透過 k_ize() 轉換為 KB),以及 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 試算表,以便更清楚地瞭解資料。如下方範例所示,最後一行 (第 7 列) 已新增至先前新增的現有資料集:

b53a5bc944734652.png

摘要

在本教學課程的前 3 個步驟中,您已連結 Google Workspace 和 Google Cloud API,以便移動及分析資料,這佔了所有工作的 80%。不過,如果無法向管理階層展示您完成的所有工作,上述一切就毫無意義。如要更清楚地呈現結果,請在產生的報表中匯總所有結果。

為進一步提升分析的實用性,除了將結果寫入試算表,還可以為每張圖片的前 5 個標籤建立索引,以便建構內部資料庫,讓授權員工依搜尋團隊查詢圖片,但這部分留給讀者練習。

目前結果會顯示在 Google 試算表中,管理階層可以存取。此階段的應用程式程式碼應與存放區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 測試網誌文章所述,這會增加程式碼的複雜度。

遵循這項最佳做法,將應用程式的主要部分重組為函式,並在每個「中斷點」使用 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

其他選項則可讓使用者選擇不同的雲端硬碟檔案名稱、Cloud Storage「子目錄」和 bucket 名稱、Cloud Vision 的前「N」個結果,以及試算表 (Google 試算表) 檔案 ID。完成這些最後的更新後,最終版本的程式碼現在應與這個存放區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. 恭喜!

本程式碼研究室的內容相當豐富,您也順利完成,可說是通過了最長的程式碼研究室之一。因此,您運用約 130 行 Python 程式碼,解決了可能的企業情境,並充分利用 Google Cloud 和 Google Workspace,在兩者之間移動資料,建構可行的解決方案。歡迎探索這個應用程式所有版本的開放原始碼存放區 (詳情請見下文)。

清除所用資源

  1. 使用 Google Cloud API 需要付費,而 Google Workspace API 則包含在每月 Google Workspace 訂閱費用中 (消費者 Gmail 使用者每月費用為零),因此 Google Workspace 使用者不需要清理/停用 API。如果是 Google Cloud,您可以前往 Cloud 控制台資訊主頁,查看「帳單」資訊卡中的預估費用。
  2. Cloud Vision 每月提供固定次數的免費 API 呼叫。因此只要不超過這些限制,就不必關閉任何項目,也不必停用/刪除專案。如要進一步瞭解 Vision API 的計費方式和免費配額,請參閱定價頁面
  3. 部分 Cloud Storage 使用者每月可獲得免費儲存空間。如果您使用本程式碼研究室封存的圖片未超過配額,就不會產生任何費用。如要進一步瞭解 GCS 計費方式和免費配額,請參閱定價頁面。您可以透過 Cloud Storage 瀏覽器查看及輕鬆刪除 Blob。
  4. 您使用 Google 雲端硬碟時也可能會有儲存空間配額,如果超過配額 (或即將超過),不妨考慮使用本程式碼研究室中建構的工具,將這些圖片封存到 Cloud Storage,以便在雲端硬碟中騰出更多空間。如要進一步瞭解 Google 雲端硬碟儲存空間,請前往 Google Workspace Basic 使用者或 Gmail/消費者使用者的相關價格頁面。

雖然大多數 Google Workspace Business 和 Enterprise 方案都提供無限儲存空間,但這可能會導致雲端硬碟資料夾雜亂無章,或檔案過多而難以管理。本教學課程中建構的應用程式非常適合用來封存多餘檔案,並清理 Google 雲端硬碟。

替代版本

雖然 final/analyze_gsimg.py 是本教學課程中「最後」的正式版本,但這並非最終版本。最終版應用程式的問題之一是使用舊版驗證程式庫,而這些程式庫已遭淘汰。我們選擇這個路徑,是因為在撰寫本文時,較新的驗證程式庫不支援幾個重要元素:OAuth 權杖儲存空間管理和執行緒安全。

目前 (較新) 的驗證程式庫

不過,舊版驗證程式庫將於日後停止支援,因此建議您查看存放區 alt 資料夾中,使用新版 (目前) 驗證程式庫的版本,即使這些版本並非執行緒安全 (但您可以自行建構執行緒安全的解決方案)。尋找名稱中含有 *newauth* 的檔案。

Google Cloud 產品用戶端程式庫

Google Cloud 建議所有開發人員使用 Google Cloud API 時,都採用產品用戶端程式庫。很抱歉,非 Google Cloud API 目前沒有這類程式庫。使用低階程式庫可確保 API 用法一致,且更容易閱讀。與上述建議類似,您可以在存放區的 alt 資料夾中查看使用 Google Cloud 產品用戶端程式庫的替代版本。尋找名稱中含有 *-gcp* 的檔案。

服務帳戶授權

完全在雲端作業時,通常不會有使用者或使用者擁有的資料,因此服務帳戶和服務帳戶授權主要用於 Google Cloud。不過,Google Workspace 文件通常是由 (真人) 使用者擁有,因此本教學課程使用使用者帳戶授權。這並不代表服務帳戶無法使用 Google Workspace API。只要這些帳戶具有適當的存取層級,當然就能在應用程式中使用。與上述類似,您可以在存放區的 alt 資料夾中,查看使用服務帳戶授權的替代版本。尋找名稱中含有 *-svc* 的檔案。

替代版本目錄

下方列出 final/analyze_gsimg.py 的所有替代版本,每個版本都具有上述一或多項屬性。在每個版本的檔案名稱中,尋找:

  • 適用於使用舊版驗證程式庫的版本 (除了 final/analyze_gsimg.py 之外)oldauth
  • 使用目前/較新驗證程式庫的「newauth
  • "gcp":適用於使用 Google Cloud 產品用戶端程式庫 (例如 google-cloud-storage 等) 的使用者。
  • 如果使用者採用服務帳戶 (「svc acct」) 驗證,而非使用者帳戶,則為「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 相同,但使用服務帳戶而非使用者帳戶

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 雲端硬碟資料夾中的圖片,不必逐一處理。
  2. (ZIP 檔案中的多張圖片) 圖片資料夾換成包含圖片檔案的 ZIP 封存檔如何?如果使用 Python,請考慮使用 zipfile 模組
  3. (分析 Vision 標籤) 將類似的圖片歸類在一起,或許可以先找出最常見的標籤,然後是第二常見的標籤,依此類推。
  4. (建立圖表) 後續步驟 #3:根據 Vision API 分析和分類結果,使用 Google Sheets API 生成圖表
  5. (將文件分類) 假設您要使用 Cloud Natural Language API 分類 PDF 檔案,而不是使用 Cloud Vision API 分析圖片。透過上述解決方案,這些 PDF 可以位於雲端硬碟資料夾或雲端硬碟上的 ZIP 封存檔中。
  6. (製作簡報) 使用 Slides API,根據 Google 試算表報表的內容產生簡報。如需靈感,請參閱這篇網誌文章和影片,瞭解如何根據試算表資料產生投影片。
  7. (匯出為 PDF) 將試算表和/或投影片組匯出為 PDF,但這並非 Google 試算表或簡報 API 的功能。提示:Google Drive API。加分題:使用 Ghostscript (Linux、Windows) 或 Combine PDF Pages.action (Mac OS X) 等工具,將試算表和簡報 PDF 合併為一個主 PDF。

瞭解詳情

程式碼研究室

一般

Google Workspace

Google Cloud

授權

這項內容採用的授權為 Creative Commons 姓名標示 2.0 通用授權。