scala-scraper を使ってお手軽 Web スクレイピング
この記事は Scala Advent Calendar 2021 2日目の記事です。
自分がよく利用する Web ページから欲しい情報だけ抜き出したくなることって、ありますよね。
さいきん趣味で、Web ページから「OGP や Twitter Card」「ページ内の特定のリンク全部」なんかを抜き出したくなりまして、scala-scraper を使ってみました。
自分の使いたい用途では、やりたいことがとても手軽に実現できてよかったので、手短にご紹介します。
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
ねっ、カンタンでしょう?
scala-scraper ここが良かった
趣味プログラミングなので「とにかく手軽にスクレイピングしたい」という観点から、このあたりが良かったです。
抽出したい情報の指定がカンタン
-
browser.get(url)
でページを指定し、 -
div.foo.bar a
のような CSS セレクタで要素を特定し、 -
attr
やtext
などのメソッドで欲しい文字列を抽出する
を組みわせていくだけでした。
Scala 標準ライブラリと親和性がある
Scala から Java ライブラリを使う場合のちょっとした面倒がなく、ラクでした。
- 値が存在しない可能性が扱いやすい
- 歴史ある Java ライブラリだと「想定していた HTML 要素が見つからなかった」みたいな状況で
null
が返るのか例外が投げられるのか、API 仕様を調べるのがちょっと億劫です。 - scala-scraper では
tryExtract
または>?>
で、Scala のOption
を受け取ることができ
- 歴史ある Java ライブラリだと「想定していた HTML 要素が見つからなかった」みたいな状況で
- その他にも場面に応じた Scala 標準ライブラリ(
Seq
、Either
)を受け取ることができる-
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 を使う敷居を高くしていたと思います。
そんなわけで、英語版メソッドも提供されているるのはなかなかよいです。
開発は枯れているが最近の 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 でも使える可能性が高いでしょう(試したわけではありません)。
開発が活発でないから導入すべきでない?
ぼく自身は、もちろん趣味プログラミングだったということもありますが、リスクに感じず採用することにしました。
もしスクレイピングが業務だとして、このメンテナンス状況をリスクと感じるかは、スクレイピング技術に求めるさまざまな要求(実装言語、学習コストの低さ、必要な機能が揃っている、あるいはパッチを送ったら対応してくれそう、など…)トレードオフ次第だと思います。
もし業務で利用するという観点で見るなら、2019年の Scala Steward の導入と Scala の新バージョンへの対応は、好意的にとらえています。
ぼく自身もいくつかの Scala OSS をメンテナンスしているので、「自分としては十分事足りてるので積極的な機能追加はしない(コミュニティに任せる)が、細々とメンテナンスしていくよ」というあらわれに見ています。
どうしても欲しい機能や困ったバグがあれば、パッチを送れば対応してくれる可能性がありそうです。
メンテナンスが止まってしまったら、Scala コードベースなので Scala プログラマにとってはフォークもしやすいでしょう。
scala-scraper がサポートしている Web ブラウザ
scala-craper は、Webページのダウンロードやパースに使うブラウザの実装が差し替えられるようになっています。
現時点では 2つの実装があり、それぞれ次のような特徴があります。
-
jsoup ベース
- JavaScript を実行しない分、軽量で速い。
-
HtmlUnit ベース
- JavaScript を実行できる(最新の Chrome や Firefox に追随してるわけではない)ので、クライアントで HTML を書き換える動的 Web ページにもある程度対応できる。
- ボタンのクリックやフォームの入力などのブラウザの操作もある程度できる
ぼく自身の用途では静的な HTML がターゲットだったので、jsoup 版を利用しました。
残念ながら、Chrome や Firefox などのメジャーな Web ブラウザをベースにすることは現時点ではできません。
それができるように WebDriver 対応を求める声もありますが、作者の方に時間がなく、まだ対応されていません。
興味があればプルリクエストを送ってみるのも一興でしょう。
以上、Scala でお手軽に Web スクレイピングをしてみたいときには scala-scraper がよかったので、お試しあれ。