Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

Firefox headlessモードをUbuntuとPythonとSelenium環境で動かす

- はじめに -

headless Chromeが来た頃、Firefoxのheadless対応の噂がありました。


そしてheadlessモードが正式に搭載されました。

developer.mozilla.org


この記事は、PythonSelenium.webdriverを使ってFirefoxのheadlessモードを触ろうという導入記事です。

今までCUIFirefox操作するとなると、xvfbとかVirtual Xを利用してスクリーンを作った上でのFirefox起動が一般的でしたがこれで少し簡単になりますね。

headless Chromeの記事と同じく設定周りについてはいつか追記するかも

環境は以下の通り
//------
Ubuntu 16.04 LTS
Python 3.6.1
pip install selenium (既)
//------

 

- UbuntuFirefoxを入れる -

aptにFirefoxリポジトリを追加

sudo apt-add-repository ppa:mozillateam/firefox-next
sudo apt-get update

途中以下のように聞かれるのでEnter

Press [ENTER] to continue or ctrl-c to cancel adding it

こんな感じの出力が出る

取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial InRelease [17.6 kB]
取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial/main amd64 Packages [12.5 kB]
取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial/main i386 Packages [12.5 kB]
取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial/main Translation-en [3,716 B]


 
インストールする

sudo apt-get install firefox

バージョンは56.0が入る

firefox -v
 Mozilla Firefox 56.0

試しにheadlessモードを使ってみると以下のような表記が出る。ctrl+cで抜ける。

firefox -headless
*** You are running in headless mode.


 

- PythonSeleniumから使う -

Seleniumから使うにはFirefox用のgeckdriverをインストールしておく必要がある。
geckdriverのバージョンはよしなに。

wget https://github.com/mozilla/geckodriver/releases/download/v0.18.0/geckodriver-v0.18.0-linux64.tar.gz
tar -zxvf geckodriver-v0.18.0-linux64.tar.gz
sudo cp ./geckodriver /usr/local/bin

geckdriverをインストールしてないと以下のようなエラーが出る。

~~~
raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: 'geckodriver'
During handling of the above exception, another exception occurred:
~~~
os.path.basename(self.path), self.start_error_message)
selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.


 
wbdriverの引数に "-headless" を付けて起動させる。
特に何もしてないと以下のように "/usr/bin/firefox" に配置されているはず。

which firefox
/usr/bin/firefox

FirefoxBinaryにpathを指定して、 "add_command_line_options" でheadlessを指定する。

# -*- coding: utf_8 -*-
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

binary = FirefoxBinary('/usr/bin/firefox')
binary.add_command_line_options('-headless')
driver = webdriver.Firefox(firefox_binary=binary)
driver.get('http://www.google.com')
print(driver.page_source)
driver.quit()


HTMLが表示されたら優勝。

ちなみに "-headless" をつけないで "webdriver.Firefox()" で起動させようとすると以下のようになる

~~~
raise WebDriverException(
   "The browser appears to have exited "
   "before we could connect. If you specified a log_file in "
   "the FirefoxBinary constructor, check it for details.")
WebDriverException: Message: The browser appears to have exited before we could connect. If you specified a log_file in the FirefoxBinary constructor, check it for details.


 
上手くSeleniumが起動しない場合の参考は以下
参考:Selenium 3.4.0 Unable to find matching capabilities · Issue #3884 · SeleniumHQ/selenium · GitHub
参考:How to specify Firefox command line options using Selenium WebDriver in Python? - Stack Overflow


 

- command line argumentとFirefoxProfileについて -

コマンドラインからのオプションとして設定できる項目はMozilla公式の以下にまとまっている。

コマンドラインオプション - Mozilla | MDN

"-sage-mode" すれば拡張を全て無効化してくれるので便利といえば便利そうである。
引数だけで設定できる項目は少なめで、後述するProfileを使ってゴニョる。


 
Seleniumのwebdriverには "FirefoxProfile" なるclassがあり、そちらでも設定を記述できる。
一例で書くと以下

# -*- coding: utf_8 -*-
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

# profileの記述
profile = webdriver.FirefoxProfile()
# useragent指定
profile.set_preference("general.useragent.override", "hogehoge")
# 画像を読み込まない
profile.set_preference("permissions.default.image", 2)
# CSSを使わない
profile.set_preference('permissions.default.stylesheet', 2)
# Flashを使わない
profile.set_preference('dom.ipc.plugins.enabled.libflashplayer.so', 'false')

# Firefoxの起動
binary = FirefoxBinary('/usr/bin/firefox')
binary.add_command_line_options('-headless')
driver = webdriver.Firefox(firefox_binary=binary, firefox_profile=profile)

# スクリーンショットを撮って終了
browser.get("hogehoge")
browser.save_screenshot("test.jpg")
browser.quit()


 
一応Firefoxのconfig entriesが一番まとまっているのはこの辺
About:config entries - MozillaZine Knowledge Base
Category:Preferences - MozillaZine Knowledge Base

あと公式サポートも参考に
Profiles - Where Firefox stores your bookmarks, passwords and other user data | Firefox Help

一部使えない(?)ものもあるけど参考にはなる
On Firefox in Selenium WebDriver tests with Python do not want images to load and CSS to render – Selenium Webdriver Trainings

 
Seleniumのオプション周りは以下がちょっと参考になる
selenium.webdriver.firefox.options — Selenium 3.6 documentation
7. WebDriver API — Selenium Python Bindings 2 documentation

 
加えて"webdriver.FirefoxProfile" で既存の設定ファイルを読み込む事もできる。
またProfileにaddExtensionする事で拡張も使える。Proxyを使う方法もある。


 
使っていて分かった事として、消化不良でquit()するとプロセスがゾンビ化して、次以降の起動で以下のようなエラーが出るっぽい。

WebDriverException: Message: connection refused

う〜んと悩んで実際にpsコマンドで見てみるとこんな感じ。

$ ps aux | grep “firefox”
master    3158  0.0  0.0      0     0 ?        Z    16:33   0:00 [firefox] <defunct>
master   12661  0.0  0.0      0     0 ?        Z    16:53   0:00 [firefox] <defunct>
master   13005  0.0  0.0  12948  1088 pts/3    S+   16:54   0:00 grep --color=auto firefox
master   80330  0.0  0.0      0     0 ?        Z    15:08   0:00 [firefox] <defunct>
master   84301  0.0  0.0      0     0 ?        Z    15:12   0:00 [firefox] <defunct>
master  101809  0.0  0.0      0     0 ?        Z    15:34   0:00 [firefox] <defunct>
master  103666  0.0  0.0      0     0 ?        Z    15:37   0:00 [firefox] <defunct>

まあUbuntuFirefoxGUIで使ってた時もこんな感じだったような気がするから許容範囲か…


 

- おわりに -

TODO:
ChromeFirefoxのheadlessを比べるやつを書く
画面操作してみてissue確認する

今後もheadlessで色々できたら良いなと思います。


 

  • 追記 

後輩曰く「Firefoxは.lockみたいなファイルができて〜」という話。
何気なくツイートしていると下記のような事がわかった。


ChromeやPhontomJSと違い、Firefoxはparent.lockをゾンビ化したプロセスが握っている限り次のFirefoxを起動できないのでこのファイルを開放してやる必要があるらしい。う〜ん…