LoginSignup
40
32

More than 5 years have passed since last update.

Google Sheets API v4 と サービスアカウントと Go でスプレッドシートを操作する

Posted at

Go で書いたCLIのプログラムから Google Sheets API v4 使ってスプレッドシートを操作したときのメモ。

使用した Go のバージョンは 1.6 です。

サービスアカウント

  1. Google Developer Console でプロジェクトを作成する
  2. API Manager の「ライブラリ」からGoogle Sheets API を有効にする
  3. API Manager の「認証情報」からサービスアカウントを作成する。作成すると秘密鍵が書かれたJSONファイルをダウンロードできるので大事に取っておく

スプレッドシート

  1. スプレッドシートを作成する。URLからスプレッドシートIDがあるので、メモしておく。
    • https://docs.google.com/spreadsheets/d/<スプレッドシートID>/
  2. スプレッドシートの共有設定から、サービスアカウントのメールアドレス指定して、サービスアカウントからのアクセスを許可する。

Go からスプレッドシートにアクセス

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"

    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"
    "google.golang.org/api/sheets/v4"
)

func httpClient(credentialFilePath string) (*http.Client, error) {
    data, err := ioutil.ReadFile(credentialFilePath)
    if err != nil {
        return nil, err
    }
    conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spreadsheets")
    if err != nil {
        return nil, err
    }

    return conf.Client(oauth2.NoContext), nil
}

func main() {
    spreadsheetId := "スプレッドシートのURLから抽出したID"
    credentialFilePath := "秘密鍵のJSONファイルのパス"

    client, err := httpClient(credentialFilePath)
    if err != nil {
        log.Fatal(err)
    }

    sheetService, err := sheets.New(client)
    if err != nil {
        log.Fatalf("Unable to retrieve Sheets Client %v", err)
    }

    _, err = sheetService.Spreadsheets.Get(spreadsheetId).Do()
    if err != nil {
        log.Fatalf("Unable to get Spreadsheets. %v", err)
    }

    fmt.Printf("success!\n")
}

go run main.go で実行。ここで403エラーが発生する場合は、スプレッドシートにサービスアカウントからのアクセスが許可されていない。

スプレッドシートを操作

Google Sheets API v4 のリファレンスはこちら。

以下の3つに分類されている。

  • spreadsheets
  • spreadsheets.sheets
  • spreadsheets.values

Go では以下のように対応している

  • sheetService.Spreadsheets
  • sheetService.Spreadsheets.Sheets
  • sheetService.Spreadsheets.Values

Go のリファレンスはこちら。

GET /v4/spreadsheets/{spreadsheetId}

例えば、 spreadsheets の get (GET /v4/spreadsheets/{spreadsheetId}) を使いたい場合、リファレンス を見てみると、パスパラメータとして spreadsheetId が必要なことが分かる。

Go で書くとこうなる。 sheetService.Spreadsheets.Get(spreadsheetId)SpreadsheetsGetCall を取得できるので、それの Do() メソッドを呼べば実際にAPIを呼ぶ。

sheetService.Spreadsheets.Get(spreadsheetId).Do()

PUT /v4/spreadsheets/{spreadsheetId}/values/{range}

spreadsheets.values の update ( PUT /v4/spreadsheets/{spreadsheetId}/values/{range} ) を使いたい場合、リファレンスを見ると、パスパラメータとして spreadsheetIdrange が必要なことが分かる。クエリパラメータとして valueInputOption があって、リクエストボディとして ValueRange が必要なことも分かる。

Go で書くとこうなる。

sheetService.Spreadsheets.Values.Update(spreadsheetId, targetRange, valueRange).ValueInputOption("USER_ENTERED").Do()

引数の targetRange はただの文字列で、 "シート名!A1:B1" というような書式。 valueRange は struct でドキュメントに詳しく書いてある -> https://godoc.org/google.golang.org/api/sheets/v4#ValueRange

何か操作してみる

行を挿入してから、以下のように書き込んでみる。

hoge 1
fuga 2
  • A2 = hoge
  • B2 = 1
  • A3 = fuga
  • B3 = 2

行の挿入はAPIリファレンスを見ているとなさそうに思えるが、 POST /v4/spreadsheets/{spreadsheetId}:batchUpdate で色々できる。

Go のコードはこんな感じ。1行目に挿入することはできなかったので、2行目から2行分挿入して、データを書き込んでいる。

package main

import (
    "errors"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"

    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"
    "google.golang.org/api/sheets/v4"
)

func httpClient(credentialFilePath string) (*http.Client, error) {
    data, err := ioutil.ReadFile(credentialFilePath)
    if err != nil {
        return nil, err
    }
    conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spreadsheets")
    if err != nil {
        return nil, err
    }

    return conf.Client(oauth2.NoContext), nil
}

func sheetId(s *sheets.Spreadsheet, sheetName string) (int64, error) {
    for _, sheet := range s.Sheets {
        if sheet.Properties.Title == sheetName {
            return sheet.Properties.SheetId, nil
        }
    }
    return 0, errors.New(sheetName + " is not exists.")
}

func insertRow(sheetService *sheets.Service, spreadsheetId string, sheetId int64) (*sheets.BatchUpdateSpreadsheetResponse, error) {
    req := sheets.Request{
        InsertDimension: &sheets.InsertDimensionRequest{
            InheritFromBefore: false,
            Range: &sheets.DimensionRange{
                Dimension:  "ROWS",
                StartIndex: 1,
                EndIndex:   3,
                SheetId:    sheetId,
            },
        },
    }
    insertRowReq := sheets.BatchUpdateSpreadsheetRequest{
        Requests: []*sheets.Request{&req},
    }
    return sheetService.Spreadsheets.BatchUpdate(spreadsheetId, &insertRowReq).Do()
}

func main() {
    spreadsheetId := "スプレッドシートのURLから抽出したID"
    credentialFilePath := "秘密鍵のJSONファイルのパス"
    sheetName := "シート1"

    client, err := httpClient(credentialFilePath)
    if err != nil {
        log.Fatal(err)
    }

    sheetService, err := sheets.New(client)
    if err != nil {
        log.Fatalf("Unable to retrieve Sheets Client %v", err)
    }

    spreadsheet, err := sheetService.Spreadsheets.Get(spreadsheetId).Do()
    if err != nil {
        log.Fatalf("Unable to get Spreadsheets. %v", err)
    }

    sheetId, err := sheetId(spreadsheet, sheetName)
    if err != nil {
        log.Fatal(err)
    }

    _, err = insertRow(sheetService, spreadsheetId, sheetId)
    if err != nil {
        log.Fatalf("Unable to insert row. %v", err)
    }

    valueRange := &sheets.ValueRange{
        MajorDimension: "ROWS",
        Values: [][]interface{}{
            []interface{}{"hoge", 1},
            []interface{}{"fuga", 2},
        },
    }
    _, err = sheetService.Spreadsheets.Values.Update(spreadsheetId, sheetName+"!A2:B3", valueRange).ValueInputOption("USER_ENTERED").Do()
    if err != nil {
        log.Fatalf("Unable to write value. %v", err)
    }

    fmt.Printf("success!\n")
}
40
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
32