あっかぎのページ

Raspberry PiでSeleniumを使ってスクレイピング

20150219_2

Raspberyy PiでSeleniumを使ってスクレイピングするお話。
(2017/06/22追記 Seleniumのアップデートで起動しなくなったので追記しました。)

背景

20150925_1

ゆうちょのUIが変更になって今までのスクリプトで残高確認できなくなったので、スクリプトを改良しました。前まではRubyのmechanizeというツールでやっていましたが、Javascriptでログイン手続きがあったりフォームの隠し属性があったりと面倒でした。

そこで、今回はブラウザの人間操作を同じように再現できる方法として、Seleniumというツールを使った方法を試してみました。

Raspberry PiにSeleniumを導入

Seleniumそのものはスクリプト上からブラウザを立ち上げて、スクリプトからブラウザ上にコマンドや操作をしていくツールです。Webページのテストやデバッグ作業を自動化するのにとても便利です。

今回はそのSeleniumをRaspberry Piに導入して、さらにヘッドレス(headless)というブラウザを起動しなくてすむ方法を紹介します。と言ってもこちらのサイトの方法をそのままするだけですが・・・

まずはiceweaselというFirefoxのdebian版とxvfbというX端末をエミュレートするツールのインストールです。

$ sudo apt-get install iceweasel
$ sudo apt-get install xvfb

次にSeleniumのドライバーとheadless(ブラウザなし動作用)のgemをインストールします。Rubyのgemシステムなので必要なパッケージを自動的にインストールしてくれます。

$ gem install selenium-webdriver
$ gem install headless

次にIP6関連の設定をOFFしておきます。(/etc/modprobe.d/ipv6.confの2行目のコメントを外す。/etc/hostsのip6をコメント化)

$ sudo vi /etc/modprobe.d/ipv6.conf

alias net-pf-10 off
alias ipv6 off
$ sudo vi /etc/hosts

127.0.0.1   localhost
#::1        localhost ip6-localhost ip6-loopback
#fe00::0        ip6-localnet
#ff00::0        ip6-mcastprefix
#ff02::1        ip6-allnodes
#ff02::2        ip6-allrouters

127.0.1.1   raspberrypi

以上で設定は完了しましたので、簡単なチェックスクリプト(a.rb)を作ります。
(追記)seleniumのアップデートに伴い、このままでは起動しなくなりました。解決方法を最後に記述します。

require 'selenium-webdriver'
require 'headless'

headless = Headless.new
headless.start

driver = Selenium::WebDriver.for :firefox
driver.navigate.to 'http://www.yahoo.co.jp/'

# タイトル、キャプチャ、html出力
puts driver.title
driver.save_screenshot('yahoo.png')
open('yahoo.html', 'w'){|f| f.write driver.page_source }

driver.quit
headless.destroy

上のスクリプトでは、yahooのタイトルやキャプチャ画像、htmlを出力してseleniumがきちんと動作するかを確認しています。

$ ruby a.rb
Yahoo! JAPAN

スクリプトを動作すると上のように表示されて、yahoo.pngとyahoo.htmlが出力されていれば動作は問題ないと思います。Raspberry Pi 2で40秒くらいかかり、処理速度はかなり遅いです。ただ、人間のブラウザ操作をそのまま再現できるので、とても便利だと思います。

出力したのが下の画像になりますが、日本語もきちんと表示されているのがわかりますね。

20150925_2

もし、文字のところが四角(□)になって文字化けしているときは、日本語のフォントが入っていないからかもしれません。その時は次のコマンドで日本語フォント(fonts-ipafont)を入れて、もう一度試してみてください。

$ sudo apt-get install fonts-ipafont

seleniumのアップデートによるエラー対策(2017/06/22 追記)

seleniumのアップデートにより、上のやり方のままでは次のエラーが出て動かなくなりました。

/home/pi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/common/service.rb:59:in `binary_path':  Unable to find Mozilla geckodriver. Please download the server from https://github.com/mozilla/geckodriver/releases and place it somewhere on your PATH. More info at https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver. (Selenium::WebDriver::Error::WebDriverError)
        from /home/pi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/common/service.rb:49:in `initialize'
        from /home/pi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/firefox/w3c_bridge.rb:40:in `new'
        from /home/pi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/firefox/w3c_bridge.rb:40:in `initialize'
        from /home/pi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/common/driver.rb:52:in `new'
        from /home/pi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver/common/driver.rb:52:in `for'
        from /home/pi/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.0/lib/selenium/webdriver.rb:88:in `for'
        from a.rb:7:in `<main>'

エラーをみるとわかるのですが、seleniumのアップデートに伴いgeckodriverを使うようになったようです。
ということで、次のサイトからRaspberry Pi用のgeckodriverをインストールします。

Raspberry Piはarmチップを使っているので、選択するのはgeckodriver-v0.17.0-arm7hf.tar.gzです。(2017/06/22はv0.17.0が最新でした)

$ wget https://github.com/mozilla/geckodriver/releases/download/v0.17.0/geckodriver-v0.17.0-arm7hf.tar.gz
$ tar zxvf geckodriver-v0.17.0-arm7hf.tar.gz
$ sudo mv geckodriver /usr/local/bin
$ geckodriver --version
geckodriver 0.17.0

The source code of this program is available at
https://github.com/mozilla/geckodriver.

This program is subject to the terms of the Mozilla Public License 2.0.
You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.

ここではgeckodriverを/usr/local/binにしています。PATHがきちんと通っていて正しいバイナリがダウンロードできていれば、上のようにバージョンチェックするときちんと返答すると思います。
この作業をこの記事の間に挟むことで、seleniumのheadless環境が構築できます。

それにしても、レンダリングがきれいになった代わりに、ものすごく動作が遅くなった気がします。Raspberry Pi 3で動作させても応答に1分近くかかっています。
前にも増してお気軽感が薄れた気がしますね。1日数回の監視スクリプトは気になりませんが、開発中とかテストやチェックで繰り返しが必要になると、この動作待ち時間がかなり負担に感じます。

ゆうちょ用のスクリプト

かなりハードコーディングな部分がありますが、自分用のゆうちょスクリプトです。定期的にcronで動作させて残高チェックをしています。

#!/usr/bin/env ruby

require 'selenium-webdriver'
require 'headless'

headless = Headless.new
headless.start

class Yucho
    def initialize(id, pw, url="")
        @id = id
        @pw = pw
        @url = 'https://direct.jp-bank.japanpost.jp/tp1web/U010101WAK.do?link_id=ycDctLgn'
        @d   = Selenium::WebDriver.for :firefox
        @d.navigate.to @url

        @okyakusamaBangou1  = id[0..3]
        @okyakusamaBangou2  = id[4..7]
        @okyakusamaBangou3  = id[8..12]
    end

    def login
        # 1st page
        @d.find_element(:name, 'okyakusamaBangou1').send_keys( @okyakusamaBangou1 )
        @d.find_element(:name, 'okyakusamaBangou2').send_keys( @okyakusamaBangou2 )
        @d.find_element(:name, 'okyakusamaBangou3').send_keys( @okyakusamaBangou3 )
        @d.find_element(:name, 'U010103').click

        5.times do |i|
            begin
                h1 = @d.find_element(:css, 'h1').text
                if h1 =~ /合言葉/
                    # puts '合言葉'
                    login_aikotoba
                elsif h1 =~ /お知らせ/
                    # puts 'お知らせ'
                    login_oshirase
                elsif h1 =~ /パスワード/
                    # puts 'パスワード'
                    login_password
                end
            rescue Selenium::WebDriver::Error::NoSuchElementError => e
                # ログインok
                break
            end
        end

        puts @d.find_element(:css, '.txtBalanceTy01 span').text
    end

    def login_password
        @d.find_element(:name, 'loginPassword').send_keys(@pw)
        @d.find_element(:name, 'U010302').click
    end

    def login_aikotoba
        q = @d.find_element(:css, '.listTy02 dd').text
        a = '答え1'  if q =~ /合言葉1/
        a = '答え2'  if q =~ /合言葉2/
        a = '答え3'  if q =~ /合言葉3/

        @d.find_element(:name, 'aikotoba').send_keys( a )
        @d.find_element(:link_text => '次へ').click
    end

    def login_oshirase
        @d.find_element(:link_text => 'ダイレクトトップ').click
    end

    def save(path)
        @d.save_screenshot(path + '.png')
        open(path, 'w'){|f| f.write @d.page_source }
    end
end

y = Yucho.new('1234567812345', 'password')
y.login

headless.destroy

合言葉やお知らせページがあったりするので、いろいろと小細工しています。基本はブラウザ入力の手順をスクリプトでしているだけなので、1度作ってしまえばあとは自動化できるので便利ですね。

まだ、ゆうちょのUIができたばっかりなので、いろんな変化ができた場合は変更しないとダメですが、ご参考程度に。

おわりに

Raspberry Piなのでブラウザ動作をエミュレーションする今回のselenium + xvfbはとても動作が遅いです。ただ、1度スクリプトを書いてしまえば自動化できるのでとても便利です。今までmechanizeやnokogiriでスクレイピングをしていましたが、ログインやJavascriptだらけのサイトにはかなり有効だと思います。

参考サイト