使用Go语言获取Google表格信息

背景 -> 背景资料

你好,我是@harhogefoo。

我认为您可能有使用Google Apps Script获取电子表格信息并执行某些操作的需求。然而,GAS无法进行并行处理。因此,如果要引用多个电子表格进行操作,执行时间可能会很长。

所以,这次我们使用Golang的groutine来整理同时获取电子表格信息的方法。

通过使用服务账号,可以访问到电子表格的信息。

建議事先閱讀官方文件的“快速入門”章節,以取得必要的背景知識。
https://developers.google.com/sheets/api/quickstart/go
該連結中所提及的內容可以總結如下:

    • Google APIsのSpreadsheet APIを有効化し、OAuthクライアントIDを発行

 

    • このIDを利用してプログラムからスプレッドシートにアクセス

 

    プログラム実行時にブラウザが立ち上がりGoogleアカウントログインを求められ、ログインをすると認証情報がファイルに保存される。これ以降は認証が不要

不过,这次我希望跳过认证流程。

我们采用了使用服务帐号来获取Google表格信息的方法。这个方法已经总结在下面的链接中,您可以参考这个链接来快速实现从创建服务帐号到访问表格的过程。
https://qiita.com/bati11/items/a4cd922149dac07981bc
为了参考,我会提供获取访问Google表格的客户端的代码。

import "golang.org/x/oauth2/google"
func getHTTPClient(credentialsJSONData []byte) (*http.Client, error) {
    conf, err := google.JWTConfigFromJSON(credentialsJSONData, "https://www.googleapis.com/auth/spreadsheets")
    if err != nil {
        return nil, errors.WithStack(err)
    }
    return conf.Client(oauth2.NoContext), nil
}

以下是OAuth2 / Google文档的链接↓
https://godoc.org/golang.org/x/oauth2/google

使用 Spreadsheet API 从电子表格中获取单元格信息。

利用 Spreadsheet API 进行访问电子表格信息有三种方法。虽然这些方法已在以下总结,但为了写明选择的原因,在本文中也会整理。

使用spreadsheets.get方法,并添加includeGridData选项来获取数据。

获取包括所有单元格信息的电子表格数据。如果不添加includeGridData选项,将返回没有单元格信息的数据。将其转化为具体的Golang代码。

import "google.golang.org/api/sheets/v4"
func main() {
    client, _ := getHTTPClient() // ここは先述のサービスアカウント情報を使ってクライアントを取得する
    service, _ := sheets.New(client)

    resp, _ := service.Spreadsheets.Get(spreadsheetID).IncludeGridData(true).Do()
    // ごにょごにょ
}

获取SpreadsheetsService.Get的文档如下↓
https://godoc.org/google.golang.org/api/sheets/v4#SpreadsheetsService.Get

请使用“spreadsheets.values.get“从中获取。

通过指定电子表格中的工作表和范围来获取数据。

import "google.golang.org/api/sheets/v4"
func main() {
    client, _ := getHTTPClient(credentialsJSONData)
    service, _ := sheets.New(client)

    rangeStr := "シート名" // シート名!A1:A99 のような記述をすれば範囲の指定も可能

    resp, _ := service.Spreadsheets.Values.Get(spreadsheetID, rangeStr).IncludeGridData(true).Do()
    // ごにょごにょ
}

下面是SpreadsheetValuesService.Get的文档链接:https://godoc.org/google.golang.org/api/sheets/v4#SpreadsheetsValuesService.Get。

以 `spreadsheets.values.batchGet` 获取

使用gRPC语法可以从电子表格中获取多个工作表的信息。
我尚未进行尝试,因此没有样本代码。

本次采用的单元信息获取方式

我们选择了使用 spreadsheets.get 加上 includeGridData 选项进行获取。

不採用2的原因

    • シートごとにリクエストするため、大量に実行するとすぐにAPI制限に引っかかってしまう

スプレッドシートの読み取りは100秒あたり500リクエストまで
### ③を採用しなかった理由

スプレッドシート内のセル情報全てを取得したかった
①の方が簡潔にかけそう
## 取得したセル情報をパースする
①spreadsheets.get + includeGridDataオプションを付与して取得する 方式で情報を取得すると以下の形式で返ってきます。
https://godoc.org/google.golang.org/api/sheets/v4#Spreadsheet
APIドキュメントをたどるとセル情報は、SpreadsheetのSheetsのData配列のRowData配列のValues配列 にそれぞれ格納されており、Valuesは CellData型で定義されている(深い)。
CellDataのドキュメント↓
https://godoc.org/google.golang.org/api/sheets/v4#CellData
CellDataは、FormattedValue、EffectiveValue、UserEnteredValueを持っている。
それぞれについて整理しておくと、

FormattedValue:  整形された値(スプレッドシートに表示されている値)。戻り値は、string。
EffectiveValue: 評価された値。戻り値は、ExtendedValue。
UserEnteredValue:  入力された値。戻り値は、ExtendedValue。

以下是ExtendedValue的文档链接:https://godoc.org/google.golang.org/api/sheets/v4#ExtendedValue
ExtendedValue根据不同的类型具有相应的信息。

文字列ならExtendedValue.StringValueに
数値ならExtendedValue.NumberValueに (ただし戻り値はfloat)
真偽値なら ExtendedValue.BoolValueに格納されています。

获取和显示单元格信息的示例代码。

import "google.golang.org/api/sheets/v4"
func main() {
    client, _ := getHTTPClient() // ここは先述のサービスアカウント情報を使ってクライアントを取得する
    service, _ := sheets.New(client)

    resp, _ := service.Spreadsheets.Get(spreadsheetID).IncludeGridData(true).Do()

    // セル情報の取得
    for _, s := range resp.Sheets {
        for _, row := range s.Data[0].RowData {
            for _, value := range row.Values {
                fmt.Println(value.FormattedValue)
                fmt.Println(value.EffectiveValue.StringValue)
                fmt.Println(value.UserEnteredValue.StringValue)
            }
        }
    }
}

选择哪个值?

这是我在实际实施中获得的经验,日期和时间数据在ExtendedValue中包含奇怪的值(如果有任何经验,请告诉我)。因此,我们需要将其作为FormattedValue(字符串)进行处理,并根据需要进行格式化和读取。对于其他数值和字符串数据,我们将从EffectedValue中读取。我们没有使用UserEnteredValue。关于EffectiveValue,我编写了一段很好的代码来获取值。

func getValueFromEffectiveValue(ev *sheets.ExtendedValue) interface{} {
    if ev == nil {
        return ""
    }
    if ev.StringValue != "" {
        return ev.StringValue
    }
    if isInteger(ev.NumberValue) {
        return math.Floor(ev.NumberValue)
    }
    return ev.NumberValue
}

同时获取电子表格信息

嗯,到目前为止,我们已经能够获取到电子表格的单元格信息了。但是,如果对多个电子表格进行串行访问,那可能会花费很长时间,所以我们将并行访问电子表格并获取信息。在Golang中,有groutine、WaitGroup和errorgroup等并行访问方法。因为我希望进行错误处理,所以选择使用errorgroup。我写了下面这样的代码。

import  (
    "sync"
    "golang.org/x/sync/errgroup"
)
func getSpreadsheetDataByID(
    mutex *sync.Mutex,
    spreadsheetID string,
    output *[]string,
) func() error {
    return func() error {
       // スプレッドシートからセル情報を取得する
       // ごにょごにょ

       // 並行に同一のリソースにアクセスする場合はmutexでLockをかけておくと安全
       mutex.Lock()
       // ここで取得したセル情報をoutputに格納する
       mutex.Unlock()
    }
}
func main() {
    output := make([]string, 0)
    mutex := &sync.Mutex{}
    var eg errgroup.Group
    spreadSheetIDs := []string{"xxx", "yyy", "zzz"}
    for _, spreadSheetID := range spreadSheetIDs {
        // NOTE: https://golang.org/doc/faq#closures_and_goroutines
        spreadSheetID := spreadSheetID
        eg.Go(getSpreadsheetDataByID(mutex, spreadSheet, &output))
    }

    if err := eg.Wait(); err != nil {
        return nil, err
    }

    // outputで何かする
}

错误团队的小陷阱

在前一章中,我们使用了errorgroup。// 注意 在下面的注释行中可以看到,
spreadsheetID := spreadsheetID 进行了重新赋值。
如果不这样做,将始终引用列表的最后一个值,并且并发处理将无法获取期望的值。
有关详细原因,请参考这个链接。
https://qiita.com/harhogefoo/items/7ccb4e353a4a01cfa773

結束之時

你觉得怎么样?

如果能對某人有所幫助,我就很開心!

快乐编码!

bannerAds