背景:
最近はFTPでのファイル転送を使うより、GoogleDrive(GSuite)を使ってのファイルのやりとりをしたいとの要望が増えた気がします。
先日はたくさんの画像ファイルをGoogleDrive経由で受け取り、それを加工して弊社サーバーに上げるという作業を行った時に、GoogleDriveに上がっているファイルを一括ダウンロードするPythonスクリプトを用意したのでご紹介します。
設定①:サービスアカウントの準備
GoogleDriveで利用するサービスアカウントキーをGCPから作成します。
サービスアカウントの作成は以下の記事に詳細があります。
ステップ4.3で作成したサービスアカウントで ${入力したアカウント名}@project-${プロジェクトID}.iam.gserviceaccount.com
がメールアドレスのような形で取得できます。これは次の設定②で利用します。
ステップ4.8にて秘密鍵JSONを取得することができるので、それをダウンロードして保持しておきます。
設定②:サービスアカウントをGoogleDriveの共有設定に登録する
GoogleDriveのフォルダ名をクリックすると、「共有」というリンクが表示されるのでクリックします。
共有するユーザー一覧のところに、設定①で作成したサービスアカウントを登録します。
そうすることで、このサービスアカウントを経由してGoogleDriveのファイルを参照することが可能になります。
スクリプト実装
設定①で作成したJSONファイルをPythonから参照できるところに保存しておく。
今回は、'lib/credentials/google-drive-credentials.json'に保存しました。
本記事への投稿用に簡略化していますが以下のスクリプトを実行するとファイルが一括DLできます。
import os
from django.conf import settings
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
from httplib2 import Http
from oauth2client.service_account import ServiceAccountCredentials
SCOPES = ['https://www.googleapis.com/auth/drive']
def main() -> None:
credentials = ServiceAccountCredentials.from_json_keyfile_name(
credentials_file_path(), SCOPES
)
http_auth = credentials.authorize(Http())
service = build('drive', 'v3', http=http_auth)
parent = '1xDiN68Ocz1aXaABCDEFGHIJK' # 先程共有したフォルダのIDでブラウザのURLから取得する
while True:
response = service.files().list(
q=f"'{parent}' in parents" if parent else None,
pageSize=100).execute()
items = response.get('files', [])
if not items: # itemsが存在しない場合中断
print(f'No files. {parent}')
break
for item in items:
request = service.files().get_media(fileId=item.get('id'))
file_path = f"to/save/path/{item.get('name')}"
with open(file_path, 'wb', buffering=0) as fh:
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
status, done = downloader.next_chunk()
print(f'Downloading... {file_path}, {status.total_size / 1024 / 1024}MB')
def credentials_file_path() -> str:
"""秘密鍵を保存しているファイルパス"""
path = os.path.join(
settings.BASE_DIR,
'lib',
'credentials',
'google-drive-credentials.json' # 設定①で取得したJSONファイルを格納した場所
)
return path
main()
改善① フォルダ内のファイルが100件以上の場合はnextPageTokenを利用する
response.get('nextPageToken')で2ページ目・3ページ目・・・と次の100件が取得できるようになります。
page_token = None # Noneの場合はページャーで初期値
while True:
response = service.files().list(
q=f"'{parent}' in parents" if parent else None,
pageToken=page_token,
pageSize=100).execute()
items = response.get('files', [])
page_token = response.get('nextPageToken') # 対象件数が100件以上あれば、このpage_tokenを用いて次のページを取得する
if not items: # itemsが存在しない場合中断
print(f'No files. {parent}')
break
改善②: このままだとTeamDriveという機能を使っている組織とのファイル共有ができない。
TeamDriveでGoogleDriveの権限を制御している場合は、ファイルリストを取得する時に includeItemsFromAllDrives=True というパラメータを付与する必要があります。そうしないとファイルリストが取得できませんでした。
response = self.service.files().list(
q=f"'{parent}' in parents" if parent else None,
includeItemsFromAllDrives=True, # TeamDriveも含めて参照することができる
pageToken=page_token,
pageSize=100).execute()
このあたりのパラメータは最近も変更されているので、公式ドキュメントを参照することをオススメします。
改善③:共有フォルダの中に更にフォルダがある場合は再帰的に取得する。
def walk(parent:str = None):
....
items = response.get('files', [])
....
for item in items
if item.get('mimeType') == 'application/vnd.google-apps.folder': # itemがフォルダであることを判定
# 再帰的に取得する
return walk(item.get('id'))
else:
....# ファイルをダウンロードする
まとめ:
・GoogleDriveからファイルを一括ダウンロードする設定とPythonスクリプトをご紹介。
・GoogleDriveの認証に今回はサービスアカウントを使ってみました。前はOAuthでやったことあったのですが、サービスアカウントの方が楽に参照できる範囲を絞れたりしてとても良いです😁
良いクリスマスを!