PyDrive2 で Google ドライブを操作する

## はじめに

この記事では PyDrive2 を用いて， Python から Google ドライブを操作する方法（ファイルの一覧取得・作成・更新 etc...）について記します．

元々は次の記事で使うために利用した技術です．「なぜ利用したのか？」「なぜこれらの操作が必要になったのか？」等の経緯は元記事をご覧ください．

https://qiita.com/yamamoto-yuta/items/bd53022ee5223c31cb82

## 環境構築・インスタンスの作成

今回は Docker で Python 環境を作りました．その際の詳細は次の記事に記してありますので，気になる方はご覧いただければ幸いです:

https://qiita.com/yamamoto-yuta/items/870d198a66476c1dea4a

また，PyDrive2 では `GoogleDrive` インスタンスを作成することで Google ドライブの操作を行います．詳細は上記記事に記載しています．今回は下記のコードでインスタンスを生成した前提で進めます:

```python
from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive

gauth = GoogleAuth()
gauth.CommandLineAuth()

drive = GoogleDrive(gauth)
```

## ファイル一覧の取得

PyDrive2 では次のコードでファイル一覧を取得できます:

```python
file_list = drive.ListFile().GetList()
```

`ListFile()` の引数に `{'q': <検索クエリ>}` を与えることで，検索条件を指定してファイル一覧を取得することができます．

例えば，次の検索クエリを指定したとします:

```python
f"'{bar/のフォルダID}' in parents and title contains 'hoge' and trashed = False"
```

この場合，検索クエリの意味は次のようになります:

- `'{bar/のフォルダID}' in parents`
    - `bar/` フォルダを親フォルダに持っている
- `title contains 'hoge'`
    - `hoge` という文字列をファイル名に含んでいる
- `trashed = False`
    - ゴミ箱内のファイルは除く
    - これを指定しなかった場合，ゴミ箱内のファイルも検索結果に含まれてしまうので注意

その他の検索クエリは次の公式ドキュメントをご参照ください:

> 公式ドキュメント: [Search query terms and operators  |  Drive API  |  Google Developers](https://developers.google.com/drive/api/guides/ref-search-terms)

> 参考: [Python, PyDriveでGoogle Driveのファイルのリストを作成 | note.nkmk.me](https://note.nkmk.me/python-pydrive-list-file/)

## ファイルの保存

PyDrive2 では，ファイルの保存は Google ドライブ上に新規ファイルを作成することで行います．コードは次の通りです:

```python
# 新規作成したいファイルのインスタンスを作成
file = self.drive.CreateFile({
    "title": file_name,
    "mimeType": mimeType,
    "parents": [{"id": folder_id}]
})
# ファイル内容をセット
file.SetContentString(contents)
# アップロード
file.Upload()
```

`CreateFile()` の引数にパラメータを渡すことで作成するファイルの情報を指定できます．

今回は次のように設定しました:

- `"title": file_name`
  - ファイル名を変数 `file_name` の値にする
- `"mimeType": mimeType`
  - ファイルのMIMEタイプを変数 `mineType` の値にする（例:  `text/html` ）
- `"parents": [{"id": folder_id}]`
  - フォルダIDが `folder_id` のフォルダへ保存する
  - フォルダIDはURL `https://drive.google.com/drive/u/X/folders/xxxxxxxxxx` の `xxxxxxxxxx` の部分に記載されている

## ファイル内容の更新

コードは次の通りです:

```python
# 更新したいファイルのインスタンスを作成
file = self.drive.CreateFile({"id": file_id})
# 更新内容をセット
file.SetContentString(contents)
# アップロード
file.Upload()
```

ファイル内容の更新は， `CreateFile()` の引数に更新したいファイルの ID を指定することで行えます．ファイル ID は URL `https://drive.google.com/file/d/xxxxxxxxxx` の `xxxxxxxxxx` の部分に記載されています．

更新内容は `.SetContentString()` の引数に与えます．なお，更新は前の内容に追記するのではなく上書きする形で行われます．

> 参考:
> * [File management made easy - PyDrive2 1.14.0 documentation](https://docs.iterative.ai/PyDrive2/filemanagement/)
> * [Python, PyDriveでGoogle Driveのダウンロード、アップロード、削除など | note.nkmk.me](https://note.nkmk.me/python-pydrive-download-upload-delete/)

## ファイルのアップロード

コードは次の通りです:

```python
# インスタンスを作成
file = drive.CreateFile({"parents": [{"id": folder_id}]})
# アップロードしたいファイルを指定
file.SetContentFile(file_path)
# そのままだとローカルでのファイルパス＝Googleドライブでのファイル名になってしまうので、
# ローカルでのファイル名＝Googleドライブでのファイル名になるよう変更
file["title"] = PurePath(file_path).name
# アップロード
file.Upload()
```

ローカルにあるファイルを Google ドライブにアップロードするには， `CreateFile()` にアップロード先のフォルダ ID を引数として与え， `.SetContentFile()` にアップロードしたいファイルへのパスを引数として与えます．

ただし，そのままだとローカルでのファイルパスが Google ドライブでのファイル名になってしまうため，ローカルでのファイル名をそのまま使うよう， `file` インスタンスのメタデータを変更しています．

> 参考:
> * [File management made easy - PyDrive2 1.14.0 documentation](https://docs.iterative.ai/PyDrive2/filemanagement/)
> * [Python, PyDriveでGoogle Driveのダウンロード、アップロード、削除など | note.nkmk.me](https://note.nkmk.me/python-pydrive-download-upload-delete/)

## ファイルのダウンロード

コードは次の通りです:

```python
file = drive.CreateFile({"id": file_id})
```

ファイルのダウンロードは， `CreateFile()` の引数にダウンロードしたいファイルのファイル ID を与えることで行えます．

## 上記操作をクラスにまとめてみた

これまで説明した「ファイル一覧取得」～「ファイルのダウンロード」までを，扱いやすいよう 1 つのクラスにまとめて次のように実装してみました．初めに `GDriveClient` インスタンスを作成し，ファイル保存等を行う際は作成したインスタンスからメソッドを呼び出して利用します．

https://github.com/yamamoto-yuta/periodically_scraper/blob/main/periodically_scraper/shared/gdrive_client.py

## 最後に

この記事では PyDrive2 で Google ドライブの操作を行う方法について記しました．ミスやより良い方法などがございましたら，コメント等でご指摘いただければ幸いです :bow:

---

こちらは「ヤプリアドベントカレンダー」の2日目に投稿した記事です．この後も続々と記事が投稿されていきますので，是非ご覧ください:bow:

https://adventar.org/calendars/7721

https://adventar.org/calendars/8018