Open7

scala-scraper を使ってお手軽 Web スクレイピング

TATSUNO YasuhiroTATSUNO Yasuhiro

この記事は Scala Advent Calendar 2021 2日目の記事です。

自分がよく利用する Web ページから欲しい情報だけ抜き出したくなることって、ありますよね。
さいきん趣味で、Web ページから「OGP や Twitter Card」「ページ内の特定のリンク全部」なんかを抜き出したくなりまして、scala-scraper を使ってみました。
自分の使いたい用途では、やりたいことがとても手軽に実現できてよかったので、手短にご紹介します。

TATSUNO YasuhiroTATSUNO Yasuhiro

scala-scraper とは

scala-scraper は、Web コンテンツのスクレイピング(必要な情報を抽出)のための、Scala 用ライブラリです。
コンテンツのダウンロードや情報の抽出を簡潔に行える DSL が特徴です。

val browser = JsoupBrowser()
val doc = browser.get("https://zenn.dev/exoego/scraps/16aeafeb6034b2")
val title = doc >> text("header h1")
// title: String = "scala-scraper を使ってお手軽 Web スクレイピング"

val oops = doc >?> text("header h2.no-such-element")
// oops: Option[String] = None

ねっ、カンタンでしょう?

TATSUNO YasuhiroTATSUNO Yasuhiro

scala-scraper ここが良かった

趣味プログラミングなので「とにかく手軽にスクレイピングしたい」という観点から、このあたりが良かったです。

抽出したい情報の指定がカンタン

  • browser.get(url) でページを指定し、
  • div.foo.bar a のような CSS セレクタで要素を特定し、
  • attrtext などのメソッドで欲しい文字列を抽出する

を組みわせていくだけでした。

Scala 標準ライブラリと親和性がある

Scala から Java ライブラリを使う場合のちょっとした面倒がなく、ラクでした。

  • 値が存在しない可能性が扱いやすい
    • 歴史ある Java ライブラリだと「想定していた HTML 要素が見つからなかった」みたいな状況で null が返るのか例外が投げられるのか、API 仕様を調べるのがちょっと億劫です。
    • scala-scraper では tryExtract または >?> で、Scala の Option を受け取ることができ
  • その他にも場面に応じた Scala 標準ライブラリ(SeqEither)を受け取ることができる
    • Seq については scala.collection.JavaConverters._ で変換してしまえばいいっちゃいいのですが、例外ではなく Either が欲しいときはやや面倒です

オペレーター(記号)だけでなく英語名のメソッドもある

scala-scraper では、記号のオペレーターだけでなく、英語名のメソッドも用意されています。

// extract は >> と同じ
val title2 = doc extract text("header h1")
// title2: String = "scala-scraper を使ってお手軽 Web スクレイピング"

// tryExtract は >?> と同じ
val oops2 = doc tryExtract text("header h2.no-such-element")
// oops2: Option[String] = None

初見でもあるいは久しぶりにコードを見たときでも、英単語の意味からコードの意図を想像しやすいのは利点に思いました。

というのは、Scala では一昔前、記号を多用する文化があったからです。
Scala では >?: などの記号を使ったオペレーターや型などをユーザーが定義できる柔軟性があり、そうした記号と数学やコンピューターサイエンスなどとの親和性、DSL の作りやすさもあったのでしょう。
たとえば、Either[L,R] 型を L \/ R と書けるようにしたり、といった感じです。

L \/ R 型などは比較的わかりやすく思いますが、記号が多用されているとなかなか初見では厳しいものがあります。
他のメジャーなプログラミング言語で見慣れた「英語名の API」からかけ離れていることもあり、Scala を使う敷居を高くしていたと思います。
そんなわけで、英語版メソッドも提供されているるのはなかなかよいです。

TATSUNO YasuhiroTATSUNO Yasuhiro

開発は枯れているが最近の Scala にも対応している

CHANGELOGを見ると、リリースは年に1度あるかないかで、2018年にはリスクがあるので見送ったというご意見もあったようです。
自分も当時業務で開発する、となったらそう考えるように思います。

ですがその後、2019 年に Scala Steward を導入して、ライブラリのメンテナンス作業を自動化しています。
また Scala 2.13 も対応されています。

現時点では 2021年5月リリースの v2.2.1 が最新で、Scala 2.12 と Scala 2.13 をサポートしています。
さらに Scala 2.13 と Scala 3.0 のライブラリの相互運用性のおかげで、Scala 3.0 でも使える可能性が高いでしょう(試したわけではありません)。

TATSUNO YasuhiroTATSUNO Yasuhiro

開発が活発でないから導入すべきでない?

ぼく自身は、もちろん趣味プログラミングだったということもありますが、リスクに感じず採用することにしました。

もしスクレイピングが業務だとして、このメンテナンス状況をリスクと感じるかは、スクレイピング技術に求めるさまざまな要求(実装言語、学習コストの低さ、必要な機能が揃っている、あるいはパッチを送ったら対応してくれそう、など…)トレードオフ次第だと思います。

もし業務で利用するという観点で見るなら、2019年の Scala Steward の導入と Scala の新バージョンへの対応は、好意的にとらえています。
ぼく自身もいくつかの Scala OSS をメンテナンスしているので、「自分としては十分事足りてるので積極的な機能追加はしない(コミュニティに任せる)が、細々とメンテナンスしていくよ」というあらわれに見ています。
どうしても欲しい機能や困ったバグがあれば、パッチを送れば対応してくれる可能性がありそうです。
メンテナンスが止まってしまったら、Scala コードベースなので Scala プログラマにとってはフォークもしやすいでしょう。

TATSUNO YasuhiroTATSUNO Yasuhiro

scala-scraper がサポートしている Web ブラウザ

scala-craper は、Webページのダウンロードやパースに使うブラウザの実装が差し替えられるようになっています。
現時点では 2つの実装があり、それぞれ次のような特徴があります。

  • jsoup ベース
    • JavaScript を実行しない分、軽量で速い。
  • HtmlUnit ベース
    • JavaScript を実行できる(最新の Chrome や Firefox に追随してるわけではない)ので、クライアントで HTML を書き換える動的 Web ページにもある程度対応できる。
    • ボタンのクリックやフォームの入力などのブラウザの操作もある程度できる

ぼく自身の用途では静的な HTML がターゲットだったので、jsoup 版を利用しました。

残念ながら、Chrome や Firefox などのメジャーな Web ブラウザをベースにすることは現時点ではできません。
それができるように WebDriver 対応を求める声もありますが、作者の方に時間がなく、まだ対応されていません。
興味があればプルリクエストを送ってみるのも一興でしょう。