JavaScriptレンダリングサーバーSplashでスクレイピング
これはクローラー/Webスクレイピング Advent Calendar 2015の9日目の記事です。
本記事では、Scrapinghub社*1が開発しているSplashというオープンソースソフトウェアを紹介します。
JavaScriptを使ったページからスクレイピングする方法としては、PhantomJSとSelenium/CasperJSなどの組み合わせが一般的ですが、これらとは少し違う手段として使えるかもしれないソフトウェアです。 私自身Splashを最近知ったばかりで、軽く探した限りでは日本語の情報もないので、調査しつつSplashの使いドコロを探ってみたいと思います。
Splashとは
READMEには以下のように書かれています。
Splash is a javascript rendering service with an HTTP API. It's a lightweight browser with an HTTP API, implemented in Python using Twisted and QT.
It's fast, lightweight and state-less which makes it easy to distribute.
(訳)SplashはHTTP APIを持つJavaScriptレンダリングサービスです。HTTP APIを持つ軽量ブラウザで、TwistedとQTを使ってPythonで実装されています。 高速、軽量かつステートレスで、容易に分散できます。
ドキュメントにはもう少し詳しく機能が書かれています。
- process multiple webpages in parallel;
- get HTML results and/or take screenshots;
- turn OFF images or use Adblock Plus rules to make rendering faster;
- execute custom JavaScript in page context;
- write Lua browsing scripts;
- develop Splash Lua scripts in Splash-Jupyter Notebooks.
- get detailed rendering info in HAR format.
(訳)
- 複数のWebページを並列処理できる。
- HTMLやスクリーンショットを取得できる。
- 画像の読み込みを無効化したり、Adblock Plusのルールを使用してレンダリングを高速化できる。
- カスタムJavaScriptをページのコンテキストで実行できる。
- Luaでブラウジングスクリプトを書ける。
- Splash LuaスクリプトをSplash-Jupyter Notebooksで開発できる。
- 詳細なレンダリング情報をHARフォーマットで取得できる。
要するに、以下のように動くということでしょう。なかなか面白そうです。
Splashを使ってみる
とりあえず使ってみましょう。 今どきのソフトウェアらしく、Dockerを使って簡単にサーバーを起動できます。
$ docker pull scrapinghub/splash $ docker run -p 5023:5023 -p 8050:8050 -p 8051:8051 scrapinghub/splash
ポートの意味はそれぞれ以下のとおりです。
以下のようにログが表示されたら起動完了です。この記事ではSplash 1.8を使います。
2015-12-08 13:20:59+0000 [-] Log opened. 2015-12-08 13:20:59.239310 [-] Splash version: 1.8 2015-12-08 13:20:59.240288 [-] Qt 4.8.1, PyQt 4.9.1, WebKit 534.34, sip 4.13.2, Twisted 15.4.0, Lua 5.2 2015-12-08 13:20:59.242303 [-] Python 2.7.3 (default, Jun 22 2015, 19:33:41) [GCC 4.6.3] 2015-12-08 13:20:59.242396 [-] Open files limit: 1048576 2015-12-08 13:20:59.242472 [-] Can't bump open files limit 2015-12-08 13:20:59.447814 [-] Xvfb is started: ['Xvfb', ':1069', '-screen', '0', '1024x768x24'] 2015-12-08 13:20:59.507558 [-] proxy profiles support is enabled, proxy profiles path: /etc/splash/proxy-profiles 2015-12-08 13:20:59.547976 [-] verbosity=1 2015-12-08 13:20:59.548159 [-] slots=50 2015-12-08 13:20:59.548618 [-] Web UI: enabled, Lua: enabled (sandbox: enabled), Proxy Server: enabled 2015-12-08 13:20:59.549491 [-] Site starting on 8050 2015-12-08 13:20:59.549761 [-] Starting factory <twisted.web.server.Site instance at 0x1b87a70> 2015-12-08 13:20:59.551205 [-] SplashProxyServerFactory starting on 8051 2015-12-08 13:20:59.551413 [-] Starting factory <splash.proxy_server.SplashProxyServerFactory instance at 0x1b8a3b0>
起動したら、ブラウザでhttp://<Dockerのホスト>:8050/
にアクセスします。私の環境ではhttp://192.168.59.103:8050/
でした。
このURLを開くと、以下のようなデモ用の画面が表示されます。
「Render me!」という緑のボタンをクリックすると、以下のようにGoogleのページがレンダリングされた画像が表示されます。 さらに、スクロールするとネットワークに関する情報とHTMLが表示されます。
「Render me!」ボタンを押すと、入力したURLにアクセスし、黒いテキストボックス内のLuaスクリプトが実行される、ということのようです。
SplashのAPIを使う
デモはこれぐらいにしてHTTP APIを使ってみましょう。
/render.html
というエンドポイントにurl
パラメータを渡すことで、指定したURLのHTMLを取得できます。
$ curl 'http://192.168.59.103:8050/render.html?url=http://google.com' <!DOCTYPE html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head><meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能を活用して、お探しの情報を見つけてください。" name="description"><meta content="noodp" name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> ...
ここで重要なのは、このAPIで取得できるHTMLは、サーバーから取得したHTMLではなく、JavaScriptのonloadイベントが発生した時点でのDOMツリーをHTMLにしたものであるという点です。
このことがわかりやすい例として、PythonでさくっとWebスクレイピングする (JavaScript読み込みにも対応しつつ) - Qiita という記事でサンプルとして使われていた、テレ朝ニュースからスクレイピングしてみます。
このページでは、ページ下部の「関連ニュース」のリストがAjaxで読み込まれます。このため、以下のように普通にcurlで取得すると空の<div>
要素が存在するだけです。
$ curl http://news.tv-asahi.co.jp/news_international/articles/000064029.html | iconv -f shift_jis ... <!-- 関連ニュース --> <div id="relatedNews"></div> ...
以下のようにSplashを経由させると、<div>
要素の中にコンテンツが含まれた状態のHTMLを取得できます。onloadのタイミングまで待つので少しだけ時間がかかります。
$ curl -v 'http://192.168.59.103:8050/render.html?url=http://news.tv-asahi.co.jp/news_international/articles/000064029.html' ... <!-- 関連ニュース --> <div id="relatedNews"> <div class="kanrennews"> <h3>関連ニュース</h3> <ul class="newslist clearfix"> <li> <div class="text"><a href="http://news.tv-asahi.co.jp/news_international/articles/000063920.html">「若い皆さん研究頑張って」 物理学賞・梶田さん</a><br><span>(2015/12/07 17:53)</span></div> </li> <li> <div class="text"><a href="http://news.tv-asahi.co.jp/news_international/articles/000063849.html">ノーベル賞大村さん現地に 梶田さん燕尾服を採寸</a><br><span>(2015/12/06 11:52)</span></div> </li> <li> <div class="text"><a href="http://news.tv-asahi.co.jp/news_international/articles/000063837.html">“ノーベルウィーク”大村さん、梶田さん現地到着</a><br><span>(2015/12/06 05:50)</span></div> </li> <li> <div class="text"><a href="http://news.tv-asahi.co.jp/news_international/articles/000063826.html">大村さんも現地到着 いよいよ「ノーベルウィーク」</a><br><span>(2015/12/05 17:31)</span></div> </li> <li> <div class="text"><a href="http://news.tv-asahi.co.jp/news_international/articles/000063810.html">ノーベル賞梶田さん到着 これからイベント目白押し</a><br><span>(2015/12/05 11:51)</span></div> </li> </ul> </div> </div> ...
あとはお好きなスクレイピングライブラリでHTMLからスクレイピングすればOKです。
細かい話ですが、このページはShift_JISでエンコードされているので、最初の例ではiconvを使って文字コードを変換しています。Splashを通すと、UTF-8でエンコードされた状態で取得できるので、iconvは不要です。metaタグは<meta charset="Shift_JIS">
のまま取得できるので、ちょっと紛らわしいですね。
render.htmlエンドポイントにはurl
以外にも様々なパラメータを渡せます。例えばonloadからさらに指定した時間待つwait
や、ページ内でJavaScriptを実行するjs_source
などがあります。
また、他にも以下のようなエンドポイントがあります。
- render.png: PNGでスクリーンショットを取得する。
- render.jpeg: JPEGでスクリーンショットを取得する。
- render.har: HARファイルを取得する。
- render.json: 複数の形式を一度に取得する。
- execute: サーバーでLuaスクリプトを実行してもっと複雑な処理を行う。
詳しくはAPIドキュメントを参照してください。
Splash HTTP API — Splash 1.8 documentation
Splashの使いドコロ
推測ですが、Splash開発のモチベーションは以下のようなものでしょう。
- JavaScriptを使ったページが増えており、JavaScriptへの対応は不可欠。
- しかしあらゆるページでPhantomJSを使うと遅くなってしまう。
- かといって、JavaScriptを使わないページはlxmlで、使うページはSelenium+PhantomJSなどと使い分けると、APIが違って面倒。
- Splashを使って、JavaScriptを実行したあとのHTMLをlxmlなどでスクレイピングすれば、JavaScriptに対応しつつAPIの一貫性を維持できる。
このため、lxmlなどを使って昔ながらのスクレイピングをしている人が、あまりコードを変更せずにJavaScriptに対応したいという目的で使えるのではないでしょうか。
一方でステートレス性を重視しているので、JavaScriptで対話的な操作をしてスクレイピングするという用途には向いていないように思います。サーバーでLuaスクリプトを実行できますが、それであれば正直SeleniumとかCasperJSとかでやっても同じように思います。
また、サーバーを立てる必要があるので、使い捨てのスクリプトにはあまり向かないでしょう。Scrapinghub社はクローラーのSaaSを提供する会社であり、Splashもサービスの一つとして提供されています。このため、スケーラビリティが重視されており、ある程度大規模に使用しないと他の手段に比べてメリットを感じにくいかもしれません。
まとめ
SplashはJavaScriptレンダリングサーバーで、JavaScriptを使ったページからスクレイピングするための手段の一つとして使える可能性を持ったソフトウェアです。
ここで紹介したのは一番基本的な機能だけで、ドキュメントを読んでいると冒頭で紹介した機能以外にもさまざまな機能があります。 例えば、HTTPプロキシとして振る舞うことができたり、Scrapyと連携したりもできるなど、色々と面白い使い方ができそうです。
良ければ使ってみてください。