かれ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を作っているようなサイトだったとしても、これでクローリング・スクレイピングが可能になります。

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

Mac OSX Yosemiteにしてから、メニューバーがよく固まる

Yosemiteにしてからメニューバーが良く固まるし、CPUを良く喰ってる事がある。

時間は進んでるのに、クリックしても無反応みたいな
たぶんDropboxがよろしく無い気もする。

根本的な解決では無いけれど、プロセスの再起動で一旦治る

ps -ax |grep /SystemUIServer | perl -pe "s/^\s*(\d+).*/\1/g" |head -n 1  |xargs kill


コマンドでの操作に慣れていなかったら、Spotlight 検索で
「activi」これくらいまで打つとActivity Monitor.appが引っかかるので、
起動して、"SystemUIServer"を落とす。

oracleのjdkをwgetで落としてくる

## 2017/08/19 追記
jdk 8u144b01からは/jdk/の後に
8u144b01/というようなディレクトリが出来、さらにその下に
090f390dda5b47b9b721c7dfaa008135
というディレクトリが間に入るようになったため、
今までのようにバージョンだけわかっていれば、ダウンロード出来るわけではなくなってしまった。
どうせ,b01とかの部分はURLから調べることになったりすると思うので、
その時に同時に調べれば良いが注意が必要です。

    • -


oracleJDKはライセンスに同意しないと行けないので、wgetでダウンロードするのツライ。
f:id:tottokug:20141127143208p:plain

実際にブラウザでライセンスに同意すると

f:id:tottokug:20141127143103p:plain

cookie

oraclelicense=accept-securebackup-cookie

こんな怪しいやつがいる。
というわけで、このCookieをくっつけて、Downloadしてみる。

$ wget --no-cookies --no-check-certificate --header "Cookie: oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jdk-8u144-linux-x64.rpm"
--2014-11-27 14:34:03--  http://download.oracle.com/otn-pub/java/jdk/8u25-b17/jdk-8u25-linux-x64.rpm
download.oracle.com (download.oracle.com) をDNSに問いあわせています... 203.179.83.21, 203.179.83.10
download.oracle.com (download.oracle.com)|203.179.83.21|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 302 Moved Temporarily
場所: https://edelivery.oracle.com/otn-pub/java/jdk/8u25-b17/jdk-8u25-linux-x64.rpm [続く]
--2014-11-27 14:34:03--  https://edelivery.oracle.com/otn-pub/java/jdk/8u25-b17/jdk-8u25-linux-x64.rpm
edelivery.oracle.com (edelivery.oracle.com) をDNSに問いあわせています... 23.10.6.140
edelivery.oracle.com (edelivery.oracle.com)|23.10.6.140|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 302 Moved Temporarily
場所: http://download.oracle.com/otn-pub/java/jdk/8u25-b17/jdk-8u25-linux-x64.rpm?AuthParam=1417066563_ad49f0ca150603a9804a860ade76ca62 [続く]
--2014-11-27 14:34:03--  http://download.oracle.com/otn-pub/java/jdk/8u25-b17/jdk-8u25-linux-x64.rpm?AuthParam=1417066563_ad49f0ca150603a9804a860ade76ca62
download.oracle.com (download.oracle.com)|203.179.83.21|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 142191827 (136M) [application/x-redhat-package-manager]
`jdk-8u25-linux-x64.rpm' に保存中

100%[====================================================================================================================>] 142,191,827 2.69MB/s 時間 52s

2014-11-27 14:34:55 (2.61 MB/s) - `jdk-8u25-linux-x64.rpm' へ保存完了 [142191827/142191827]

ファイルサイズもちゃんとした、rpmがダウンロード出来ました。

Marvericksでbashを新しくしてみた。

とりあえずテスト

$ env x='() { :;}; echo !!!!!!!!!!' bash -c 'echo hello'
!!!!!!!!!!
hello

無事に脆弱性が確認出来ました。
ということで、新しいBashに入れ替えます。
幸いな事にmacportsには既に対策済みのがあるようなので、macportsでちゃちゃっと入れます。

$ sudo port selfupdate
$ sudo port upgrade 

としたところで、port使うのが久しぶりなせいか、xcodeがどうとか言ってきました。
おそらく普段からxcode使ってる人はこんな事言われずに普通に終わるんじゃないかなぁと思います。

Warning: The Xcode Command Line Tools don't appear to be installed; most ports will likely fail to build.
Warning: Install them by running `xcode-select --install'.
Error: It seems you have not accepted the Xcode license; most ports will fail to build.
Error: Agree to the license by opening Xcode or running `sudo xcodebuild -license'.
Error: Unable to upgrade port: 1
To report a bug, follow the instructions in the guide:
    http://guide.macports.org/#project.tickets

とりあえず言われるがままに

$ xcode-select --install

したら、こんな感じのウィンドウが出てきたので、
f:id:tottokug:20140925190632p:plain
インストールを押して数分後、インストール完了です。
f:id:tottokug:20140925190638p:plain


それで、今度はライセンスがどうのと言っているので確認して、agreeします。

$ sudo xcodebuild -license

気をつけなくてはいけないのは、長い文章が出てきて、最後に"agree"と打たないといけません。
適当にaと打ってreturnを押してしまうと、もう一回同じコマンドを打って、長い文章を見ないといけません。
ちゃんとagreeとうちましょう。xcodeも準備が出来たみたいなので、

$ sudo port upgrade bash

これでbashもいい感じになったはずです。

$ env x='() { :;}; echo !!!!!!!!!!' bash -c 'echo hello'
bash: 警告: x: ignoring function definition attempt
bash: `x' の関数定義をインポート中にエラーが発生しました
hello

ちゃんとエラーが出てくれるようになりました。

metapostで、epsを作った時に拡張子が1と2とかじゃなくて.epsになるようにする

outputtemplate:="%j.%c.eps";
beginfig(1);

〜略〜

endfig;

上のようにoutputtemplateを指定すると、

元々のファイル名が
hoge.mpだったとしたら、
beginfig(1)で指定していた画像は
hoge.1.epsになる。
%jが元のファイル名の拡張子取り除いた文字列
%cがbeginfigの中
になっているっぽい

JAWS Festa Tohoku 2014へ行ってマイペースに発表してきた #jawsug #festa2014

2014年9月6日

JAWS FESTA Tohoku 2014
これで

得上竜一(とくがみりゅういち) | JAWS FESTA Tohoku 2014
これを喋ってきました。


今回しゃべるにあたって、最先端物産展というトラックだったこともあったので、
最近人工知能界隈は
Softbankpepper売るとか言ってきたり、
Google猫論文出してきたり、
人工知能学会誌の表紙が話題
になったり

と、人工知能が何やら盛り上がっているし、今自分の興味の対象が人工知能だしって言うことで、話してきました。


人工知能がそもそも何だっていうのと、人工知能が今までにどんな問題にぶつかって乗り越えてというのがあったのかという歴史と、
今の技術と、クラウドのインフラがあるからこそ解決出来る事とを考えていくと、

あら、今なら人工知能作ることなんて、自分の技術とお財布でも出来そう。身近になったものです。

しかもこれくらいの金額なら、趣味としても、ビジネスとしても成り立つんじゃない?
というお話でした。



いつも、JAWS-UGで話をする時は話を聞きに来た人のうちの2人だけがめちゃくちゃ面白いって言ってくれて、ほかはポカンとしてるくらいを目指しているのだけれど、今回ははずしました。

話の濃さをミスったという反省があるので、資料は公開しません。

Apache UIMAのサンプルを動かす

Apache UIMA面白いですね。

IBMの誇るクイズ王(ワトソン (コンピュータ) - Wikipedia)のソースコード(の一部)がASFに寄贈され、現在も開発が続いているプログラムです。

Mac OSX(10.9.4)でUIMAのサンプルを動かしてみます。

Macと言いつつほぼUnixとして使っているので、Linuxでも同様に動くはずです。

Javaの用意

まずはJavaが必要です。手元の環境ではJDKの1.8.0u5が入っていたので、この機会にJava SE Development Kit 8 - Downloadsからダウンロードして1.8.0u20を入れました。

Mac OSXの場合はJavaはインストールしても自動的に変わるわけではないので、
.profileなどに

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home

を追加しておきます。

$ java -version
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)
$ javac -version
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
javac 1.8.0_20

Javaのアップデートが完了したことが確認できました。

Apache UIMAのinstall

Apache UIMA - DownloadsからApache UIMAをダウンロードしてきます。
Apache UIMAには"UIMA AS Asynchronous Scaleout"と"UIMA Java framework & SDK"がありますが、今回はローカルだけで動かすので、"UIMA Java framework & SDK"だけをダウンロードしました。

解凍して、設置した場所(手元では/usr/local/apache-uima)をUIMA_HOMEとして.profile内でexportしておきます。

UIMAを動かしてみる

UIMAには最初から色々と便利なスクリプトが用意されています。

${UIMA_HOME}/bin/documentAnalyzer.sh

実行すると↓こんなWindowが開きました。
f:id:tottokug:20140831011404p:plain

Input Directory: ${UIMA_HOME}/apache-uima/examples/data/
Input File Format: text
Character Encoding: UTF-8
Output Directory:${UIMA_HOME}/examples/data/processed
Location of Analysis Engine XML Descriptor: ${UIMA_HOME}/examples/descriptors/analysis_engine/NamesAndPersonTitles_TAE.xml
XML Tag containing Text(option): 空欄
Language: en

と入力して、Interactiveを押すと、なんか入力出来そうなフォームが出てきました。
f:id:tottokug:20140831012317p:plain

ここに何か文章を入れるようなのですが、英語で何かを書こうなんて無理なお話です。
そこでピノキオの原作のテキストを数行お借りすることにしました。

How it happened that Mastro Cherry, carpenter, found a piece of wood
that wept and laughed like a child.


Centuries ago there lived--


"A king!" my little readers will say immediately.

No, children, you are mistaken. Once upon a time there was a piece of wood. It was not an expensive piece of wood. Far from it. Just a common block of firewood, one of those thick, solid logs that are put on the fire in winter to make cold rooms cozy and warm.


I do not know how this really happened, yet the fact remains that one fine day this piece of wood found itself in the shop of an old carpenter. His real name was Mastro Antonio, but everyone called him Mastro Cherry, for the tip of his nose was so round and red and shiny that it looked like a ripe cherry.


As soon as he saw that piece of wood, Mastro Cherry was filled with joy.
Rubbing his hands together happily, he mumbled half to himself:

"This has come in the nick of time. I shall use it to make the leg of a
table."

このテキストをフォームにペーストして”Analyze”を押すと結果が確認できました。

↓の画像のように、人の名前っぽいところが抜き出せている事が分かります。
f:id:tottokug:20140831014355p:plain

こんなにも簡単にテキストの分析が出来るとはという感じで、今後も期待のUIMAのサンプルをいじって遊んでみました。



参考:
UIMA 101: 前編 - UIMAことはじめ
これがちょっと古い情報だったので、今の2.6.0でも同じように出来るのか確かめました。