Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

ScalaでGoogle Sheets API(Java)を使う

こんにちは。セプテーニオリジナルの池田です。

直近のプロジェクトで、Google Sheets APIを使う機会があったのですが、 公式サイトScalaのサンプルがなかったので 今回ScalaでのGoogle スプレッドシートの読み込みと書き込みについて紹介します。

目次

環境
下記の環境で動作確認を行いました。

1.GoogleスプレッドシートAPIをオンにして、認証情報を設定

GCPのコンソール画面よりAPIの許可を行います。

手順は公式サイトのJava Quickstartを参考をしてください。

Java Quickstart  |  Sheets API  |  Google Developers

以下リンク先の日本語訳です。

  1. このウィザード を使用 して、Google Developers Consoleでプロジェクトを作成または選択し、APIを自動的にオンにします。 [ 続行]、[ 資格情報に移動]の順にクリックします。
  2. 上のプロジェクトに資格情報を追加、ページをクリックし、キャンセルボタンを押します。
  3. ページの上部にある[ OAuth同意画面 ]タブを選択します。電子メールアドレスを選択し、 まだ設定されていない場合は製品名を入力し、[ 保存 ]ボタンをクリックし ます。
  4. [ 認証情報 ]タブを選択し、[ 認証情報の作成 ]ボタンをクリックし、[ OAuthクライアントID ]を選択します。
  5. アプリケーションタイプを[ その他]に選択し、[ Google Sheets API Quickstart]という名前を入力して[ 作成 ]ボタンをクリックします。
  6. 「OK」をクリックしてダイアログを終了します。
  7. クライアントIDの右側にあるfile_download(JSONのダウンロード)ボタンをクリックします。
  8. このファイルを作業ディレクトリに移動し、名前を変更しclient_secret.jsonます。

今回ローカル環境で実行するためAPI を呼び出す場所を『その他の非UI(cronジョブ、デーモンなど)』に指定します。

※ローカル環境ではなくアプリケーションに組み込む場合は、oauth2callbackを用意する必要があります。

ここで登録した認証情報のキー(json形式)は、getResourceAsStreamで呼び出すので、resources配下に置いてください。 ここではclient_secret.jsonという名称でresources配下に保存します。

2.build.sbtにライブラリを追加

build.sbt

name := "GoogleSheetsAPISample"

version := "1.0"

scalaVersion := "2.12.0"

libraryDependencies ++= Seq(
  "com.google.api-client"   % "google-api-client"          % "1.23.0",
  "com.google.oauth-client" % "google-oauth-client-jetty"  % "1.23.0",
  "com.google.apis"         % "google-api-services-sheets" % "v4-rev486-1.23.0"
)

今回使用するライブラリはこちらです。

現在Scala用のAPIClientがないのでJava用のAPIClientを使います。

3.スプレッドシート読み込みのサンプル

今回以下のスプレッドシートにデータを読み込みます。

読み込むスプレッドシートの内容

ABCDEF
1順位都市人工面積(㎢)
21位東京=横浜日本37,843,0008,547
32位ジャカルタインドネシア30,539,0003,225
43位デリーインド24,998,0002,072
54位マニラフィリピン24,123,0001,580
6

データ元:世界の都市圏人口の順位 - Wikipedia

Google Sheets APIではスプレッドシートIDと対象のシートの情報が必要になります。スプレッドシートIDはスプレッドシートを開いているURLに記載されています。

URL:https://docs.google.com/spreadsheets/d/スプレッドシートID/edit


それでは、Quickstartというクラスを作成します。

import java.io.{File, IOException, InputStreamReader}
import java.util
import com.google.api.client.auth.oauth2.Credential
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver
import com.google.api.client.googleapis.auth.oauth2.{GoogleAuthorizationCodeFlow, GoogleClientSecrets}
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.client.util.store.FileDataStoreFactory
import com.google.api.services.sheets.v4.{Sheets, SheetsScopes}
import scala.collection.JavaConverters._

object Quickstart {

  private val applicationName = "Google Sheets API Scala Quickstart"
  // ローカル環境で実行する場合、user.home配下に.credentials/sheets.googleapis.com-scala-quickstartのディレクトリを用意してください
  private val dataStoreDir = new File(System.getProperty("user.home"), ".credentials/sheets.googleapis.com-scala-quickstart")
  private val dataStoreFactory = new FileDataStoreFactory(dataStoreDir)
  private val jsonFactory = JacksonFactory.getDefaultInstance
  private val httpTransport = GoogleNetHttpTransport.newTrustedTransport()

  /** アプリケーションに必要な権限のスコープ。アプリケーション実行後、
    * スコープを変更する際には,dataStoreDirのcredentialsを削除する必要がある。
    */
  private val scopes = util.Arrays.asList(SheetsScopes.SPREADSHEETS)

  /** 認証されたクレデンシャルを作成 */
  @throws[IOException]
  private def authorize: Credential = {
  //resourcesディレクトリ配下に認証情報のキー(client_secret.json)を置く
    val in = classOf[Nothing] getResourceAsStream "/client_secret.json"
    val clientSecrets = GoogleClientSecrets.load(jsonFactory, new InputStreamReader(in))
    val flow = new GoogleAuthorizationCodeFlow.Builder(
      httpTransport,
      jsonFactory,
      clientSecrets,
      scopes
    ).setDataStoreFactory(dataStoreFactory).setAccessType("offline").build

    new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver).authorize("user")
  }

  /**
    * APIクライアントサービスをビルドする
    */
  @throws[IOException]
  private def getSheetsService: Sheets = {
    val credential = authorize
    new Sheets.Builder(httpTransport, jsonFactory, credential).setApplicationName(applicationName).build
  }

  @throws[IOException]
  def main(args: Array[String]): Unit = {
    val service = getSheetsService
    val spreadsheetId: String = "スプレッドシートIDを指定"
    //rangeはシート名とセルの範囲を指定します。
    val range = "シート1!A1:E"
    val response = service.spreadsheets.values.get(spreadsheetId, range).execute
    val values = response.getValues
    //javaのList型で返ってくるので、scala のコレクションに変換します。
    for {row <- values.asScala
         columnA = row.get(0)
         columnB = row.get(1)
         columnC = row.get(2)
         columnD = row.get(3)
         columnE = row.get(4)
    } yield {
      println(s"$columnA $columnB $columnC $columnD $columnE")
    }
  }
}

実行結果

順位 都市 国 人工 面積(㎢)
1位 東京=横浜  日本 37,843,000 8,547
2位 ジャカルタ  インドネシア 30,539,000 3,225
3位 デリー インド 24,998,000 2,072
4位 マニラ フィリピン 24,123,000 1,580

上記のような読み込みができました。

またシートの最終列のcellに値が入っていない場合、rowgetすると IndexOutOfBoundsExceptionが返ってきますのでご注意ください。

4.スプレッドシート書き込みのサンプル

F列に人口密度のカラム行を追加したいと思います。

ABCDEF
1順位都市人工面積(㎢)
21位東京=横浜日本37,843,0008,547
32位ジャカルタインドネシア30,539,0003,225
43位デリーインド24,998,0002,072
54位マニラフィリピン24,123,0001,580
6

先ほどの読み込みのサンプルに必要なimport文を追記し、main関数を書き換えます。

import com.google.api.services.sheets.v4.model.ValueRange

・・・

  @throws[IOException]
  def main(args: Array[String]): Unit = {
    val service = getSheetsService

    //書き込む対象のセルの範囲
    val targetCell = "F1:F5"

    //rangeにはシート名と書き込むセルを指定
    val range = s"${sheet}!${targetCell}"

    //JavaのList[List[Object]]に変換する必要がある
    val values = List(
      List("人口密度(人/k㎡)").asJava,
      List("4,400").asJava,
      List("9,500").asJava,
      List("12,100").asJava,
      List("15,300").asJava
    ).asJava

    val body = new ValueRange
    val requestBody = body.set("values", values)
    val request =
      service.spreadsheets.values
        .update(spreadsheetId, range, requestBody)
        .setValueInputOption("USER_ENTERED")
    request.execute
    }

実行後

f:id:taketor:20171119144628p:plain

まとめ

このように、Google Sheets APIではScalaでも手軽にgoogleシートの読み込み、書き込みができます。

注意事項として、JavaGoogle Sheets APIを使っているので、APIから返ってくる型やリクエスト型が JavaのList型になります。そのため返ってきたデータをScalaのfor文で回したいときは Scalaのコレクションに変換する必要がありまし、リクエストをpostするときは、JavaのList型にする必要があります。

もっと手軽に実際にGoogleAPIにリクエストしたい場合はOAuth 2.0 Playgroundを使うと良いです。

参考にしたサイト