💭

PythonでGoogle Drive を操作する方法(Pydrive2)

2024/04/07に公開

仕事でGoogle DriveのAPIを利用したいと思ったのですが、調べてもよくわからなかったので自分で試行錯誤してモジュールを作ってみました。
業務効率化のスクリプトを作るときなどに活用できるかなと思います。

ただ、Google Drive APIのAPIキーが必要になるので、そちらはネットの記事を参考に別途取得しておいてください。

事前準備

パッケージのインストール

まず、このスクリプトを実行するためには、以下のPythonパッケージが必要です。

  • pydrive2: Google Drive APIとやり取りを行うためのライブラリ
  • pandas: データ分析ライブラリで、このスクリプトではファイルの情報をデータフレームとして扱うために使用しています。

これらのパッケージはpipを使用してインストールすることができます。
以下のコマンドを実行してインストールできます。

pip install pydrive2 pandas

このコマンドを実行すると、必要なパッケージがインストールされます。

セッティングファイルの作成

次にpydrive2を使ってGoogle Drive APIを利用する場合、以下の3つのファイルが必要となります。

  1. client_secrets.json: Google Cloud Consoleからダウンロードすることができます。OAuth 2.0 クライアントIDの認証情報を含んでいます。
  2. saved_credentials.json: ユーザーが認証を行った後に生成されます。ユーザーのアクセストークンとリフレッシュトークンが含まれています。これによりユーザーは再認証することなくGoogle Drive APIを利用することができます。
  3. settings.yaml: pydrive2の設定を含んでいます。認証に関する情報や、上記の2つのjsonファイルのパスなどが指定されます。

それぞれのファイルについて詳しく説明します。

1. client_secrets.json

このファイルはGoogle Cloud Consoleからダウンロードします。以下の手順で取得できます。(UIが変更になっている可能性があるのでその場合は同じような画面を探してみてください)

  1. Google Cloud Consoleにアクセスし、プロジェクトを選択または新規作成します。
  2. 左側のナビゲーションメニューから「APIとサービス」 > 「認証情報」を選択します。
  3. 「認証情報を作成」をクリックし、「OAuthクライアントID」を選択します。
  4. 「アプリケーションの種類」で「デスクトップアプリ」を選択し、「作成」をクリックします。
  5. 作成した認証情報の右側にあるダウンロードボタンをクリックし、jsonファイルをダウンロードします。
  6. ダウンロードしたjsonファイルをclient_secrets.jsonとして保存します。

2. saved_credentials.json

このファイルはユーザーが認証を行った後に自動的に生成されます。pydrive2が初めて認証を行う際に、client_secrets.jsonを参照してブラウザで認証画面が表示されます。ユーザーが認証を行うと、pydrive2は得られたアクセストークンとリフレッシュトークンをsaved_credentials.jsonに保存します。

3. settings.yaml

このファイルはpydrive2の設定を指定します。以下のような内容を含めます。

client_config_backend: file
client_config_file: client_secrets.json
client_config:
  client_id: your_client_id
  client_secret: your_client_secret

save_credentials: True
save_credentials_backend: file
save_credentials_file: saved_credentials.json

get_refresh_token: True
oauth_scope:
  - https://www.googleapis.com/auth/drive
  - https://www.googleapis.com/auth/drive.install

client_config_fileにはclient_secrets.jsonのパス、save_credentials_fileにはsaved_credentials.jsonのパスを指定します。また、get_refresh_tokenTrueにすることで、アクセストークンが失効した場合でも自動的に更新(リフレッシュ)されます。oauth_scopeはアプリケーションのアクセス許可を定義します。

これらのファイル (client_secrets.json, saved_credentials.json, settings.yaml) は、スクリプトが実行されるときにPythonがアクセスできる場所に配置してください。一般的には、これらのファイルはスクリプトと同じディレクトリに配置します。

例えば、次のようなディレクトリ構造になるかもしれません:

.
├── main.py
├── gdrive_api.py
├── client_secrets.json
├── saved_credentials.json
└── settings.yaml

ここで、main.pyが実行されるスクリプト、gdrive_api.pyがGoogle Drive APIを操作するためのスクリプトで、これらは同じディレクトリにあることを前提としています。

ただし、これらの設定ファイルは機密情報を含むため、安全に管理することが重要です。共有されたり公開されたりしないように注意してください。

また、settings.yamlにおいて client_config_filesave_credentials_file の設定を変更すれば、これらのファイルが別のディレクトリに配置されていても問題ありません。例えば、次のように設定することが可能です:

client_config_file: /path/to/your/client_secrets.json
save_credentials_file: /path/to/your/saved_credentials.json

これらの設定を変更することで、client_secrets.jsonsaved_credentials.json の位置を自由に指定することができます。ただし、指定するパスはPythonからアクセス可能なものである必要があります。

以上が、pydrive2を使う際に必要な設定ファイルの解説です。注意点としては、これらのファイルには機密情報が含まれますので、不適切に公開されないように注意してください。

コードの解説

こちらがコード全文です。

"""A Python library for interacting with Google Drive API"""

from pydrive2.drive import GoogleDrive
from pydrive2.auth import GoogleAuth
import os
import pandas as pd

class GoogleDriveAPI:
    def __init__(self):
        self.gauth = GoogleAuth() # Google認証のインスタンス化
        self.drive = GoogleDrive(self.gauth) # GoogleDriveのインスタンス化
        self.gauth.LocalWebserverAuth()# ローカルWebサーバーで認証する


    def get_folder_id(self, folder_name):
        """フォルダ名からフォルダIDを取得"""
        query = "trashed = false and mimeType='application/vnd.google-apps.folder' and title='{}'".format(folder_name)
        folder_list = self.drive.ListFile({'q': query}).GetList()
        if len(folder_list) > 0:
            return folder_list[0]['id']
        else:
            return None
        
    
    def get_file_id(self, folder_name=None, file_name=None, folder_id=None):
        """フォルダ内の指定された名前の最新ファイルのファイルIDを取得する"""
        if not folder_id:
            folder_id = self.get_folder_id(folder_name)
        query = f"trashed = false and '{folder_id}' in parents and mimeType != 'application/vnd.google-apps.folder'"
        if file_name:
            query += f" and title = '{file_name}'"
            file_list = self.drive.ListFile({'q': query}).GetList()
        else:
            file_list = self.drive.ListFile({'q': query}).GetList()
            if len(file_list) > 0:
                latest_file = max(file_list, key=lambda f: f['createdDate'])
                return latest_file['id']
        if file_list:
            return file_list[0]['id']
        else:
            return None

    def get_all_file_id(self, folder_name):
        """フォルダー名からフォルダを特定し、そのフォルダ内のファイルID一覧を取得する"""
        folder_id = self.get_folder_id(folder_name)
        file_list = self.drive.ListFile({'q': f"\'{folder_id}\' in parents"}).GetList()
        file_id_list = []
        for file in file_list:
            file_id_list.append(file['id'])
        return file_id_list



        
    def get_file_df(self, folder_name):
        """フォルダ内のファイルIDと作成日時のデータフレームを取得する"""
        folder_id = self.get_folder_id(folder_name)
        query = f"trashed = false and '{folder_id}' in parents"
        file_list = self.drive.ListFile({'q': query}).GetList()
        file_id_list = []
        for file in file_list:
            file_id_list.append([file['title'] ,file['createdDate'] ,file['id']])
        file_df = pd.DataFrame(file_id_list, columns=['title', 'createdDate', 'id'])
        return file_df



    def get_created_at(self, folder_name):
        '''指定したフォルダ名のファイルIDと作成日時の辞書を取得する'''
        folder_id = self.get_folder_id(folder_name)
        query = f"trashed = false and '{folder_id}' in parents"
        file_list = self.drive.ListFile({'q': query}).GetList()
        file_dic = {}
        for i in range(len(file_list)):
            file_dic[file_list[i]['id']] = file_list[i]['createdDate']
        return file_dic

    def gdrive_upload(self, folder_id, content_file, file_name='no_name.csv'):
        '''Upload file to Google Drive'''
        metadata = {
            'parents':[
                {"id":folder_id}
            ],
            'title': file_name,
            'mimeType':'text/csv'
        }
        f = self.drive.CreateFile(metadata=metadata)
        f.SetContentFile(content_file)
        f.Upload()


    def gdrive_download(self, folder_name=None, output_name=None, file_id=None, target_dir=""):
        """ファイルを現在のディレクトリまたは指定されたディレクトリにダウンロードする"""
        if not file_id:
            folder_id = self.get_folder_id(folder_name)
            if not folder_id:
                print("Folder not found")
                return
            file_id = self.get_file_id(folder_id=folder_id)
            if not file_id:
                print("File not found")
                return
        f = self.drive.CreateFile({'id': file_id})
        f.FetchMetadata()
        if not f.metadata.get('trashed', False):
            f.GetContentFile(os.path.join(target_dir, output_name))

    def download_from_file_name(self, folder_name, file_name, target_dir=""):
        """指定したフォルダ名とファイル名から特定のファイルをダウンロードする"""
        folder_id = self.get_folder_id(folder_name)
        if not folder_id:
            print("Folder not found")
            return
        file_id = self.get_file_id(folder_name=folder_name, file_name=file_name)
        if not file_id:
            print("File not found")
            return
        f = self.drive.CreateFile({'id': file_id})
        f.FetchMetadata()
        if not f.metadata.get('trashed', False):
            f.GetContentFile(os.path.join(target_dir, file_name))

ライブラリのインポート

from pydrive2.drive import GoogleDrive
from pydrive2.auth import GoogleAuth
import os
import pandas as pd

まず、必要なライブラリをインポートします。
pydrive2はGoogle Drive APIを操作するためのライブラリで、GoogleDriveとGoogleAuthというクラスを使います。
osはファイルパスの操作やディレクトリの作成など、オペレーティングシステムの機能を使うためのライブラリです。
pandasはデータフレームを操作するためのライブラリです。

GoogleDriveAPIクラス

GoogleDriveAPIクラスは、Google Driveの操作を行うためのクラスです。
各メソッドはGoogle Drive APIを活用して、ファイルやフォルダの取得、ファイルのアップロード・ダウンロードなどを行います。

__init__メソッド

def __init__(self):
    self.gauth = GoogleAuth()
    self.drive = GoogleDrive(self.gauth)
    self.gauth.LocalWebserverAuth()

このメソッドは、GoogleDriveAPIクラスのインスタンスを初期化します。Google Driveとの接続と認証を行います。

get_folder_idメソッド

def get_folder_id(self, folder_name):
    """フォルダ名からフォルダIDを取得"""
    query = "trashed = false and mimeType='application/vnd.google-apps.folder' and title='{}'".format(folder_name)
    folder_list = self.drive.ListFile({'q': query}).GetList()
    if len(folder_list) > 0:
        return folder_list[0]['id']
    else:
        return None

このメソッドは、指定されたフォルダ名に対応するフォルダIDを取得します。Google Driveでは、各フォルダやファイルは一意のIDを持っています。

get_file_idメソッド

def get_file_id(self, folder_name=None, file_name=None, folder_id=None):
    """フォルダ内の指定された名前の最新ファイルのファイルIDを取得する"""
    if not folder_id:
        folder_id = self.get_folder_id(folder_name)
    query = f"trashed = false and '{folder_id}' in parents and mimeType != 'application/vnd.google-apps.folder'"
    if file_name:
        query += f" and title = '{file_name}'"
        file_list = self.drive.ListFile({'q': query}).GetList()
    else:
        file_list = self.drive.ListFile({'q': query}).GetList()
        if len(file_list) > 0:
            latest_file = max(file_list, key=lambda f: f['createdDate'])
            return latest_file['id']
    if file_list:
        return file_list[0]['id']
    else:
        return None

このメソッドは、指定したフォルダ内の特定のファイル(または最新のファイル)のIDを取得します。

get_all_file_idメソッド

def get_all_file_id(self, folder_name):
    """フォルダー名からフォルダを特定し、そのフォルダ内のファイルID一覧を取得する"""
    folder_id = self.get_folder_id(folder_name)
    file_list = self.drive.ListFile({'q': f"\'{folder_id}\' in parents"}).GetList()
    file_id_list = []
    for file in file_list:
        file_id_list.append(file['id'])
    return file_id_list

このメソッドは、指定したフォルダ内のすべてのファイルのIDを取得します。

get_file_dfメソッド

def get_file_df(self, folder_name):
    """フォルダ内のファイルIDと作成日時のデータフレームを取得する"""
    folder_id = self.get_folder_id(folder_name)
    query = f"trashed = false and '{folder_id}' in parents"
    file_list = self.drive.ListFile({'q': query}).GetList()
    file_id_list = []
    for file in file_list:
        file_id_list.append([file['title'] ,file['createdDate'] ,file['id']])
    file_df = pd.DataFrame(file_id_list, columns=['title', 'createdDate', 'id'])
    return file_df

このメソッドは、指定したフォルダ内のすべてのファイルの情報(ファイル名、作成日時、ID)をpandasのデータフレームとして取得します。

get_created_atメソッド

def get_created_at(self, folder_name):
    '''指定したフォルダ名のファイルIDと作成日時の辞書を取得する'''
    folder_id = self.get_folder_id(folder_name)
    query = f"trashed = false and '{folder_id}' in parents"
    file_list = self.drive.ListFile({'q': query}).GetList()
    file_dic = {}
    for i in range(len(file_list)):
        file_dic[file_list[i]['id']] = file_list[i]['createdDate']
    return file_dic

このメソッドは、指定したフォルダ内のすべてのファイルの作成日時を取得します。戻り値は、ファイルIDをキーとし、作成日時を値とする辞書です。

gdrive_uploadメソッド

def gdrive_upload(self, folder_id, content_file, file_name='no_name.csv'):
    '''Upload file to Google Drive'''
    metadata = {
        'parents':[
            {"id":folder_id}
        ],
        'title': file_name,
        'mimeType':'text/csv'
    }
    f = self.drive.CreateFile(metadata=metadata)
    f.SetContentFile(content_file)
    f.Upload()

このメソッドは、指定したフォルダにファイルをアップロードします。ファイル名が指定されていない場合、デフォルトで'no_name.csv'という名前が付けられます。

gdrive_downloadメソッド

def gdrive_download(self, folder_name=None, output_name=None, file_id=None, target_dir=""):
    """ファイルを現在のディレクトリまたは指定されたディレクトリにダウンロードする"""
    if not file_id:
        folder_id = self.get_folder_id(folder_name)
        if not folder_id:
            print("Folder not found")
            return
        file_id = self.get_file

_id(folder_id=folder_id)
        if not file_id:
            print("File not found")
            return
    f = self.drive.CreateFile({'id': file_id})
    f.FetchMetadata()
    if not f.metadata.get('trashed', False):
        f.GetContentFile(os.path.join(target_dir, output_name))

このメソッドは、指定したフォルダからファイルをダウンロードします。ファイルのIDが指定されていない場合、そのフォルダ内の最新のファイルがダウンロードされます。

download_from_file_nameメソッド

def download_from_file_name(self, folder_name, file_name, target_dir=""):
    """指定したフォルダ名とファイル名から特定のファイルをダウンロードする"""
    folder_id = self.get_folder_id(folder_name)
    if not folder_id:
        print("Folder not found")
        return
    file_id = self.get_file_id(folder_name=folder_name, file_name=file_name)
    if not file_id:
        print("File not found")
        return
    f = self.drive.CreateFile({'id': file_id})
    f.FetchMetadata()
    if not f.metadata.get('trashed', False):
        f.GetContentFile(os.path.join(target_dir, file_name))

このメソッドは、指定したフォルダ内から特定の名前のファイルをダウンロードします。指定したフォルダ内に指定した名前のファイルが存在する場合、そのファイルがダウンロードされます。

使用方法

Pythonでは、一つのファイルに記述したクラスや関数を別のファイルから呼び出して使用することができます。これをモジュールと呼びます。

まず、上記で定義したGoogleDriveAPIクラスを含むPythonファイル(たとえば、gdrive_api.py)を作成します。

その後、別のPythonファイル(たとえば、main.py)から以下のようにgdrive_api.pyをインポートしてGoogleDriveAPIクラスを使用します。

from gdrive_api import GoogleDriveAPI

# GoogleDriveAPI クラスのインスタンスを作成
drive = GoogleDriveAPI()

# 例えば、特定のフォルダ名からフォルダIDを取得
folder_id = drive.get_folder_id("MyFolder")

print(folder_id)

この場合、gdrive_api.pymain.pyは同じディレクトリにある必要があります。

フォルダ構成が以下のようになっている場合、

.
├── main.py
└── lib
    └── gdrive_api.py

以下のようにインポートします。

from lib.gdrive_api import GoogleDriveAPI

# GoogleDriveAPI クラスのインスタンスを作成
drive = GoogleDriveAPI()

# 例えば、特定のフォルダ名からフォルダIDを取得
folder_id = drive.get_folder_id("MyFolder")

print(folder_id)

このように、Pythonのモジュールシステムを利用することで、一つのファイルに記述したクラスや関数を他のファイルから呼び出して使用することができます。

例1:フォルダ名とファイル名を入力してファイルをダウンロードする

GoogleDriveAPIクラスのdownload_from_file_nameメソッドを使って、指定したフォルダ名とファイル名からファイルをダウンロードする例を以下に示します。

まずは、gdrive_api.pyをインポートします。次に、GoogleDriveAPIクラスのインスタンスを作成し、そのインスタンスのdownload_from_file_nameメソッドを呼び出します。

from gdrive_api import GoogleDriveAPI

# GoogleDriveAPI クラスのインスタンスを作成
drive = GoogleDriveAPI()

# フォルダ名とファイル名を指定
folder_name = "MyFolder"
file_name = "MyFile.txt"

# ダウンロード先のディレクトリを指定(オプション、指定しなければ現在のディレクトリにダウンロードされます)
target_dir = "/path/to/download/directory"

# ファイルをダウンロード
drive.download_from_file_name(folder_name, file_name, target_dir)

このスクリプトを実行すると、MyFolderという名前のフォルダ内にあるMyFile.txtという名前のファイルが、指定したディレクトリにダウンロードされます。

注意点としては、このスクリプトを実行するにはgdrive_api.pyが同じディレクトリに存在しているか、またはPythonのパスが正しく設定されている必要があります。また、GoogleDriveAPIの認証が必要な場合は、適切に認証を行ってください。

例2:フォルダの中で作成日が一番新しいファイルをダウンロードする

GoogleDriveAPIクラスのget_file_idメソッドとgdrive_downloadメソッドを使って、指定したフォルダ内で作成日が一番新しいファイルをダウンロードする例を以下に示します。

from gdrive_api import GoogleDriveAPI

# GoogleDriveAPI クラスのインスタンスを作成
drive = GoogleDriveAPI()

# フォルダ名を指定
folder_name = "MyFolder"

# 作成日が一番新しいファイルのIDを取得
file_id = drive.get_file_id(folder_name=folder_name)

# ファイル名を指定(この例ではダウンロードするファイルの元の名前を使います)
file_name = file_id  # ここでは便宜上、ファイルIDをそのままファイル名として利用します。実際には適切な名前を指定してください。

# ダウンロード先のディレクトリを指定(オプション、指定しなければ現在のディレクトリにダウンロードされます)
target_dir = "/path/to/download/directory"

# ファイルをダウンロード
drive.gdrive_download(file_id=file_id, output_name=file_name, target_dir=target_dir)

このスクリプトを実行すると、MyFolderという名前のフォルダ内で作成日が一番新しいファイルが、指定したディレクトリにダウンロードされます。

以上になります。
上記以外にもいろいろなメソッドが考えられるので、ぜひご自身の用途にあわせて試してみてください。

ちなみにこのモジュールを作ったのは1年くらい前ですが、私の環境では今でもきちんと動いています。
もし、ご自身の環境で動かない場合はご連絡ください。

Discussion