GoogleDrive から GoogleCloudStorageへ移動ハンズオン

はじめに

仕事の都合で GoogleCloudを触り始めた、GoogleCloud歴 3か月の駆け出しエンジニアです。

GoogleDriveから、GoogleCloudStorage(以下、GCS)に資材を移動させようとした際、これといったマネージドサービスが見つかりませんでしたので、「GoogleDrive API」を利用して資材移動に挑戦してみました。

方法論

  • Google Drive から GCSへの資材移動
    file

ハンズオン

1.Google Cloudの準備

1.1.プロジェクト作成・API有効化

  • 以下ブログを参考にGoogleCloudの設定をする。

参照ブログ:AWS Lambda から Gemini APIを利用した呼出しハンズオン

  • 「1.3.API有効化」では、以下「Google Drive API」を有効化する。
    file

1.2.サービスアカウントの作成

  • 「IAMと管理」から左ペイン「サービスアカウント」へ移動して「+ サービスアカウントを作成」を押下する。
    file

  • サービスアカウント作成

設定内容
サービスアカウント名 ${サービスアカウント名} drive-gcs-transfer
サービスアカウントID ${サービスアカウントID} drive-gcs-transfer
サービスアカウント説明 ${サービスアカウント説明} 空欄
ロールを選択 ${必要ロール} ・Storage オブジェクト作成者
  • 作成完了後、タブ「鍵」を押下して「キーを追加」押下して、サービスアカウントのキーを「JSON形式」でダウンロードする(後述手順 4.1.事前準備の「ファイルのアップロード」で利用)
    file

2.GCSの作成

  • 「マネジメントコンソール」画面から「GCS」画面へ遷移します。
  • 「バケットを作成」を選択し、新規バケットを作成する。
設定内容
バケット名 ${バケット名} drive-gcs-transfer

3.GoogleDriveの準備

  • 「1.2.サービスアカウントの作成」で作成したサービスアカウントに「閲覧者」としての権限を付与する。
    file

  • GCSへ転送させるフォルダID(赤枠部分)を取得して、メモ等に保存する。
    file

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」ファイルをアップロード)
    file

  • 設定ファイルの作成

内容
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」に移動していることが画面からもわかります。
    file

おわりに

得られた知見

  • GoogleDriveAPIの使い方
  • GoogleCloudにおけるサービスアカウントへの理解

今後の課題

  • 生成AIで利用する元ネタを集めるための構築でしたので、これからGoogleDriveAPIを利用してGCSへ保管していく予定ですが、上限値などより細かい設定に関して理解を深めたいです。
Last modified: 2025-03-09

Author