PhantomJSの代替としてGhost.pyを使う

# deck2pdfというもの

今年は[deck2pdf](https://pypi.python.org/pypi/deck2pdf)という、HTMLスライドをキャプチャしてPDF化するCUIツールを作ってました。

基本的は方式はググればよく出てくるように、「各ページをPNGにキャプチャ→全部をつなげてPDFにする」というもので、バージョン0では

* コア部分はPython
* キャプチャのためのレンダリング用エンジンはPhantomJS

という分担で書いていました。
で、せっかくなので可能な限りPythonパッケージで統一しようと思って探していて見つけたのが、この [Ghost.py](https://github.com/jeanphix/Ghost.py) です。

# Ghost.py を知ってみる

Ghost.pyは、Pythonで書かれたWebKitクライアントで、セッション, evaluate, スクリーンキャプチャなどを備えています。PhantomJSなどとは細かい比較をしたわけではないですが、最低限の挙動をさせる分には困らなかったので（一部はまりかけましたが）、そのまま自分のパッケージでも使用しました。

# Ghost.py を使ってみる

## インストールしてみる

Ghost.pyはQtを使うため、PySideかPyQtが必要です。
今回はPySideを使ってみました。

※以降、Mac前提となります

```
$ brew install qt
$ pip install PySide==1.2.2
$ pyside_postinstall.py -install
$ pip install Ghost.py
```

### 補足

実はこの時点でPySideの最新バージョンは1.2.4なのですが、1.2.4はMac系のwheelがないためビルドなどが動くらしくインストールまでの時間が結構かかります。PySideを現時点でインストールしておらず、とりあえずお試しで使ってみたいなら、上記の通り1.2.2を使ってしまった方が早いと思います。

## 使ってみる

```pycon:まずはghost起動
>>> from ghost import Ghost
>>> ghost = Ghost()
>>> session = ghost.start()
```

Ghostのインスタンスを作成した時点でクライアント側のプロセスが動きます。start()メソッドでセッションのインスタンスを作成します。

```pycon:HTML5slidesのデモページへアクセス
>>> resp = session.open('http://html5slides.googlecode.com/svn/trunk/template/index.html')
2015-12-20T18:02:12.662Z [WARNING ] QT: libpng warning: iCCP: known incorrect sRGB profile
2015-12-20T18:02:12.746Z [WARNING ] QT: libpng warning: iCCP: known incorrect sRGB profile
>>> type(resp)
<type 'tuple'>
>>> len(resp)
2
>>> resp[0]
<ghost.ghost.HttpResource object at 0x10a99f510>
>>> resp[1]
[<ghost.ghost.HttpResource object at 0x10a99f510>, <ghost.ghost.HttpResource object at 0x10a99f410>, <ghost.ghost.HttpResource object at 0x10a99f610>, <ghost.ghost.HttpResource object at 0x10a99f750>, <ghost.ghost.HttpResource object at 0x10a99f8d0>, <ghost.ghost.HttpResource object at 0x10a99f910>, <ghost.ghost.HttpResource object at 0x10a99fb10>, <ghost.ghost.HttpResource object at 0x10a99fc10>, <ghost.ghost.HttpResource object at 0x10a99fa10>, <ghost.ghost.HttpResource object at 0x10a99fd10>]
>>>
>>> resp[0].url
u'http://html5slides.googlecode.com/svn/trunk/template/index.html'
>>> resp[1][0].url
u'http://html5slides.googlecode.com/svn/trunk/template/index.html'
>>> resp[1][1].url
u'http://html5slides.googlecode.com/svn/trunk/slides.js'
>>> resp[1][2].url
u'http://fonts.googleapis.com/css?family=Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono'
```

インタラクティブシェルだと分かりづらいですが、`session.open(url)`で指定したURLとコンテンツ内で参照しているリソース全部をリクエストして取得してくれます。

```pycon
>>> session.capture_to('capture_1.png')
```
capture_toメソッドでスクリーンショットを取れます。が、、、
![capture_1.png](https://qiita-image-store.s3.amazonaws.com/0/6805/eaefc4a7-b703-13f2-0c31-1d66e98d6b4b.png)

ちゃんとキャプチャ領域を指定しないとひどいことになります。
もしくはviewport設定が可能なのであらかじめサイズを固定した方が良いかもしれません。

```pycon
>>> session.capture_to('capture_2.png', region=(1940, 0, 3000, 740))
>>>
```

![capture_2.png](https://qiita-image-store.s3.amazonaws.com/0/6805/1ab2cce8-b725-e988-74a8-b37a62b54830.png)


## ちょっとだけハマったところ

このGhost.pyはセッション内でjsを直接呼び出すことができます。

```pycon:html5slides製スライドを次のページに進ませる
>>> session.evaluate('nextSlide()')
(None, [])
>>> session.capture_to('capture_3.png', region=(1940, 0, 3000, 740))
```
![capture_3.png](https://qiita-image-store.s3.amazonaws.com/0/6805/765b3bb5-661a-36f6-5d6c-a90e28500e47.png)

スライドを進める関数を実行させてキャプチャしてもスライドが進んでくれません。
（普通のChromeなどで　nextSlideを実行した場合は問題なくスライドが進みます）


deck2pdfを作っているうちに確認した感じだと、どうやらGhost.pyのセッションは時間進行をGhost.pyの外にあるコードに委ねているらしいです。そのため、何もしないままだと仮にjsコードをevaluateで呼び出しても、時間が進まめない限り実行をしないようです。

```pycon:
>>> session.sleep(1)
>>> session.capture_to('capture_4.png', region=(1940, 0, 3000, 740))
```

1秒だけスライドさせてみた結果がこちら。問題なくスライドされた内容がキャプチャできました。
![capture_4.png](https://qiita-image-store.s3.amazonaws.com/0/6805/ebc95476-ff99-c755-b035-7f3cd8e89a08.png)


# まとめ

このようにちょっとクセはあるのですが、WebKitベースで出来ること自体はある程度できるので、うまく付き合っていけば色々遊べそうではありました。

Python純度の高いSpeakerDeckクローンとか、HTMLをまとめたアーカイブからスライド作るとか、ネタには困らなそうです