2011年2月4日

Python を使ってスクリーンキャプチャ

PIL (Python Imaging Library) の ImageGrab を使うとスクリーンキャプチャを簡単に生成できます。 PIL は MacOSX や Ubuntu でも easy_install を使えば簡単にインストールできますが、 ImageGrab は Windows でしか使えないようです。 Windows 環境では、VisualStudio がインストールされていればソースコードをコンパイルできると思いますし、 そうでない場合にもバイナリパッケージを使えます。 公式サイトには 32bit 版のパッケージしかありませんが、こちらのサイト (Python Extension Packages for Windows - Christoph Gohlke) には 64bit 版のパッケージもあります。

Windows でしかキャプチャできないのも何なので、Selenium も使ってみます。 Python モジュールの selenium 2.0a5 にはキャプチャに関するバグがありますが、ちょっと修正すれば動作しますので手元でも比較的簡単に試すことができます。 このバグは開発中の trunk では修正されていますので、次回以降のリリースでは問題なく動作するでしょう。

Python 2.5 以上で実行してみましたので、最近の環境だとだいたい動作すると思います。

ImageGrab を使う

PIL モジュールのインストールが完了したら後は簡単です。 次のコードだけで現在のスクリーンを screen.png という名前で保存できます。

$ python
>>> import ImageGrab
>>> img = ImageGrab.grab()
>>> img.save('screen.png')

カレントディレクトリに screen.png というファイルがあるはずです。

領域を指定するには grab() 関数に引数を渡します。

>>> img = ImageGrab.grab((100, 100, 200, 200))
>>> img.save('screen.jpg')

ついでながら、PNG 形式に加えて JPEG 形式でも保存できることを確認できました。 当然ながらキャプチャしている領域も違いますね。

Web ページのキャプチャ

ちょっと脱線しますが、 webbrowser モジュールを使って、Web ページのキャプチャを作成してみます。

$ python
>>> import webbrowser
>>> import ImageGrab
>>> webbrowser.open('google.com')
>>> ImageGrab.grab().save('screen.png')
True

これだとスクリーンに表示されているもの全てがキャプチャされてしまいますので、手動で加工する必要があります。 grab() の呼び出しで領域を指定すれば良さそうですが、その領域を計算するのは大変だと思います。

Web ページのキャプチャ (2)

それでは Selenium を使ってみます。 Selenium 自体の基本的な使い方は PyPI のページ に書かれている通りです。 Remote Control Server と呼ばれるサーバが .jar パッケージになっていますので起動しておきます。 Windows 7 の場合は UAC の関係で管理者権限が必要になる場合が多いと思います。 コマンドプロンプトを右クリックして「管理者として実行」... とかそんな感じです。

Python の API は selenium.seleniumselenium.remote.webdriver が使いやすいと思いますが、 用途 (もしくは気分?) によって使い分ける必要が出てくるでしょう。 PyPI のページでは Webdriver を使っていますし、 Selenium-RC Introduction では selenium を使っています。 それぞれの API は pydoc で確認できます。

$ pydoc selenium.remote.webdriver
$ pydoc selenium.selenium

seleniumRemote Control Server と通信する部分を受け持ちますので、 The WebDriver Wire Protocol に記載されている ことと対応関係を持ちます。 一方、WebDriver はプロトコルに関しては selenium.remote.remote_connection に分離していますので、 ワンクッション挟むイメージになります。 関心事を分離する観点からは有効ですが、データを右から左に流しているだけに比べると、バグが混入する可能性があることも意味します。

Selenium の構成に踏み込んでいくのは大変なので、実際にキャプチャを生成してみます。 selenium の場合は capture_screenshot()capture_entire_page_screenshot() を使います。 たいがいのユースケースでは capture_entire_page_screenshot() の方が適切だと思いますが、 意外と面倒そうなのが何とも言い難い部分でしょうか。 どちらのメソッドも引数には保存先のファイル名を絶対パスで指定します。 (capture_entire_page_screenshot() はうまく動かなかったのでスキップ)

>>> from selenium import selenium, FIREFOX
>>> import os, os.path
>>> s = selenium('localhost', 4444, FIREFOX, 'http://google.com')
>>> s.start()
>>> s.open('/')
>>> s.capture_screenshot(os.path.join(os.getcwd(), 'screen.png'))
>>> s.stop()

capture_screenshot() の引数に相対パスを指定した場合は Remote Control Server を実行しているディレクトリに画像ファイルが生成されます。つまり、ローカル環境で全てを動かしていますので、 絶対パスでカレントを指定すると手元に生成されているように見えているだけです。

一方 WebDriver の場合は get_screenshot_as_file() を使います。 Python の selenium モジュールのバージョン 2.0a5 だと次のようになります。

>>> from selenium.remote import connect
>>> from selenium import CHROME
>>> browser = connect(CHROME)
>>> browser.get('http://google.com')
>>> browser.get_screenshot_as_file('screen.png')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python26\lib\site-packages\selenium-2.0a5-py2.6.egg\selenium\remote\webdriver.py", line 321, in get_screenshot_as_file
    f.write(base64.decode(png))
TypeError: decode() takes exactly 2 arguments (1 given)

え...っと、バグです。。 issue 833 で登録されており、 r9847 のコミットで解決されています。 base64 モジュールの decode を呼び出していますが、 decodestring が正解です。 エラーメッセージの該当箇所を修正すれば動作しました。 単純なミスなので、さらにエンバグしなければ次のバージョンでは使えるようになるでしょう。

ともあれ、 WebDriver の方がなんとなく直感的な気がします。 スクリプト実行側で BASE64 文字列をデコードしてファイルに保存しますので、パスの指定は相対パスで構いません。 もうひとつの選択肢として get_screenshot_as_base64() を使うこともできます。 機能は名前が表す通りで、 get_screenshot_as_file() はこれをラップしたものと考えると分かりやすいでしょう。

終わりに

PIL の ImageGrab と Selenium でスクリーンショットを取得しました。 実際に画像を生成するコンポーネントが異なりますので、セットアップの手間を考えるとどちらが簡単とは一概に言えません。 しかし、ImageGrab は Windows でしかサポートされていませんので、直近でマルチプラットフォームを考えると Selenium が良さそうです。 また、テストスイートを流しながら画面のキャプチャを保存するケースとの相性も良いでしょうね。 画面に収まりきらずスクロールが必要なページをキャプチャする場合もあるでしょうし。

初めは PIL でカンタンに解決すると思いましたが、意外と落とし穴が多い。。。 プログラミング言語に依存しない記事では次のふたつ (と、その近辺のリンク先) が tips として良いと思いました。

そもそも WebDriver って... という説明は英文だとこちら。


蛇足ながら、Perl の WWW:Mechanize だとスクリーンショットを保存できるようですが、Python 版には移植されていないみたいですね。

コメントを投稿