Colaboratoryで認証なしでG Suiteの操作する方法

# 認証なしで実行できるようにしようと思った背景

最近、スプレッドシートの数万のデータを扱うのにGASでは限界を感じていて、Colabで実行したら軽かったのでColabでのスプレッドシート操作が多くなってきました。
ですが、毎回下記の認証をするのが面倒だったので、認証なしで実行できる方法を実装しました。


<img width="923" alt="2020-07-22_09h48_52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428239/ad491efc-457e-b1ff-a3c8-2be0ed797664.png">


```python:Colabでの通常のコード
!pip install gspread

from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread

# 認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

ss_id = 'スプレッドシートのID'
sht_name = 'シート名'

workbook = gc.open_by_key(ss_id)
worksheet = workbook.worksheet(sht_name)
```

Colabではなく、Pythonでスプレッドシートを操作するときは認証が必要ないので、
同じコードで実装できるようにしました。
Pythonでの実装方法は下記を参考にさせて頂きました。

[Pythonでのスプレッドシート操作の方法](https://tanuhack.com/operate-spreadsheet/)

# JSONファイルを用意するための手順
詳細は上記記事を見た方がわかりやすいですが、簡易的な手順は下記となります。

① Spreadsheet API, Drive APIの有効化
https://console.cloud.google.com/apis/library/sheets.googleapis.com
https://console.cloud.google.com/apis/api/drive.googleapis.com
② 認証情報でサービスアカウントの作成し、JSONデータのダウンロード
https://console.cloud.google.com/apis/credentials

# JSONデータをアプリケーションとして公開

Pythonで実装するときはダウンロードしたJSONファイルの場所を指定する必要があります。
しかし、Colabではローカルフォルダからデータの読み込みはできず、ドライブ上のファイルしか指定ができない上、
ドライブ上のファイルを指定するのにも認証が必要でした。
なので、認証に必要なJSONファイルはGASのアプリケーションから取得するようにしました。


```javascript:GAS、JSONデータ
function doGet(e) {

  // ダウンロードしたJSONファイルをそのまま記載
  const json = {
    "type": "service_account",
    "project_id": "",
    "private_key_id": "",
    "private_key": ""
    "client_email": "",
    "client_id": "",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": ""
  };
  
  return ContentService.createTextOutput(JSON.stringify(json)).setMimeType(ContentService.MimeType.JSON);
}
```

GASで上記ファイルを作成後、`ウェブアプリケーションとして導入`を行います。
その時に発行されるURLを下記コードに入れることで認証なしで実行可能となります。
※アクセス範囲は`全員（匿名ユーザを含む）`にしてください。

# スプレッドシート操作をする方法

事前に操作すスプレッドシートに上記JSONファイルで出てきた中の
client_emailのアドレスを共有する必要があるので注意ください。

```python:認証なしでの実行コード
import requests
import gspread
import json
from oauth2client.service_account import ServiceAccountCredentials 

scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']

# 上記で発行されたURLを記載
json_file = 'url'

# URLの情報を辞書型へ変換
key = json.loads(requests.get(json_file).text)

# 通常「ServiceAccountCredentials.from_json_keyfile_name」でJSONファイルのディレクトリを指定するが
# 辞書型データを直接読み込ませる
credentials = ServiceAccountCredentials.from_json_keyfile_dict(key, scope)

gc = gspread.authorize(credentials)

ss_id = 'スプレッドシートのID'
sht_name = 'シート名'

workbook = gc.open_by_key(ss_id)
worksheet = workbook.worksheet(sht_name)
```

ただし、APIを叩ける頻度は100秒間で100回が上限なので、注意ください。
https://console.cloud.google.com/apis/api/sheets.googleapis.com/quotas?hl=ja


# ドライブ操作をする方法


## ドライブ操作するための準備

スプレッドシート同様操作するフォルダに権限を与えるのを忘れずに行ってください。

```python:ドライブ操作するための準備
import json
import requests
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from oauth2client.service_account import ServiceAccountCredentials 

scope = ['https://www.googleapis.com/auth/drive']

# 上記で発行されたURLを記載
json_file = 'url'

# URLの情報を辞書型へ変換
key = json.loads(requests.get(json_file).text)

# 通常「ServiceAccountCredentials.from_json_keyfile_name」でJSONファイルのディレクトリを指定するが辞書を直接読み込ませる
credentials = ServiceAccountCredentials.from_json_keyfile_dict(key, scope)

gauth = GoogleAuth()
gauth.credentials = credentials

drive = GoogleDrive(gauth)
```

## ファイルのアップデート

```python:フォルダにテキストをアップロードする場合
#IDを指定してUPLOADする
FOLDER_ID = ''
f = drive.CreateFile({'title' : 'ファイル名.txt',
             'parents' : [{'id' : FOLDER_ID }]})
f.SetContentString("Hello World !")
f.Upload()
```

## ドライブからフォルダIDやファイルIDを検索する方法

```python:フォルダ名からフォルダIDを取得する関数
def get_folderid(FORDER_NAME):
  """
  フォルダ名からフォルダIDを取得する関数
  """
  FORDER_ID = ''
  for folder_list in drive.ListFile({'q':"mimeType='application/vnd.google-apps.folder'"}):
    FORDER_ID = [folder['id'] for folder in folder_list if folder['title'] == FORDER_NAME][0]
    return FORDER_ID
    if FORDER_ID:
      break
```

```python:フォルダIDとファイル名からファイルIDを取得する関数
def get_fileid(FORDER_ID, FILE_NAME):
  """
  フォルダIDとファイル名からファイルIDを取得する関数
  """
  for file_list in drive.ListFile({'q': f"'{FORDER_ID}' in parents"}):
    FILE_ID = ''
    FILE_ID = [file['id'] for file in file_list if file['title'] == FILE_NAME][0]
    return FILE_ID
    if FILE_ID:
      break
```

## csvデータをpandasで扱う方法

```python:パターン①
import pandas as pd
file_id = 'ファイルID'
file_name = 'ファイル名.csv'

f = drive.CreateFile({'id': file_id})
f.GetContentFile(file_name)

df = pd.read_csv(file_name, encoding='CP932', header=None)
df.head()
```


```python:パターン②
import pandas as pd
from io import StringIO

file_id = 'ファイルID'

downloaded = drive.CreateFile({'id': file_id})
csv = StringIO(downloaded.GetContentString())
df = pd.read_csv(csv)
df.head()
```