Python & Selenium のプログラムを実行形式(.exe)化する(webdriverも.exeに含める方法)

公開日:  

python selenium pyinstaller


Python & Seleniumで作成したプログラムを実行形式(.exe)にします。
特に今回は一つのexeファイルにすることにします。
PyInstallerを使うのが簡単なのですが、そのままではWebDriver(この記事ではchromedriver.exeを使用)をexe内に含んでくれません。exe内に含んでくれないということは、実行ファイルを配布する際、WebDriverも一緒に配布しないといけません。これが嫌なのでexeに内包する方法を調べました。

実行環境

  • Windows 10
  • ChromeDriver 76.0.3809.68
  • Python 3.6.2
  • selenium 3.141.0
  • PyInstaller 3.5

PyInstallerで実行形式にする

PyInstallerの基本的な使い方は以下のページで解説しています。

PyInstallerを使うときに次の2点に注意すれば大丈夫です。

  1. PyInstallerのオプションの指定
  2. ソース内でのパスの指定

PyInstallerのオプションの指定

前提として次のフォルダ構成とします。

MyProj/
 ├ driver/
 │ └ chromedriver.exe
 └ main.py

PyInstallerで実行形式にするのですが、オプションとして--add-binaryをつけます。これはPython以外のバイナリファイル(今回はchromedriver.exe)をexeに含めたい場合に指定します。

次のコマンドを使用します(パスの部分はそれぞれ書き換えてください)。

pyinstaller ./main.py --onefile --noconsole --add-binary "./driver/chromedriver.exe;./driver"
  • --onefile 一つのexeにして出力してくれます。
  • --noconsole 作成されたexeを実行する際にコンソールを出さなくします。
  • --add-binary exe内に加えたいバイナリファイルを指定します。指定方法は"元ファイルパス;取込先ファイルパス"です。

--add-binaryオプションの指定方法がちょっと分かりにくいですが、まずは--onefileオプションなしでビルドしてみるとわかりやすいです。--onefileオプションなしでビルドすると複数のファイルに分かれて出力されます。
例えば以下のようになります。

dist/
 └ main/
   ├ driver/
   │ └ chromedriver.exe
   └ main.exe
(※実際には他にもたくさんフォルダ・ファイルができます)

コマンドラインオプションに指定せずにspecファイルに書くことでも同様のことができます。

a = Analysis(...
         binaries=[ ( './driver/chromedriver.exe', './driver' ) ],
         ...

specファイルはPyInstallerを実行すると勝手に作られるので、作られたやつを修正して使うのがいいと思います。
加えたいファイルが複数ある場合なんかはspecファイルのほうが使い勝手がいいと思います。
specファイルでビルドする場合は以下のようにします。

pyinstaller main.spec

PyInstallerの設定としては以上の内容で大丈夫ですが、ソースもちょっと修正する必要があるかもしれません。
次の項で説明します。

ソース内でのパスの指定

--onefileオプションを使う場合、--add-binaryで指定したバイナリファイルはexe内に取り込まれます。それらは、実行時に一時フォルダに展開されます。そのため、Pythonのソース内で相対パスを使用してたりするとうまく動かない場合があります。

例えば以下のように相対パスを使用している部分は書き換える必要があります。

driver = webdriver.Chrome('./driver/chromedriver.exe')

次のように書き換えます(必要なモジュールはimportしてください)。

def resource_path(relative_path):
    try:
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.dirname(__file__)
    return os.path.join(base_path, relative_path)

driver = webdriver.Chrome(resource_path('./driver/chromedriver.exe'))

関数resource_pathを追加してそれを使用するようにします。
resource_pathは以下のロジックでパスを取得します。

  1. exeから実行するときは、sys._MEIPASS(exeから実行時、一時フォルダのパスが入る)からの相対パスを取得する。
  2. Pythonから実行するときは、このファイル(__file__)からの相対パスを取得する。

これでPythonから実行しても、exeファイルで実行してもちゃんと動きます。

まとめ

これで説明は終わりです。
Python & Selenium の例でしたが、画像ファイルなどをexeに内包したい場合などでも同じだと思います。



関連記事