Python, PyDriveでGoogle Driveのフォルダ作成、ファイル移動、一括処理
PyDriveを使うとPythonからGoogleドライブを簡単に操作できる。
ここでは、Googleドライブ上のフォルダ関連の操作について説明する。
- Googleドライブにフォルダを作成
- ローカルのファイルを指定のフォルダにアップロード
- Googleドライブのファイルを指定のフォルダに移動
- Googleドライブのフォルダを削除
- フォルダ内のファイルのリストを作成
- 一階層のみリスト化
- サブフォルダの中身まで再帰的にリスト化
- フォルダ内のファイルを一括処理(ダウンロードなど)
PyDriveの基本的な使い方(利用登録や認証、ファイルのダウンロード、アップロード、削除など)は以下の記事を参照。
なお、以下はPyDriveバージョン1.3.1
の情報。
Googleドライブにフォルダを作成
フォルダの作成もファイルの作成と同様、CreateFile()
でGoogleDriveFile
オブジェクトを作成しUpload()
する、という流れ。
import pprint
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
gauth = GoogleAuth()
gauth.LocalWebserverAuth()
drive = GoogleDrive(gauth)
f_folder = drive.CreateFile({'title': 'new_folder',
'mimeType': 'application/vnd.google-apps.folder'})
print(f_folder)
# GoogleDriveFile({'title': 'new_folder', 'mimeType': 'application/vnd.google-apps.folder'})
f_folder.Upload()
pprint.pprint(f_folder)
# {'alternateLink': 'https://drive.google.com/drive/folders/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'appDataContents': False,
# 'capabilities': {'canCopy': False, 'canEdit': True},
# 'copyRequiresWriterPermission': False,
# 'copyable': False,
# 'createdDate': '2019-07-16T14:04:39.276Z',
# 'editable': True,
# 'embedLink': 'https://drive.google.com/embeddedfolderview?id=1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'etag': '"_sqIxUq0fTLFIA17mBQDotbHWsg/MTU2MzI4NTg3OTI3Ng"',
# 'explicitlyTrashed': False,
# 'iconLink': 'https://drive-thirdparty.googleusercontent.com/16/type/application/vnd.google-apps.folder+48',
# 'id': '1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'kind': 'drive#file',
# 'labels': {'hidden': False,
# 'restricted': False,
# 'starred': False,
# 'trashed': False,
# 'viewed': True},
# 'lastModifyingUser': {'displayName': 'nkmk on',
# 'emailAddress': 'nkmk.on@gmail.com',
# 'isAuthenticatedUser': True,
# 'kind': 'drive#user',
# 'permissionId': '15529215658844670952'},
# 'lastModifyingUserName': 'nkmk on',
# 'lastViewedByMeDate': '2019-07-16T14:04:39.276Z',
# 'markedViewedByMeDate': '1970-01-01T00:00:00.000Z',
# 'mimeType': 'application/vnd.google-apps.folder',
# 'modifiedByMeDate': '2019-07-16T14:04:39.276Z',
# 'modifiedDate': '2019-07-16T14:04:39.276Z',
# 'ownerNames': ['nkmk on'],
# 'owners': [{'displayName': 'nkmk on',
# 'emailAddress': 'nkmk.on@gmail.com',
# 'isAuthenticatedUser': True,
# 'kind': 'drive#user',
# 'permissionId': '15529215658844670952'}],
# 'parents': [{'id': '0AAeKIFCqYN07Uk9PVA',
# 'isRoot': True,
# 'kind': 'drive#parentReference',
# 'parentLink': 'https://www.googleapis.com/drive/v2/files/0AAeKIFCqYN07Uk9PVA',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz/parents/0AAeKIFCqYN07Uk9PVA'}],
# 'quotaBytesUsed': '0',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'shared': False,
# 'spaces': ['drive'],
# 'title': 'new_folder',
# 'userPermission': {'etag': '"_sqIxUq0fTLFIA17mBQDotbHWsg/38oVvgHkBS5xDYba_gRDJx2lqsQ"',
# 'id': 'me',
# 'kind': 'drive#permission',
# 'role': 'owner',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz/permissions/me',
# 'type': 'user'},
# 'version': '1',
# 'writersCanShare': True}
ポイントは'mimeType'
。'mimeType'
を'application/vnd.google-apps.folder'
に設定するとフォルダとして扱われる。
なお、ファイルの場合はSetContentFile()
でローカルのファイルをセットしてアップロードできるが、SetContentFile()
にフォルダ(ディレクトリ)のパスを指定するとエラーになる。
f_folder = drive.CreateFile()
# f_folder.SetContentFile('src')
# IsADirectoryError: [Errno 21] Is a directory: 'src'
ローカルのフォルダの中身をアップロードするには、glob()
などでローカルのファイルのパスの一覧を取得し、SetContentFile()
でひとつずつアップロードすればよい。
ローカルのファイルをGoogleドライブの指定のフォルダにアップロードする方法については次に説明する。
ローカルのファイルを指定のフォルダにアップロード
Googleドライブにおけるファイルやフォルダは親フォルダ(直上のフォルダ)の情報を保持している。
親フォルダの情報はGoogleDriveFile
オブジェクトでは'parents'
に格納されている。
したがって、ローカルのファイルをGoogleドライブ上の指定のフォルダにアップロードしたい場合は、'parents'
に所望のフォルダのIDを指定すればよい。
ここではフォルダ名から検索してフォルダのIDを取得している。詳細は以下の記事を参照。
folder_id = drive.ListFile({'q': 'title = "new_folder"'}).GetList()[0]['id']
f = drive.CreateFile({"parents": [{"id": folder_id}]})
f.SetContentFile('src/lena.jpg')
f['title'] = 'lena.jpg'
f.Upload()
print(type(f['parents']))
# <class 'list'>
pprint.pprint(f['parents'])
# [{'id': '1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'isRoot': False,
# 'kind': 'drive#parentReference',
# 'parentLink': 'https://www.googleapis.com/drive/v2/files/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1nUWOO8QWyqE13PMW4fzemKF8gcsOCXkJ/parents/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz'}]
print(f['parents'][0]['id'] == folder_id)
# True
サンプルコードからも分かるように、'parents'
はIDなどの情報を含む辞書のリスト。リストなので設定するときも[]
が必要。
'parents'
がリストなのは、Googleドライブにおける親フォルダは実際はタグのような扱いであるため。フォルダとして考えると違和感があるが、'parents'
に複数のフォルダを設定できる。
Googleドライブのファイルを指定のフォルダに移動
Googleドライブ上のファイルを指定のフォルダに移動する場合も考え方はアップロードと同じ。'parents'
の'id'
を更新すればよい。
メタデータの更新についての詳細は以下の記事を参照。
ルートフォルダ(マイフォルダ)に移動する例。ルートフォルダのIDは'root'
で代用可。
f = drive.CreateFile({'id': file_id})
f.FetchMetadata()
pprint.pprint(f['parents'])
# [{'id': '1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'isRoot': False,
# 'kind': 'drive#parentReference',
# 'parentLink': 'https://www.googleapis.com/drive/v2/files/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1nUWOO8QWyqE13PMW4fzemKF8gcsOCXkJ/parents/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz'}]
f['parents'] = [{'id': 'root'}]
f.Upload()
pprint.pprint(f['parents'])
# [{'id': '0AAeKIFCqYN07Uk9PVA',
# 'isRoot': True,
# 'kind': 'drive#parentReference',
# 'parentLink': 'https://www.googleapis.com/drive/v2/files/0AAeKIFCqYN07Uk9PVA',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1nUWOO8QWyqE13PMW4fzemKF8gcsOCXkJ/parents/0AAeKIFCqYN07Uk9PVA'}]
任意のフォルダに移動する例。該当のフォルダのIDを指定する。
folder_id = drive.ListFile({'q': 'title = "new_folder"'}).GetList()[0]['id']
f['parents'] = [{'id': folder_id}]
f.Upload()
pprint.pprint(f['parents'])
# [{'id': '1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'isRoot': False,
# 'kind': 'drive#parentReference',
# 'parentLink': 'https://www.googleapis.com/drive/v2/files/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1nUWOO8QWyqE13PMW4fzemKF8gcsOCXkJ/parents/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz'}]
フォルダのIDはブラウザでGoogleドライブを開いてURLから確認することもできる。末尾がIDになっている。
https://drive.google.com/drive/u/0/folders/<ID>
なお、フォルダを別のフォルダに移動させたい場合も、同様に'parents'
の'id'
を更新すればよい。
Googleドライブのフォルダを削除
ファイルと同様、フォルダの削除にはTrash()
, UnTrash()
, Delete()
を使う。
注意点はフォルダ内のファイルやフォルダ('parents'
のIDがそのフォルダであるファイルやフォルダ)の扱い。フォルダを削除するとフォルダ内のファイルやフォルダも削除される。
ゴミ箱に入れるTrash()
の例。ファイルとその親フォルダのIDからGoogleDriveFile
オブジェクトを生成してフォルダをゴミ箱に入れる。
メタデータの'labels'
の'trashed'
はそのファイルがゴミ箱に入っているかどうかを示し、'explicitlyTrashed'
はそのオブジェクト自体が直接ゴミ箱に入れられたか、親フォルダとともに再帰的にゴミ箱に入れられたかを示す。
file_id = drive.ListFile({'q': 'title = "lena.jpg"'}).GetList()[0]['id']
f_file = drive.CreateFile({'id': file_id})
pprint.pprint(f_file['parents'])
# [{'id': '1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'isRoot': False,
# 'kind': 'drive#parentReference',
# 'parentLink': 'https://www.googleapis.com/drive/v2/files/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz',
# 'selfLink': 'https://www.googleapis.com/drive/v2/files/1nUWOO8QWyqE13PMW4fzemKF8gcsOCXkJ/parents/1dQT4GYkl2zHDP_7yXYcKuomX6XggF9Cz'}]
f_folder = drive.CreateFile({'id': f_file['parents'][0]['id']})
f_folder.Trash()
f_folder.FetchMetadata()
print(f_folder['labels']['trashed'])
# True
print(f_folder['explicitlyTrashed'])
# True
f_file.FetchMetadata()
print(f_file['labels']['trashed'])
# False
print(f_file['explicitlyTrashed'])
# False
ゴミ箱から出すUnTrash()
。
f_folder.UnTrash()
f_folder.FetchMetadata()
print(f_folder['labels']['trashed'])
# False
print(f_folder['explicitlyTrashed'])
# False
f_file.FetchMetadata()
print(f_file['labels']['trashed'])
# False
print(f_file['explicitlyTrashed'])
# False
完全に削除するDelete()
。フォルダ内のファイルも完全に削除されるので注意。
f_folder.Delete()
# f_folder.FetchMetadata()
# ApiRequestError: <HttpError 404 when requesting https://www.googleapis.com/drive/v2/files/144fD0jujJEyCdaM5jv8Fgp-czXLZNfBe?alt=json returned "File not found: 144fD0jujJEyCdaM5jv8Fgp-czXLZNfBe">
# f_file.FetchMetadata()
# ApiRequestError: <HttpError 404 when requesting https://www.googleapis.com/drive/v2/files/1-j36c901moQ3gsTXwnvwLK3lRRKhS1CT?alt=json returned "File not found: 1-j36c901moQ3gsTXwnvwLK3lRRKhS1CT">
フォルダ内のファイルのリストを作成
PyDriveを利用したGoogleドライブのファイル・フォルダのリスト生成についての詳細は以下の記事を参照。
ここでは特にフォルダ内のファイル・フォルダのリスト生成について説明する。以降のサンプルコードでは、以下の構造のファイル・フォルダを例とする。
root
├── dir1
│ ├── file11
│ ├── file12
│ └── subdir1
│ └── file111
├── dir2
│ └── file21
├── file1
└── file2
一階層のみリスト化
ListFile()
の引数の'q'
に'"<folder_id>" in parents
という条件を指定する。GoogleDriveFile
オブジェクトのリストが得られる。
folder_id = drive.ListFile({'q': "title = 'dir1'"}).GetList()[0]['id']
file_list = drive.ListFile({'q': '"{}" in parents and trashed = false'.format(folder_id)}).GetList()
print(type(file_list))
# <class 'list'>
print(type(file_list[0]))
# <class 'pydrive.files.GoogleDriveFile'>
for f in file_list:
print(f['title'], ' \t', f['id'])
# subdir1 1CpPdnFTdN6DWPz7VBM1pV-RMZfcbvzck
# file12 1iv9UB1pi65MqfFmqYwBoz3AfZbKr2jLx
# file11 15sy12U3d845pXjLaEWww4VLO9Vs7C4J9
上の例ではtrashed = false
という条件もつけている。上述のように、Googleドライブにおいてはゴミ箱に入っているかどうかはメタデータの'labels'
の'trashed'
で判定され、親フォルダを示すparents
は元のまま。trashed = false
がないと、ゴミ箱に入っているファイルやフォルダもリストアップされてしまう。要注意。
サブフォルダの中身まで再帰的にリスト化
ListFile()
でparents
の条件を指定するだけだと、さらに下の階層、サブフォルダの中のファイルやフォルダはリスト化できない。
サブフォルダの中まで対象とするには、以下のように再帰的な関数が考えられる。
def get_list_recursively(parent_id, l=None):
if l is None:
l = []
file_list = drive.ListFile({'q': '"{}" in parents and trashed = false'.format(parent_id)}).GetList()
l += file_list
for f in file_list:
if f['mimeType'] == 'application/vnd.google-apps.folder':
get_list_recursively(f['id'], l)
return l
for f in get_list_recursively(folder_id):
print(f['title'], ' \t', f['id'])
# subdir1 1CpPdnFTdN6DWPz7VBM1pV-RMZfcbvzck
# file12 1iv9UB1pi65MqfFmqYwBoz3AfZbKr2jLx
# file11 15sy12U3d845pXjLaEWww4VLO9Vs7C4J9
# file111 1bfc7jlDobxuoyZeF8CDyXlN66zl84Ech
フォルダを除外してファイルのみを対象としたい場合は以下の通り。
def get_list_file_recursively(parent_id, l=None):
if l is None:
l = []
file_list = drive.ListFile({'q': '"{}" in parents and trashed = false'.format(parent_id)}).GetList()
l += [f for f in file_list if f['mimeType'] != 'application/vnd.google-apps.folder']
for f in file_list:
if f['mimeType'] == 'application/vnd.google-apps.folder':
get_list_file_recursively(f['id'], l)
return l
for f in get_list_file_recursively(folder_id):
print(f['title'], ' \t', f['id'])
# file12 1iv9UB1pi65MqfFmqYwBoz3AfZbKr2jLx
# file11 15sy12U3d845pXjLaEWww4VLO9Vs7C4J9
# file111 1bfc7jlDobxuoyZeF8CDyXlN66zl84Ech
フォルダ内のファイルを一括処理(ダウンロードなど)
ファイル(GoogleDriveFile
オブジェクト)のリストが取得できればそれを元に一括処理を行うのは簡単。
これまでの例ではprint(f['title'], ...)
などのようにファイル名などを出力していたが、そこでファイルのダウンロードやリネームなどの処理を行えばよい。
GetContentFile()
でダウンロードする例。
for f in get_list_file_recursively(folder_id):
f.GetContentFile(os.path.join('dst', f['title']))
リスト自体が必要ない場合、特に再帰的に大量のファイルを処理するのであれば、一旦リスト化してから処理するよりも直接処理するほうが効率的。
ダウンロードの場合はフォルダ構造を保持したままダウンロードすることもできる。
def download_file_recursively(parent_id, dst_dir):
os.makedirs(dst_dir, exist_ok=True)
file_list = drive.ListFile({'q': '"{}" in parents and trashed = false'.format(parent_id)}).GetList()
for f in file_list:
if f['mimeType'] == 'application/vnd.google-apps.folder':
download_file_recursively(f['id'], os.path.join(dst_dir, f['title']))
else:
dst_path = os.path.join(dst_dir, f['title'])
f.GetContentFile(dst_path)
print('Download {} to {}'.format(f['title'], dst_path))
download_file_recursively('root', 'dst/root')
# Download file21 to dst/root/dir2/file21
# Download file111 to dst/root/dir1/subdir1/file111
# Download file12 to dst/root/dir1/file12
# Download file11 to dst/root/dir1/file11
# Download file2 to dst/root/file2
# Download file1 to dst/root/file1
なお、ブラウザのGoogleドライブではフォルダをZIPでまとめてダウンロードできる。Python / PyDriveで自動化する必要がなければブラウザでアクセスして手動でダウンロードするほうがはるかに簡単。