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

1. 總覽

我們以這個程式碼研究室為例,探討可能的企業工作流程:封存圖片、分析及產生報表。假設貴機構有一組映像檔在資源有限的資源中佔用過多空間,您需要封存資料、分析這些圖像,最重要的是產生報告,其中會總結封存位置以及分析結果,並整理起來供管理使用。為實現這個目標,Google Cloud 提供了多種工具,包括運用來自兩個產品系列的 API:Google Workspace (原稱 G Suite 或 Google 應用程式) 和 Google Cloud (舊稱 GCP)。

在這個情境中,商業使用者可擁有 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 Billing 帳戶的 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 未使用的字元字串。您隨時可以更新這項資訊。
  • 所有 Google Cloud 專案的專案 ID 均不得重複,且設定後即無法變更。Cloud 控制台會自動產生一個不重複的字串。但通常是在乎它何在在大部分的程式碼研究室中,您必須參照專案 ID (通常為 PROJECT_ID)。如果您對產生的 ID 不滿意,可以隨機產生一個 ID。此外,您也可以自行嘗試,看看系統是否提供該付款方式。在完成這個步驟後就無法變更,而且在專案期間仍會保持有效。
  • 資訊中的第三個值是專案編號,部分 API 會使用這個編號。如要進一步瞭解這三個值,請參閱說明文件
  1. 接下來,您需要在 Cloud 控制台中啟用計費功能,才能使用 Cloud 資源/API。執行這個程式碼研究室並不會產生任何費用,如果有的話。如要關閉資源,以免系統產生本教學課程結束後產生的費用,您可以刪除自己建立的資源,或刪除整個專案。Google Cloud 的新使用者符合 $300 美元免費試用計畫的資格。

啟動 Cloud Shell

摘要

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

啟用 Cloud Shell

  1. 在 Cloud 控制台中,按一下「啟用 Cloud Shell」圖示 853e55310c205094.png

55efc1aaa7a4d3ad.png

如果您先前從未啟動 Cloud Shell,您會看見中繼畫面 (需捲動位置),說明螢幕內容。如果出現這種情況,請按一下「繼續」 (之後不會再顯示)。以下是單次畫面的外觀:

9c92662c6a846a5c.png

佈建並連線至 Cloud Shell 只需幾分鐘的時間。

9f0e51b578fecce5.png

這個虛擬機器搭載您需要的所有開發工具。提供永久的 5 GB 主目錄,而且在 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 直譯器,特別是如果您屬於數據資料學或機器學習社群的一部分,更是如此。如果您採用的話,Jupyter NotebooksColab 和 Jupyter Notebook 的預設解譯器都是 IPython。

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;如果這些操作說明與 3.x 有顯著差異,我們將會提供 Python 2 適用的特定操作說明。

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

本節為選用項目,只有必須在本程式碼研究室中使用虛擬環境的使用者 (如上方的警告側欄) 才需要。如果您的電腦只有 Python 3,可以直接發出這個指令,建立一個名為 my_env 的 virtualenv (您可以視需要選擇其他名稱):

virtualenv my_env

不過,如果您同時擁有 Python 2 和在電腦上,建議您安裝 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"

如果您是從 Cloud Shell 改用 Python 2,您將會收到支援功能,說明已淘汰的支援功能:

*******************************************************************************
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」指令執行成功 (沒有錯誤/輸出),就可以開始與 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

在下一個畫面,您有兩個動作:設定應用程式的授權「同意畫面」並選擇應用程式類型:

4e0b967c9d70d262.png

如果尚未設定同意畫面,您會在控制台中看到警示,必須立即完成。(如果已設定同意畫面,請略過這些後續步驟)。

按一下「設定同意畫面」您可以在其中選取「外部」應用程式 (如果您是 G Suite 客戶,則為「內部」):

f17e97b30d994b0c.png

請注意,就本練習而言,由於您不是發布程式碼研究室範例,因此您可以自行選擇。大部分的人會選取「外部」但只要輸入「應用程式名稱」即可」欄位:

b107ab81349bdad2.png

目前,您只需要使用應用程式名稱,因此請選擇能反映該程式碼研究室的人員,然後按一下「Save」

正在建立 OAuth 用戶端 ID (使用者帳戶驗證)

現在,請返回「憑證」分頁建立 OAuth2 用戶端 ID。您會看到各種可建立的 OAuth 用戶端 ID:

5ddd365ac0af1e34.png

我們正在開發一款指令列工具 (其他),因此請選擇該工具,然後按一下「Create」按鈕。請選擇符合所建立應用程式的用戶端 ID 名稱,或直接使用預設名稱 (通常為「其他用戶端 N」)。

儲存憑證

  1. 畫面上會顯示包含新憑證的對話方塊。按一下「確定」關閉

8bec84d82cb104d7.png

  1. 返回「憑證」頁面,向下捲動至「OAuth2 用戶端 ID」部分中,找出並按一下剛建立用戶端 ID 最右下角的下載圖示 f54b28417901b3aa.png1b4e8d248274a338.png
  2. 系統會開啟對話方塊,讓您儲存 client_secret-LONG-HASH-STRING.apps.googleusercontent.com.json 檔案,該檔案可能位於「下載」資料夾。建議您縮短為較容易使用的名稱 (例如 client_secret.json,這是範例應用程式使用的名稱),然後將其儲存至您將在本程式碼研究室中建立範例應用程式的目錄/資料夾。

摘要

您現在可以開始啟用本程式碼研究室所用的 Google API。此外,在 OAuth 同意畫面中,我們選取了「Vision API 示範」,因此預期會在部分螢幕截圖中看到這項資訊。

6. 啟用 Google API

本程式碼研究室會使用四個 (4) Google Cloud API,一組是 Google Cloud (Cloud Storage 和 Cloud Vision),以及 Google Workspace 的另一對 (Google 雲端硬碟和 Google 試算表)。以下是啟用 Google API 的一般操作說明。瞭解如何啟用其中一個 API 後,其他 API 也會有類似的情況。

無論您在應用程式中要使用哪個 Google API,都必須啟用 API。API 可透過指令列或 Cloud 控制台啟用。API 的啟用程序並無不同,因此您只要啟用一個 API,即可透過類似的方式啟用其他 API。

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

雖然透過 Cloud 控制台啟用 API 較為常見,但有些開發人員偏好透過指令列完成所有事項。方法是查詢 API 的「服務名稱」。網址類似於:SERVICE_NAME.googleapis.com。您可以在支援的產品圖表中找到這些資訊,也可以利用 Google Discovery API,以程式輔助的方式查詢這些產品。

利用這些資訊準備 Cloud Shell 時,您可以透過 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 控制台中,前往「API 管理員」。這個資訊主頁頁面會顯示應用程式的流量資訊、相關圖表,顯示應用程式要求、應用程式產生的錯誤,以及應用程式回應時間:

df4a0a5e00d29ffc.png

下方圖表列出您的專案已啟用的 Google API:

5fcf10e5a05cfb97.png

如要啟用 (或停用) API,請按一下頂端的「啟用 API 和服務」

eef4e5e863f4db66.png

或者,前往左側導覽列並選取「API 與」服務圖書館

6eda5ba145b30b97.png

無論您採用哪種方式,您都會進入「API 程式庫」頁面:

5d4f1c8e7cf8df28.png

輸入 API 名稱即可搜尋及查看相符的結果:

35bc4b9cf72ce9a4.png

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

9574a69ef8d9e8d2.png

無論您要使用哪一個 Google API,啟用所有 API 的程序都大同小異。

費用

許多 Google API 都無須付費即可使用,但大部分的 Google Cloud 產品與 API 使用都需要付費。啟用 Cloud API 時,系統可能會要求您提供有效的帳單帳戶。不過,部分 Google Cloud 產品提供的是「一律免費」項目層級,必須超過這個額度才會產生帳單費用。

新使用者符合免費試用資格,目前前 90 天內可折抵 $300 美元。使用程式碼研究室通常不會產生太多費用,也不會產生任何費用,因此建議您先準備免費試用,直到準備好進行試用為止,特別是一次性優惠。無論您是否使用免費試用,都沒有效期限制,而且免費方案配額沒有期限。

啟用 API 前,使用者應先參考任何 API 的定價資訊 (例如 Cloud Vision API 定價 頁面),特別是該 API 是否提供免費方案,以及 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 雲端硬碟檢視及下載 Google 雲端硬碟。在應用程式程式碼中,這些權限範圍會顯示為 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 尚未建立完成,因此會忽略這項警告)。按照在另一個瀏覽器分頁中使用該網址的操作說明,即可體驗與上述本機開發環境幾乎完全相同的體驗 (請見上方螢幕截圖)。最後一個畫面會顯示一個最終畫面,當中會顯示要在 Cloud Shell 中輸入的驗證碼:

40a567cda0f31cc9.png

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

摘要

除「Authentication successful」以外,沒有任何額外輸出內容。提醒您,這只是設定...您尚未完成任何操作,您採取的做法將會順利開始,邁向第一次可以正確執行的工作。(最大的優點是,系統只要求授權一次,所有連續執行作業都會略過,因為您的權限已快取完成)。現在,讓我們來讓程式碼執行一些實際工作,從而產生實際輸出內容。

疑難排解

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

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

在上一個步驟中,我們建議以 analyze_gsimg.py 的形式建立程式碼,並從該處編輯。也可以只將所有內容直接剪下貼到 iPython 或標準 Python 殼層;不過,我們將繼續個別建構應用程式,因此會比較繁瑣。

假設應用程式已獲得授權,並建立 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

雲端硬碟 files() 集合含有 list() 方法,可對指定的檔案執行查詢 (q 參數)。fields 參數可用來指定您感興趣的傳回值,如果您不在意其他值,為何會放棄所有內容,並讓作業速度變慢?如果您是第一次使用欄位遮罩篩選 API 傳回值,請參閱這篇網誌文章:影片。否則,請執行查詢並擷取傳回的 files 屬性。如果沒有相符結果,則預設為空白清單陣列。

如果沒有結果,系統會略過函式的其餘部分,並以隱含形式傳回 None。否則,擷取第一個相符的回應 (rsp[0])、回傳檔案名稱、其 MIME 類型、上次修改時間戳記、最後是 get_media() 函式 (透過其檔案 ID) 擷取的二進位酬載,同樣也在 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 並設為 FILE,指令碼執行成功時,畫面上應會顯示輸出內容,確認能否從雲端硬碟讀取檔案 (但不會儲存到電腦):

$ 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 查詢,然後下載該檔案。業務用途:封存您的雲端硬碟資料,或許還能使用 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 中的存放位置,特別是「值區」的名稱您要將檔案存放 存放在任何「上層資料夾」中前置字串。稍後會進一步說明:

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

值區說明:Cloud Storage 提供不規則的 blob 儲存空間。將檔案上傳到這裡時,Google 雲端硬碟並無法理解檔案類型、副檔名等的概念,就像 Google 雲端硬碟一樣。只是「胖球」至 Cloud Storage而且 Cloud Storage 中沒有資料夾或子目錄的概念。

可以。您可以在檔案名稱中加入斜線 (/) 來代表多個子資料夾的抽象化機制,但在當天結束後,所有的 blob 都會放入值區中,「/」則是檔案名稱中的字元。詳情請參閱值區和物件命名慣例頁面

上述步驟 1 要求雲端硬碟唯讀範圍。到時候,您就可以輕鬆了。現在,需要上傳 (讀取/寫入) 權限至 Cloud Storage。將 SCOPES 從單一字串變數變更為權限範圍的陣列 (Python 元組 [或清單]),如下所示:

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

接著在雲端硬碟的端點下方,建立 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() 呼叫需要值區名稱、檔案中繼資料和二進位 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,然後上傳到 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">

仔細查看,雖然雲端硬碟下載成功,但檔案上傳至 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'

摘要

這個大型問題在於如何使用相對較少的程式碼,在兩個雲端式儲存系統之間傳輸檔案。業務用途是將可能受限的資源備份至「Colder」,儲存空間會比之前提到的 1 大便宜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 字串,方便在報表中使用。

成功上傳至 Cloud Storage 後,呼叫 vision_label_img() 的這 5 行應正確放置如下:

            # 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 的 SSA。

我們也會在名為 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',
)                  

現在,在其他服務附近建立服務端點,看起來會像這樣:

# 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」(以使用者鍵盤在鍵盤上輸入的方式寫入資料,可保留所有儲存格格式設定功能)。

如果呼叫成功,傳回值並沒有什麼用處,因此我們選擇讓 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 中已封存檔案的位置 (值區 + 檔案名稱)、檔案的 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 個標籤建立索引,這樣我們就能建立內部資料庫,讓授權員工透過搜尋團隊查詢圖片,但這樣我們才會將這視為讀者練習。

現階段,我們現在都會將結果轉換為工作表,方便您管理。此階段的應用程式程式碼應與位於 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 的「子目錄」)和值區名稱 (前「N」)產生的結果,以及試算表 (試算表) 檔案 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 的計費方式和免費配額,請參閱 Google Cloud 定價頁面
  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 的所有替代版本,每個版本皆具備上述一或多個屬性。請在每個版本的檔案名稱中找出:

  • oldauth」適用於使用舊版驗證程式庫 (除了 final/analyze_gsimg.py 之外) 的 版本
  • newauth」新式/新版驗證程式庫的讀者
  • gcp」使用 Google Cloud 產品用戶端程式庫,即 google-cloud-storage 等
  • svc」透過服務帳戶 (「svc acct」) 驗證 (而非使用者帳戶) 的使用者

以下是所有版本:

檔案名稱

說明

final/analyze_gsimg.py

主要樣本;採用舊版驗證程式庫

alt/analyze_gsimg-newauth.py

final/analyze_gsimg.py 相同,但會使用新版驗證程式庫

alt/analyze_gsimg-oldauth-gcp.py

final/analyze_gsimg.py 相同,但會使用 Google Cloud 產品用戶端程式庫

alt/analyze_gsimg-newauth-gcp.py

alt/analyze_gsimg-newauth.py 相同,但會使用 Google Cloud 產品用戶端程式庫

alt/analyze_gsimg-oldauth-svc.py

final/analyze_gsimg.py 相同,但使用的是 SV 帳戶,而非使用者帳戶

alt/analyze_gsimg-newauth-svc.py

alt/analyze_gsimg-newauth.py 相同,但使用 SV 帳戶驗證,而非使用者帳戶

alt/analyze_gsimg-oldauth-svc-gcp.py

alt/analyze_gsimg-oldauth-svc.py 相同,但採用 Google Cloud 產品用戶端程式庫,與 alt/analyze_gsimg-oldauth-gcp.py 相同,但使用的是 SV 帳戶,而非使用者帳戶

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 分析和分類功能使用 Sheets API 產生圖表
  5. (將文件分類) 您沒有使用 Cloud Vision API 分析圖片,而是假設您有要利用 Cloud Natural Language API 分類的 PDF 檔案。如使用上述解決方案,這些 PDF 檔案可能會儲存在雲端硬碟資料夾或 ZIP 封存檔中。
  6. (建立簡報) 使用 Slides API 根據 Google 試算表報告內容產生簡報。如果需要靈感,請參閱這篇網誌文章和影片
  7. (匯出為 PDF) 以 PDF 格式匯出試算表和/或簡報,但這項功能並非試算表或 Slides API 的功能。提示:Google Drive API。額外加分:使用 Ghostscript (Linux、Windows) 或 Combine PDF Pages.action (Mac OS X) 等工具,將試算表和簡報 PDF 檔案合併成一個主要 PDF。

瞭解詳情

程式碼研究室

一般

Google Workspace

Google Cloud

授權

這項內容採用的是創用 CC 姓名標示 2.0 通用授權。