2023-11-29

PythonからGoogle Sheetsを操作する

python

株の評価額を自動で管理したかったのでpythonからGoogle Sheetsを操作する方法を調べた。 調べてみると実はあまり事例がなかったのと、Pythonでのチュートリアル以上の良いドキュメントが見当たらなかった。 そのため施行錯誤しつつ以下の一次資料を参考に実装した。

コンセプト

  • 操作する対象のスプレッドシートのIDは既知。
  • 1セルずつ取得・更新できればいい = バッチでの更新は対応しない。
  • 左上からのオフセットで各セルにアクセスしたい。
  • 自動化前提なのでサービスアカウントで認証処理はする。

前準備

セットアップを随分前に行ったので記憶が無い。 公式チュートリアルに記載のある、Google Sheets APIの有効化は確実に行っているがその他OAuth関連のセットアップはやっていない気がする。 この記事に記載のある通り、 サービスアカウントを作成したあとに、対象のスプレッドシートの共有管理からサービスアカウントのアドレスを編集者として登録するだけで大丈夫な気がする。

実装

SheetID、シート名、認証情報を引数に取ってset_value, get_valueで操作するシンプルなクラス。 スコープを指定していないが、今回は直接Spreadsheetからサービスアカウントの編集を許可しているせいか問題なく動いてしまった。 OAuthを使ってユーザーから操作の認可を受ける場合などにはおそらく必要になるのだろう。(よくしらない)

python
Copied!
import json
import os.path

from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build


class SpreadSheetError(Exception):
    pass


class OffsetTable:

    def __init__(self, sheet_id: str, sheet_name: str, credential_str: str) -> None:
        self.sheet_id = sheet_id
        self.sheet_name = sheet_name

        # Init credential
        credential = parse_credential(credential_str)

        # Init sheet
        service = build('sheets', 'v4', credentials=credential)
        self.sheet = service.spreadsheets()

    def set_value(self, x: int, y: int, value: any) -> None:
        try:
            address = f"{self.sheet_name}!R{x}C{y}"
            body = {"values": [[value]]}
            self.sheet.values().update(spreadsheetId=self.sheet_id, range=address, body=body, valueInputOption="RAW").execute()
        except Exception as e:
            raise SpreadSheetError(f"Failed to access sheet: {e}")

    def get_value(self, x: int, y: int) -> any:
        try:
            address = f"{self.sheet_name}!R{x}C{y}"
            result = (
                self.sheet.values()
                .get(spreadsheetId=self.sheet_id, range=address)
                .execute()
            )
            values = result.get('values', [])
            if not values:
                raise RuntimeError('Failed to access sheet.')
            return values[0]
        except Exception as e:
            raise SpreadSheetError(f"Failed to access sheet: {e}")


def parse_credential(credential: str) -> Credentials:
    # 環境変数に無理に入れていたりすると余計なクォテーションがついている可能性があるので落とす
    credential = credential.strip("\"\'\n ")
    token_dict = json.loads(credential)
    credentials = Credentials.from_service_account_info(token_dict)
    return credentials

おわりに

全部実装したあとにライブラリがあることを知った。絶望。

gspread で Python から Google スプレッドシートを扱えるようにするまで | zenn