White Box技術部

WEB開発のあれこれ(と何か)

【Scala/ShangriLa】秋アニメ情報のJSONをGETリクエストで取得する

秋アニメは何観てますか?

こんなグラフを見たことある方はいないでしょうか?

実はこれ、友達が作成して公開しているものだったようです(知ったときは驚きました)。 グラフ作成のためのデータを取得するAPIも公開していて、これが今scalaベースで動いています。

qiita.com

どうも最近彼はこの個人プロジェクトに情熱を傾けているので、色々話を聞いていたら、いつの間にか自分も参加していました(ぇ

というわけで、今回はSora APIを叩くScalaのサンプルコードについてです。

ちなみに私は「すべてがFになる」を観ています

原作を読んだのは大学生の頃で、その頃はこれがアニメになるとも、将来それを自分が観てるとも思わなかったですが、相変わらずコードは書いているので、不思議な連続感を感じています。

書いてるコードはCからScalaに変わっていましたが。

sbt設定の手直し

さて、さっそくScalaのコードをEclipseで書こうと思ったのですが、sbtがエラーを吐くのでその修正から行うことになりました。
しかし、sbt使おうとするといつも不具合対応しているような気がします。利用する機会が不定期なのが悪いんですかね。

ちなみに実行環境は以下の通りです。

ユーザホーム\.sbt\repositoriesのfileプロトコル指定が間違っている

sbtコマンドを実行すると、以下のようなエラーになりました。

$ sbt
java.lang.IllegalArgumentException: URI has an authority component
        at java.io.File.<init>(File.java:423)
        at sbt.Classpaths$.sbt$Classpaths$$bootRepository(Defaults.scala:1758)
        at sbt.Classpaths$$anonfun$appRepositories$1.apply(Defaults.scala:1729)
        at sbt.Classpaths$$anonfun$appRepositories$1.apply(Defaults.scala:1729)
        at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
        at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
        at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
        at scala.collection.mutable.WrappedArray.foreach(WrappedArray.scala:34)
        at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
        at scala.collection.AbstractTraversable.map(Traversable.scala:105)
        at sbt.Classpaths$.appRepositories(Defaults.scala:1729)
        at sbt.Classpaths$$anonfun$41.apply(Defaults.scala:1102)
        at sbt.Classpaths$$anonfun$41.apply(Defaults.scala:1102)
        at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
        at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
        at sbt.EvaluateSettings$MixedNode.evaluate0(INode.scala:175)
        at sbt.EvaluateSettings$INode.evaluate(INode.scala:135)
        at sbt.EvaluateSettings$$anonfun$sbt$EvaluateSettings$$submitEvaluate$1.apply$mcV$sp(INode.scala:69)
        at sbt.EvaluateSettings.sbt$EvaluateSettings$$run0(INode.scala:78)
        at sbt.EvaluateSettings$$anon$3.run(INode.scala:74)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
[error] java.lang.IllegalArgumentException: URI has an authority component
[error] Use 'last' for the full log.
Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? 

以下に書いてありましたが、repositoriesファイルの中の記述にダメなところがあるようです。
ファイル指定のスラッシュが足りないようなので、file://をfile:///に修正したらエラーは消えました。

  • 修正対象ファイル
    C:\Users\ユーザ名.sbt\repositories

  • 参考
    stackoverflow.com

Permanent指定のオプションを無効化

これは別に不具合ではないのですが、Java 8からPermanent領域が消えているので、 sbtconfig.txtからMaxPermSizeオプションを外します(付けているとWarningが出るので)。

  • C:\Program Files (x86)\sbt\conf\sbtconfig.txt
変更前:-XX:MaxPermSize=256m
変更後:# -XX:MaxPermSize=256m

オプションについての詳しい話は、以下が参考になります。

equj65.net

Eclipseプラグインの有効化

前は普通に実行できていた気がするのですが、 sbt eclipseコマンドもエラーが出たので、公式の記述の通りに対応しました。

  • 対処法
    C:\Users\ユーザ名.sbt\0.13\pluginsにplugins.sbtファイルを作成して、以下を書き込み
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")

その後、Eclipseプロジェクトを作成するフォルダ配下でsbt eclipseを実行。

sbt、まとめました

ここら辺のことはQiitaにまとめました。 新規インストール時の参考にどうぞ。

qiita.com

Scalaプログラミングをするぞ!

これでScalaを書く準備ができました。

私のScalaの先生は、コップ本(第1版)とScala逆引きレシピなのですが、Scalaのバージョンアップの影響をどちらも受けていて、 書いている通りにやろうとすると結構な率でハマります。そして言うまでもなく、今回もしっかりハマりました。

逆引きレシピの改訂版が待ち遠しいです。

Sora APIの利用サンプル(Scala

以下がアニメ情報を取得するSora APIを、Scalaを使ってGETで叩き、JSONデータを取得するサンプルコードです。

github.com

APIのサンプルというよりは、Play JSON libraryDispatchのサンプルみたいになってしまいました。

Dispatchは予めハマっていたので今回はそれほどハマらなかったのですが、 Play JSONJSONからScala変換で結構ハマりました。

以下がそのJSON to Scalaを実行している箇所のコードを抜き出したものになるのですが、

  • JsValueはListとして保持されているので、一度Listから取り出して処理をしないといけない。
  • JSONのvalue値を文字列比較で一致を見るときは、そのまま文字列で比較するのではなく、JsStringのオブジェクトを使って比較すること。
  • JSONのvalue値をStringに変換しても、ダブルクォートが除去されず、文字列としてくっついたまま返却されてくること。

これらのことに気をつけて、やっと得たかった情報を得ることができました。

    case Success(content) => {
      val responseJson = Json.parse(content)
      println("GET /anime/v1/master/2015/2 json response" + responseJson.toString())

      val animeInfo = responseJson.as[List[JsValue]] map { seed =>
        seed.as[Map[String, JsValue]]
      }

      // 取得した情報から、「てさぐれ!部活もの すぴんおふ プルプルんシャルムと遊ぼう」の情報を利用する場合
      val tesagure: List[Map[String, JsValue]] = for (
        anime <- animeInfo if anime("title_short1") == JsString("てさぐれ!部活もの")
      ) yield anime

      println("\n「てさぐれ!部活もの すぴんおふ プルプルんシャルムと遊ぼう」 のアニメ情報")
      tesagure.head.toSeq.sortBy(_._1).foreach { parameter =>

        // valueをString変換してもダブルクォートが付いたままなので、表示前に除去する
        val value = parameter._2.toString().replaceFirst("""^\"(.*)\"$""", "$1")
        println(s"""  ${parameter._1} : ${value}""")
      }
    }

また、これらの処理はonComplete式中で実行しているのですが、そうなると非同期処理として実施されるため、 このままだとEclipseで実行したプロセスが終了せずに残り続ける問題がありました。

そこで今回は簡単な対処として、2秒待ったらプログラム終了、というコードを最後に入れています。

  // 非同期処理の終了
  Thread.sleep(2000)
  sys.exit(0)

最後のsys.exit(0)sys.exit()でも同じ挙動のはずです。

しかし、これはもうちょっとスマートに対応したいものですね。いい方法が見つかったら更新しておきます。 他にいい方法があるよ!という知見を持っている方がいればプルリク等で指摘いただけると助かります。