はじめに
仕事の都合で GoogleCloudを触り始めた、GoogleCloud歴 3か月の駆け出しエンジニアです。
GoogleDriveから、GoogleCloudStorage(以下、GCS)に資材を移動させようとした際、これといったマネージドサービスが見つかりませんでしたので、「GoogleDrive API」を利用して資材移動に挑戦してみました。
方法論
- Google Drive から GCSへの資材移動
ハンズオン
1.Google Cloudの準備
1.1.プロジェクト作成・API有効化
- 以下ブログを参考にGoogleCloudの設定をする。
参照ブログ:AWS Lambda から Gemini APIを利用した呼出しハンズオン
- 「1.3.API有効化」では、以下「Google Drive API」を有効化する。
1.2.サービスアカウントの作成
-
「IAMと管理」から左ペイン「サービスアカウント」へ移動して「+ サービスアカウントを作成」を押下する。
-
サービスアカウント作成
設定内容 | 値 | 例 |
---|---|---|
サービスアカウント名 | ${サービスアカウント名} | drive-gcs-transfer |
サービスアカウントID | ${サービスアカウントID} | drive-gcs-transfer |
サービスアカウント説明 | ${サービスアカウント説明} | 空欄 |
ロールを選択 | ${必要ロール} | ・Storage オブジェクト作成者 |
- 作成完了後、タブ「鍵」を押下して「キーを追加」押下して、サービスアカウントのキーを「JSON形式」でダウンロードする(後述手順 4.1.事前準備の「ファイルのアップロード」で利用)
2.GCSの作成
- 「マネジメントコンソール」画面から「GCS」画面へ遷移します。
- 「バケットを作成」を選択し、新規バケットを作成する。
設定内容 | 値 | 例 |
---|---|---|
バケット名 | ${バケット名} | drive-gcs-transfer |
3.GoogleDriveの準備
-
「1.2.サービスアカウントの作成」で作成したサービスアカウントに「閲覧者」としての権限を付与する。
-
GCSへ転送させるフォルダID(赤枠部分)を取得して、メモ等に保存する。
4.Cloud Shellの準備
4.1.事前準備
-
マネジメントコンソール右上 [>.] アイコンより、Cloudshellの起動し以下コマンドを実施する。
-
Cloud Shellでディレクトリ作成
mkdir drive-gcs-transfer cd drive-gcs-transfer
-
Python 仮想環境作成
python3 -m venv venv
-
仮想環境の有効化
source venv/bin/activate
-
requirements.txt ファイルの作成
google-api-python-client google-auth-httplib2
-
依存ライブラリのインストール
pip install -r requirements.txt
-
ファイルのアップロード(1.2.サービスアカウントの作成で取得した「JSON」ファイルをアップロード)
-
設定ファイルの作成
値 | 内容 |
---|---|
Servece_Account_Key | 「1.2.サービスアカウントの作成」で取得したJSONファイル名 |
DRIVE_FOLDER_ID | 「3.GoogleDriveの準備」で取得した ファイルID |
GCS_BUCKET_NAME | 「2.GCSの作成」で作成したGCSバケット名 |
GCS_DESTINATION_FOLDER | GCS内の作成したいフォルダ名 |
vi config.ini
cat config.ini
[DEFAULT]
KEY_PATH=(例)drive-gcs-transfer-1234567-890.json
DRIVE_FOLDER_ID=(例)123456789-d
GCS_BUCKET_NAME=(例)drive-gcs-transfer
GCS_DESTINATION_FOLDER=(例)destination_folder
4.2.スクリプト
import os
import subprocess
import time
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
import configparser
# 設定ファイルを読み込む
config = configparser.ConfigParser()
config.read('config.ini')
# 設定ファイルから設定値を読み込む
KEY_PATH = config['DEFAULT']['KEY_PATH'].strip("'")
DRIVE_FOLDER_ID = config['DEFAULT']['DRIVE_FOLDER_ID'].strip("'")
GCS_BUCKET_NAME = config['DEFAULT']['GCS_BUCKET_NAME'].strip("'")
GCS_DESTINATION_FOLDER = config['DEFAULT']['GCS_DESTINATION_FOLDER'].strip("'")
def download_from_drive(file_id, filepath):
"""Google Drive からファイルをダウンロードします."""
creds = service_account.Credentials.from_service_account_file(KEY_PATH)
drive_service = build('drive', 'v3', credentials=creds)
request = drive_service.files().get_media(fileId=file_id)
with open(filepath, 'wb') as fh:
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
status, done = downloader.next_chunk()
print(f"Download Progress: {int(status.progress() * 100)}%")
def upload_to_gcs(filepath, bucket_name, upload_path):
"""GCS バケットへファイルをアップロードします."""
gcs_path = f"gs://{bucket_name}/{upload_path}"
command_str = f"gsutil cp {filepath} {gcs_path}"
subprocess.run(command_str, shell=True, check=True)
print(f"File uploaded to {gcs_path}")
def create_gcs_folder(bucket_name, folder_path):
"""GCS にフォルダを作成する (擬似フォルダを作る)"""
gcs_folder_path = f"gs://{bucket_name}/{folder_path}/"
empty_file = "/dev/null"
command = ['gsutil', 'cp', empty_file, gcs_folder_path]
try:
result = subprocess.run(command, check=True, stderr=subprocess.PIPE, text=True)
print(f"フォルダ (擬似フォルダ) 作成成功: {gcs_folder_path}")
except subprocess.CalledProcessError as e:
print(f"エラーが発生しました:{e}")
print(f"エラー出力:{e.stderr}")
def delete_null_file_in_gcs_folder(bucket_name, folder_path):
"""GCS フォルダ (擬似フォルダ) 内の null ファイルを削除します."""
gcs_null_file_path = f"gs://{bucket_name}/{folder_path}/null"
command = ['gsutil', 'rm', gcs_null_file_path]
try:
result = subprocess.run(command, check=True, stderr=subprocess.PIPE, text=True)
print(f"null ファイル削除成功: {gcs_null_file_path}")
except subprocess.CalledProcessError as e:
print(f"エラーが発生しました:{e}")
print(f"エラー出力:{e.stderr}")
def get_drive_folder_name(folder_id):
"""Google Drive フォルダの名前を取得します."""
creds = service_account.Credentials.from_service_account_file(KEY_PATH)
drive_service = build('drive', 'v3', credentials=creds)
try:
folder = drive_service.files().get(fileId=folder_id, fields='name').execute()
return folder.get('name')
except Exception as error:
print(f'An error occurred: {error}')
return None
def list_drive_files_in_folder(folder_id):
"""Google Drive フォルダ内のファイルとフォルダをリストアップします."""
creds = service_account.Credentials.from_service_account_file(KEY_PATH)
drive_service = build('drive', 'v3', credentials=creds)
results = []
page_token = None
while True:
try:
param = {}
param['q'] = f"'{folder_id}' in parents and trashed=false"
param['pageToken'] = page_token if page_token else None
file_list = drive_service.files().list(**param).execute()
results.extend(file_list.get('files', []))
page_token = file_list.get('nextPageToken')
if not page_token:
break
except Exception as error:
print(f'An error occurred: {error}')
break
return results
def transfer_drive_folder_to_gcs(drive_folder_id, gcs_bucket_name, gcs_destination_folder):
"""Google Drive フォルダを GCS に転送します."""
drive_folder_name = get_drive_folder_name(drive_folder_id) # Google Drive フォルダ名を取得
if not drive_folder_name:
print("Google Drive フォルダ名の取得に失敗しました。")
return
gcs_parent_folder_path = os.path.join(gcs_destination_folder, drive_folder_name) # GCS 親フォルダパスを構築
create_gcs_folder(gcs_bucket_name, gcs_parent_folder_path) # GCS 親フォルダを作成
file_list = list_drive_files_in_folder(drive_folder_id)
for item in file_list:
item_name = item['name']
item_id = item['id']
item_mime_type = item['mimeType']
gcs_item_path = os.path.join(gcs_parent_folder_path, item_name) # GCS でのパスを親フォルダ配下に構築
if item_mime_type == 'application/vnd.google-apps.folder':
# フォルダの場合、GCS にフォルダを作成し、再帰的に処理 (必要に応じて実装)
print(f"Creating folder: {item_name}")
create_gcs_folder(gcs_bucket_name, gcs_item_path)
print(f"Processing folder: {item_name}")
transfer_drive_folder_to_gcs(item_id, gcs_bucket_name, gcs_item_path)
else:
# ファイルの場合、ダウンロードして GCS にアップロード
local_filepath = os.path.join('/tmp', item_name)
print(f"Downloading file: {item_name}")
download_from_drive(item_id, local_filepath)
print(f"Uploading file: {item_name} to gs://{gcs_bucket_name}/{gcs_item_path}")
upload_to_gcs(local_filepath, gcs_bucket_name, gcs_item_path)
os.remove(local_filepath)
print(f"Temporary file {local_filepath} deleted")
time.sleep(1) # API Rate Limit 対策 (必要に応じて調整)
delete_null_file_in_gcs_folder(gcs_bucket_name, gcs_parent_folder_path) # null ファイルを削除
if __name__ == '__main__':
transfer_drive_folder_to_gcs(DRIVE_FOLDER_ID, GCS_BUCKET_NAME, GCS_DESTINATION_FOLDER) # GCS 転送先フォルダを事前に作成
print("Folder transfer completed.")
5.実行結果
5.1.コンソールでの見え方
(venv) $ python drive_to_gcs_folder.py
# フォルダ作成の通知
フォルダ (擬似フォルダ) 作成成功:
gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/
# GoogleDriveからのダウンロード
Downloading file: test01.xlsx
Download Progress: 100%
# GCSへアップロード
Uploading file: test01.xlsx to gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/test02.xlsx
Copying file:///tmp/test01.xlsx [Content-Type=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet]...
/ [1 files][ 6.4 KiB/ 6.4 KiB] Operation completed over 1 objects/6.4 KiB. File uploaded to gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/test01.xlsx
# ローカルのファイル削除
Temporary file /tmp/test01.xlsx deleted
# フォルダ作成時に作成された nullファイルの削除
null ファイル削除成功: gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/null
Folder transfer completed.
5.2.GCSのフォルダ構成
- 「Google Drive」のフォルダ名および、フォルダ内のオブジェクトが、「GCS」に移動していることが画面からもわかります。
おわりに
得られた知見
- GoogleDriveAPIの使い方
- GoogleCloudにおけるサービスアカウントへの理解
今後の課題
- 生成AIで利用する元ネタを集めるための構築でしたので、これからGoogleDriveAPIを利用してGCSへ保管していく予定ですが、上限値などより細かい設定に関して理解を深めたいです。