かれ4

かれこれ4個目のブログ

HTMLパーサとしてのwkhtmltoimage。js実行後のDOMが取れるよ。

クローラー/スクレイピング Advent Calendar 2014の12月20日です。

タイトルに書いてあることが全てではありますが、いちおうスクレイピングするにあたっての事を。

スクレイピングする時のアプローチとして、大きく2つあると思います。
1つは、完全にテキストとして正規表現で抜き出してくる方法。
もう一つが、HTMLをパースしてXPathやそれに似た(CSSセレクタ)構文で取得する方法。

今回は後者のパースして取得する方法について書きます。


なぜパースする必要があるのか

世の中のHTMLは汚い。CMSを使っていたとしても汚い。
タグがグチャグチャです。
グチャグチャのタグの構造からXPathで取得しようとすると、思った動作をしないことが多々あります。
なので、一旦パースをして、きちんとした構造を使わないと痛い目を見ることがすくなくありません。

どのパーサを選ぶべきなのか

HTMLのパーサには色々な種類があります。
私は普段Javaでのクローリング・スクレイピングをしているので、Javaのライブラリを良く使うのですが、
今個人で使っているのは主にTagSoupです。
業務で使っているのは、MozillaParserです。
しかし、今業務で使っているMozillaParserをやめて、WebKitをベースにしたものを検討しています。

MozillaParserから、WebKit

なぜWebKitベースのものへと切り替えるのかというと、
WebKitベースは現在 SafariGoogle ChromeOpera等最近のモダンなブラウザの大半が使っていて、表示確認などもおそらくこれらに最適化されているだろうと予測されるからです。
(厳密にはWebKitからForkしたBlinkだったりしますが、、)

WebKitベースのParser

特にWebKitベースのParserがあるわけではないのですが、wkhtmltopdfというライブラリを使用します。
(実際に利用するのはwkhtmltoimageですが)
ただ、このままでは、色々と問題があるので当然、改造することになりますが、LGPLなので安心です。
大きな問題点としては以下の2点が上げられます。

  • 問題点1:GUIのない環境での動作
  • 問題点2;標準ではHTMLの出力では無くグラフィクスでの出力

この2つを解決しないことにはParserとしては使えません。
では、この2つをいかにして解決していくかというところを今回の主題として書いていきます。

まずはGUIのない環境での動作については、これは仮想フレームバッファを利用することで、解決ができます。
これは、JenkinsでJavascriptのテストをしたことがある人であれば、馴染みのあるものだと思います。

GUIのない環境だったとしても、仮想のディスプレイを作り、そこに対して描画するという方法です。


もう一つの問題点、標準ではHTMLの出力をしてくれないという点です。
これはもう改造してしまえば良いだけなので、簡単です。

ただ、改造する為の環境構築がなかなかのクセモノだったりもします。

まずQtが必要です。しかもyumやaptで入るような新しいものではなくて、若干古いものが必要になってきます。
どのバージョンが必要かはwkhtmltopdfのソースリポジトリ(https://github.com/wkhtmltopdf/qt)に記されているので、そちらを利用します。

Qtの準備

Qtの準備とコンパイル環境の構築はここに書いてしまうと、文章ボリュームの大半をしめてしまうので、ここには書きません。


wkhtmltopdfの改造

wkhtmltopdfは現状では、PDFでの出力はできますが、HTMLを出力することはできません。
なので、改造してhtmlを出力できるようにします。
 

改造は ここのソースをベースにしていきます。

改造のポイントは大きく2つで、

  • コマンドラインの引数を処理する部分にHTML出力用のオプションを追加する。
  • オプションに応じてHTMLをファイルに書き出す。

です。

まずオプションを追加するのに

  • src/image/imagearguments.cc
  • src/image/imagecommandlineparser.cc
  • src/image/imagecommandlineparser.hh
  • src/lib/imagesettings.cc
  • src/lib/imagesettings.hh

詳細は省略しますが、この辺りのファイルを編集します。


実際にファイルにHTMLを書き出すための処理を追加するのに、
src/lib/imageconverter.cc
このファイルのImageConverterPrivate::pagesLoadedを変更します。

変更点としてはオプションでHTML出力が指定されていた時に、ファイルに書きだすようにします。

if(settings.ophtml != ""){
  QString html = frame->toHtml();
  QFile file(settings.ophtml);
  if(file.open(QIODevice::WriteOnly)){
    QTextStream outfile(&file);
    if(settings.codec != ""){
      QTextCodec* codec = TextCodec::codecForName(settings.codec.toAscii());
      outfile.setCodec(codec);
    }
    outfile << html;
    file.close();
  }
}

を追加します。

これで、makeしてあげれば、html出力が出来るwkhtmltoimageができます。

$ wkhtmltoimage --output-html yahoo.html http://blog.tottokug.com blog_tottokug_com.png

とすると、http://blog.tottokug.comのhtmlがwebKitでパースされ、Javascriptの実行後のHTMLを取得したい場合は --javascript-delay というオプションでJavascript実行後に指定したミリ秒間の遅延を持って出力されます。

curlwgetで取ってきたhtmlをいきなり使うよりも、きれいな構造になったHTMLを取得する事ができます。

また、JavascriptでHTMLを作っているようなサイトだったとしても、これでクローリング・スクレイピングが可能になります。

副作用として、レンダリングしたものを画像として保存する事も出来ます。