gspread(およびgspread_dataframe)の利用方法について

pythonからスプレッドシートを触るとき、gspread/gspread_dataframeなどのライブラリを利用することができます。

これらライブラリについて内部実装を見ながら、どのような使い方をするかまとめました。

前提・方針

  • スプレッドシートの操作はサービスアカウントを利用する
    • Cloud FunctionsやCloud Runからスプレッドシートを自動更新できるようにするため
    • ローカル環境では、サービスアカウントにimpersonateする
      • セキュリティの観点から、サービスアカウントの秘密鍵をローカルにダウンロードしないようにするため
  • なるべく、スプレッドシートへのアクセスは最小限にする
    • Sheets APIではユーザごとに60リクエスト/分、プロジェクトごとに300リクエスト/分に制限されているため1

本書では書かないこと

以下の内容については、他の技術記事等で掲載されているため記載を省略いたします。

注記

説明のため、以下の略記を使います。

また、gspreadの各関数は内部的にSheets APIを利用しています。そのため、リクエストやサーバの状況によっては4xx, 5xxエラーが返ることがあります。そのため、各関数を利用するときはgspread.exceptions.APIError例外をハンドルすることが推奨されます。

動作確認したバージョン

  • gspread: 5.2.0
  • gspread_dataframe: 3.2.2

スプレッドシート・ワークシートを開くための準備

クライアント取得

実装例

ローカル環境用のimpersonate済みcredentialsがあれば引数から利用します。

もしなければ(=GCP上で動作していれば)、ADC(Application Default Credentials)によりサービスアカウントの認証情報を得ます。

from typing import Optional

from google.auth.credentials import Credentials
from gspread import authorize
from gspread.client import Client


def get_client(credentials: Optional[Credentials]) -> Client:
  if credentials is None:
      credentials, _ = auth.default(
          scopes=['https://spreadsheets.google.com/feeds',
                  'https://www.googleapis.com/auth/drive'])
  return authorize(credentials)

スプレッドシートを開く

gspread.Clientの以下いずれかの関数を使います。

以下、例外の一覧です。open_by_key, open_by_urlについては公式リファレンスに書かれていない例外があるのでご注意ください(以後の関数について、gspread.APIErrorの記載は省略します)。

例外(open)

  • gspread.SpreadsheetNotFound: スプレッドシートが存在しないときの例外
  • gspread.APIError: またはSheets APIに対し4xx, 5xx系のHTTPステータスが返ったとき

例外(open_by_key)

  • gspread.APIError: スプレッドシートが存在しない場合、またはSheets APIに対し4xx, 5xx系のHTTPステータスが返ったとき

例外(open_by_url)

  • gspread.NoValidUrlKeyFound: URLが不正の場合
  • gspread.APIError: スプレッドシートが存在しない場合、またはSheets APIに対し4xx, 5xx系のHTTPステータスが返ったとき

実装例

ここではopen_by_keyのみ記載します。

from gspread import Spreadsheet
from gspread.exceptions import APIError
from gspread.client import Client


def open_spreadsheet(client: Client, spreadsheet_id: str) -> Spreadsheet | None:
    try:
        return client.open_by_key(spreadsheet_id)
    except APIError:
        # 厳密にエラーハンドリングする場合、APIErrorのcodeパラメータ(HTTPステータスに相当)を見てハンドリングしてください
        print("Spreadsheet (id: {spreadsheet_id}) not found or service not available!")
        return None

ワークシートを取得する(ワークシートの有無を確認する)

gspread.Spreadsheetのworksheet関数を使います。

引数:

  • title(str): ワークシート名

例外

  • gspread.Spreadsheet.WorksheetNotFound: ワークシートが存在しない場合

実装例

以下の例でワークシートを取得できます。また、ワークシートのインスタンスを取得できるかどうかでワークシートの有無を確認できます。

from typing import Optional

from gspread import Spreadsheet, Worksheet, WorksheetNotFound


def get_worksheet(book: Spreadsheet, sheet_name: str) -> Optional[Worksheet]:
    try:
        return book.worksheet(sheet_name)
    except WorksheetNotFound:
        return None

ワークシートの操作

シートの値全てを取得

gspread.Worksheetのget_values関数を使います。本関数はSheets APIspreadsheets.values.getに相当します。なお、get_all_valuesはdeprecatedとなっているので、利用は推奨されません。

引数:

  • range_name(str, optional): どの範囲を取得するか。"A1:B5"などの形式。
  • major_dimension("ROWS"か"COLUMNS", optional): 行ごとに取得するか、列ごとに取得するか。デフォルトはROWS
  • value_render_option("FORMATTED_VALUE"か"UNFORMATTED_VALUE"か"FORMULA", optional): 数式・フォーマット設定の有無。"FORMATTED_VALUE"は数式計算+フォーマットあり、"FORMULA"は数式計算のみ、"UNFORMATTED_VALUE"はどちらもなし。デフォルトはFORMATTED_VALUE?

実装例

from dataclasses import dataclass
from enum import Enum

from gspread import Worksheet


class RowNo(Enum)
    ID = 0
    HOGE = 1


@dataclass
class DataModel
    id: str
    hoge: int


def fetch_values(worksheet: Worksheet) -> list[DataModel]:
    rows: list = worksheet.get_values()
    return [DataModel(
                id=row[RowNo.ID],
                hoge=int(row[RowNo.ID], 10) if (rows[RowNo.ID].strip) > 0 else None,
            ) for row in rows[1:]]    # 1行目はヘッダと仮定して飛ばす

シートの特定領域にまとめて値を書き込み

gspread.Worksheetのvalues_update関数を使います。本関数はSheets APIspreadsheets.values.update APIに相当します。

引数

  • range(str): 更新する範囲。"A1:B3"など
  • body(list[list], optional): 書き込む2次元のリスト。同上APIのbodyに相当する
  • params(dict, optional): updateオプション。同上APIのクエリパラメータに相当する

実装例

def values_update(spreadsheet_id: str, sheet_name: str, data: list[DataModel]):
    worksheet = get_worksheet(spreadsheet_id, sheet_name)
    if worksheet is None:
        return
    worksheet_values = ([self.to_values(d) for d in data])
    column_alphabet = chr(len(worksheet_values[0]) + 64)    # 列数は26までと仮定
    worksheet_range = \
        f"{sheet_name}!A2:{column_alphabet}{len(worksheet_values)+1}"

    try:
        worksheet.spreadsheet.values_update(
            range=worksheet_range,
            body=worksheet_values,
            params={
                'valueInputOption': 'USER_ENTERED'
            }
        )
    except Exception:
        print(f"failed to write to worksheet {sheet_name}")
        print(traceback.format_exc())

    @staticmethod
    def to_values(data: DataModel) -> list:
        # dataの属性分だけ増やす
        return [
            data.id,
            data.hoge,
        ]

参考1: 列の番号をアルファベット表記に置き換えたい

列はA~ZZZ列2までなので、以下で表現できます。

ALPHABET_NUM = 26

def to_row_alphabet(row_no: int) -> str:
    def to_alphabet(no: int) -> str:
        if no < 0:
            return ""
        return chr((no % ALPHABET_NUM) + 1 + 64)

    n1 = row_no
    n2 = ((row_no - ALPHABET_NUM) // ALPHABET_NUM)
    n3 = ((row_no - ALPHABET_NUM ** 2 - ALPHABET_NUM) // (ALPHABET_NUM ** 2))

    return f"{to_alphabet(n3)}{to_alphabet(n2)}{to_alphabet(n1)}".strip(' ')

参考2: pandas.DataFrameを使ってシートを読み書きしたい

gspread_dataframeライブラリを使用します。このライブラリはgspreadライブラリをwrapした関数群を提供します。

参考ページ: pandas.Dataframeからスプレッドシートに簡単に反映するgspread-dataframeライブラリ

読み込み、書き込みは以下のSheets APIを1度のみリクエストしています。

  • 読み込み: spreadsheets.values.get
  • 書き込み: spreadsheets.values.update

ただし、ワークシートを新規追加する場合はさらにリクエスト数が増えます。

読み込み - gspread_dataframe.get_as_dataframe

指定されたワークシートの値を全て取得した上で、オプションに指定された条件を満たすpd.DataFrameを返します。

from gspread_dataframe import get_as_dataframe

df = get_as_dataframe(worksheet)

引数:

  • worksheet(gspread.Spreadsheet.Worksheet): ワークシートのインスタンス
  • evaluate_formulas(bool, optional): 数式を評価し、値として読み込むか?
  • その他、pd.read_csvで使えるオプション(headers, usecols, skiprows, parse_datesなど。いずれもoptional)

書き込み - gspread_dataframe.set_with_dataframe

データフレームの内容を、指定されたワークシートに書き込みます。

from gspread_dataframe import set_with_dataframe

set_with_dataframe(worksheet, df)

引数:

  • worksheet(gspread.spreadsheet.Worksheet): ワークシートのインスタンス
  • dataframe(pd.DataFrame): pandasデータフレーム
  • row(int): どの列から書き込むか。デフォルトは1(最初の列)
  • col(int): どの行から書き込むか。デフォルトは1(最初の行)
  • include_index(bool): indexも書き込むか。デフォルトはFalse
  • include_column_header(bool): 列名も書き込むか。デフォルトはTrue
  • resize(bool): ワークシートのサイズをデータフレームにピッタリ合うように変形させるか。デフォルトはFalse。Falseの場合、ワークシートの方がサイズが小さいときのみ変形する。
  • allow_formulas(bool): 数式をそのまま数式として書き込むか。デフォルトはTrue
  • string_escaping(string | Callable[str, bool]): 文字列のエスケープ方法をどうするか。"default"なら"'"から始まる値のみ、"off"なら何もしない、"full"なら全ての値、CallableならTrueを返したセル値をエスケープする