pywebkitgtkとjswebkitでJavaScriptを実行してJavaScript実行済みのドキュメントを得る
GoogleはJavaScriptで隠匿したメールアドレスを読み取り、検索結果に表示している? | スラド ITを見て、こんなのはWebKitを使えば余裕だろ、ということでやってみました。
Cで記述してもよいのですが、ビルド設定が面倒なのでpythonでやってみます(追記: C言語でも実装しました)。
ubuntuにはpython-webkitがあるのですが、しかしこれにはまだWebKitのJavaScriptCoreへアクセスするコードが入っていません。
探したところ、pywebkitgtk(python-webit)のサイトのissue trackerにアップされてたjswebkitというJavaScriptCoreアクセスライブラリを利用することで、JavaScriptを呼ぶことができました。
- http://code.google.com/p/pywebkitgtk/issues/detail?id=28 のComment 9の添付ファイルjswebkit.tar.gz
jswebkitビルド
ダウンロード後
tar zxf jswebkit.tar.gz cd jswebkit python setup.py build cp build/lib.linux-i686-2.6/jswebkit.so ..
ビルドに必要なパッケージはいくつかありそうですが不明。libwebkit-devやpython-webkit-devは入れてありました。
コード例: getbodytext.py
# # Print entire HTML text after processed JavaScript # # usage: # /usr/bin/xvfb-run python getbodytext.py test.html # # libs: # - pygtk: http://www.pygtk.org/ # - pywebkitgtk(python-webkit): http://code.google.com/p/pywebkitgtk/ # - jswebkit: http://code.google.com/p/pywebkitgtk/issues/detail?id=28#c9 #import pygtk #pygtk.require("2.0") import gtk import jswebkit import webkit def loaded(view, frame): try: #print frame.get_title() gc = frame.get_global_context() # JSGlobalContextRef ctx = jswebkit.JSContext(gc) #print ctx.EvaluateScript("""document.body.innerHTML""") print ctx.EvaluateScript(""" (function () { var scripts = document.getElementsByTagName("script"); for (var i = 0; i < scripts.length; i += 1) { scripts[i].innerHTML = ""; } var range = document.createRange(); range.selectNodeContents(document.body); return range.toString(); })() """) pass except: import traceback traceback.print_exc() pass gtk.main_quit() pass def print_body(url): gtk.gdk.threads_init() window = gtk.Window(gtk.WINDOW_TOPLEVEL) webview = webkit.WebView() window.add(webview) webview.connect("load-finished", loaded) webview.open(url) webview.show_all() window.show_all() gtk.main() pass if __name__ == "__main__": import sys url = sys.argv[1] if not url.startswith("http"): url = "file://" + url print_body(url) pass
実行例
test.html
<?xml version="1.0"?><!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Test</title> </head> <body> <script>document.write("Hello");document.write("@example.com");</script></body> </html>
コマンドライン(jswebkit.soはgetbodytext.pyと同じディレクトリにおいてある)
$ xvfb-run python getbodytext.py test.html Xlib: extension "RANDR" missing on display ":99.0". Hello@example.com
xvfbの設定のためか余計な1行が"標準出力側に"出てしまうのですが、おおむねうまくできています。
付録: aタグ収集の例
ret = ctx.EvaluateScript(""" (function () { var ret = []; var as = document.getElementsByTagName("a"); for (var i = 0; i < as.length; i += 1) { ret.push(as[i].href); } return ret; })() """) for i in range(len(ret)): print ret[i] pass
aタグのhrefでjavascript:でlocation書き換えするタイプ( javascript:location.href="foo@example.com" を複雑にしたような物)はもう少し工夫が必要そうです。
付録2: ロード直後のDOMをXML形式で取り出す例
print ctx.EvaluateScript(""" new XMLSerializer().serializeToString(document) """)
補足: Xlib〜メッセージをstderrに出すxvfb-run
引数のコマンドのstderrメッセージをstdout側に出すのは、ubuntuのxvfb-runのバグっぽいです。bashスクリプトなので、コピーし編集します。
cp /usr/bin/xvfb-run . emacs xvfb-run
で、このxvfb-runファイルの終わりのほうの
# Start the command and save its exit status. set +e DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" 2>&1 RETVAL=$? set -e
と、stderrをstdoutにつなげている部分があるので、
# Start the command and save its exit status. set +e DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" # 2>&1 RETVAL=$? set -e
とコメントアウトし、これを使うといいです。