2018年5月31日木曜日

"Invalid value" というエラーに悩まされながら Python から Google スプレッドシートを更新した

はい、表題の通りです。日本語の情報がなかなか見当たらず難儀したのでここに記しておきます。目的としてはRaspberry Piで自室のCO2濃度を測定しGoogleスプレッドシートにログを記録していくだけの簡単なお仕事です。


まずはGoogleの開発者アカウントとAPIプロジェクトを行い(この辺は大昔に済ませてあったので省略)、OAuthに使うクライアントIDを認証情報のページから作成。この時のミソは何はともあれ用途を「その他」にしておくこと。最終的にモバイルアプリやウェブアプリを開発するのが目的だとしても、手前のテストやら何やらを行うためにコンソールから自分のアカウントで認証するには「その他」でないとうまくいかない。


クライアントIDが取得できたらサーバーサイドの準備に入る。まずGoogleのAPIを使うためのライブラリ「google-api-python-client」をpipでインストールしたら、下記のコードを実行して自分のアカウントで認証してしまう。
from oauth2client.file import Storage from oauth2client import client,tools import argparse flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() flags.noauth_local_webserver = True SCOPES = 'https://www.googleapis.com/auth/spreadsheets' # 読み取り専用でよい場合は最後の部分をspreadsheets.readonlyにすれば読取権限のみの認証となる CLIENT_SECRET = '/media/hdd1/client_secret.json' # クライアントIDを作成した際に出力したclient_secret.jsonの格納先を指定 credential_path = '/media/hdd1/python/.spread' # このパスに認証情報が保存される store = Storage(credential_path) flow = client.flow_from_clientsecrets(CLIENT_SECRET, SCOPES) credentials = tools.run_flow(flow, store, flags) そして得られるURLにアクセスし画面に表示された認証キーを入力すれば準備完了。credential_pathに認証情報が自動で保存されるため、プログラムからはこのファイルを読み込めばアカウントにアクセス可能になる。


そして本番のコードがこちら。
from apiclient import discovery from oauth2client.file import Storage import httplib2 store = Storage('/media/hdd1/python/.spread') # これだけで認証情報を読み戻せる credentials = store.get() http = credentials.authorize(httplib2.Http()) discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?version=v4') service = discovery.build('sheets', 'v4', http=http,discoveryServiceUrl=discoveryUrl) spreadsheetId = 'xxxxxxxxxxxxxxxxxxxxx' service.spreadsheets().batchUpdate( spreadsheetId = spreadsheetId, body = { 'requests': [ {'insertDimension': {'range': {'dimension': 'ROWS', 'startIndex':1, 'endIndex':2} } } ] } ).execute() service.spreadsheets().values().update( spreadsheetId = spreadsheetId, range = 'A2:B2', valueInputOption = 'USER_ENTERED', body = { 'values': [[datetime.now().strftime('%Y/%m/%d %H:%M:%S'), result]] } ).execute() なお spreadsheetId というのは、ターゲットとなるスプレッドシートのURLに含まれている。 https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxx/edit
念のため説明しておくと1度目のexecuteで2行目の上に1行を挿入し、挿入された2行目の部分に2度目のexecuteで値を書き込んでいる。ここでカギになるのが2度目のexecuteのbodyに含まれるvaluesはリストのリストであるということ。書き込むセルが1行しかなかろうとも、仮に1セルしかなかろうとも、常にリストのリストでなければならない(リストじゃなくてタプルでも多分大丈夫だろうけど)。名誉のためにどのサイトだったかは明示しませんが(というかいちいち記憶していませんが)参考にしていたサイトのサンプルコードが body = { 'values': [hoge, foo]] } みたいな感じだったので単純にリストを渡せばいいんだろうと思い込んで解決までに1週間くらい検索しまくってたので、誰かの役に立てられればと思い。


ちなみに公式ドキュメントのサンプルコードはというと

ときちんと表記されています。

結論:困ったら公式ドキュメントを読もう!

0 件のコメント:

コメントを投稿