【Python】Selenium WebDriver で Webスクレイピング

Python で Webスクレイピング する場合, これまで mechanize や Beautiful Soup を使う事が多かったのですが Selenium WebDriver を試してみました。

Webクローリング&スクレイピング

Webクローリング&スクレイピングには, 以下のような機能が必要です。

  • ドメインからHTMLデータを取り出す
  • HTMLを Parseし必要な情報を取り出す
  • 情報を格納する
  • ページ遷移してこのプロセスを繰り返す

クローリングは, 複数のWebサイトの複数のページにアクセスし情報を取得する技術で, ページ内のリンクを探す, 内部リンクと外部リンクの違いを評価する, 再帰的なページ遷移などの機能が必要になります。特に, クローラが拾ってきた HTML, XML, pdfなどから任意の情報を取得する技術をスクレイピングといいます。

スクレイピング時に JavaScriptを解釈したい場合は Selenium を使う方法があります。

CentOSにSelenium環境を構築

今回はOSXで FirePath を使って xpath を取得しながらコードを書いていったので Firefox にしましたが, AWSやVPSなどGUI環境がない場合, ヘッドレスブラウザの Phantomjs を使う手もあります。

# python 2.7
$ yum -y install xorg-x11-server-Xvfb
$ yum -y install firefox
$ export DISPLAY=:1
$ pip install selenium
$ pip install pyvirtualdisplay

基本的な Selenium の使い方はDocsを参考に。

Ubuntu14.04の場合も書いておきます。

# python 2.7
$ sudo apt-get install xvfb
$ sudo apt-get install firefox
$ export DISPLAY=:1
$ pip install selenium
$ pip install pyvirtualdisplay

FirePathでxpathを取得する

Seleniumでは DOMTree 上の Element を特定するために Locator という仕組みを使います。xpath, CSS 等を指定できます。
今回はFirefoxのFirePath Add-onsを使って xpath を取得してみます。

要素で右クリックを選択して, Inspect in FirePath で簡単に取得できます。

firepath

googleトップページのinputタグなら //*[@id=’searchText’] です。一度取得したElementに対して相対pathで移動もできます。
Locatorによる特定では, DOM変更に対して弱い書き方をしないための注意が必要です。

WebサイトにLoginしてみる

Loginする例です。覚えることは割と少ないと思います。

# -*- coding: utf-8 -*-

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()
driver.get('https://example.com')

loginid = driver.find_element_by_xpath("//input[@name='P001']")
password = driver.find_element_by_xpath("//input[@name='P002']")

loginid.send_keys("your-id")
password.send_keys("your-password")

submit = driver.find_element_by_name("loginBtn")
submit.send_keys(Keys.RETURN)

実際には実行してみないとわからないことが色々とありました。
implicitly_wait(time_to_wait) で DOM が構築されるまで wait したり, send_keys() の引数は OSX は数値で OK だったけど CentOS は文字リテラルでないとダメだったり, NoSuchElementExceptionのエラーハンドリング, Proxy設定とか中々大変。

# -*- coding: utf-8 -*-

from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException

def is_element_present(self, xpath):
	try:
		self.driver.find_element_by_xpath(xpath)
		return True
	except NoSuchElementException, e: 
		return False

異なる環境に移行したり, ブラウザのバージョンを上げたら Selenium との相性もあるので一度は動作確認した方が良さそうです。

WebDriver API との不整合でどうしてもブラウザのバージョンを下げたい時もあるかもしれません。
補足として, 特定バージョンの FireFox をインストールする方法を書いておきます。例として, Firefox 38.0 をインストールしてみます。

$ cd /usr/local
$ wget https://ftp.mozilla.org/pub/firefox/releases/38.0/linux-x86_64/ja/firefox-38.0.tar.bz2
$ tar xvjf firefox-38.0.tar.bz2
$ firefox/firefox -V
$ sudo ln -s /usr/local/firefox/firefox /usr/bin/firefox

Xvfb

メモリ1GBのマシンで定期実行でスクレイピングしていたら `Can’t load the profile や The browser appears to have exited before we could connect, Cannot allocate memory` のエラーにより Selenium の起動が不安定になる事がありました。

FirefoxにSocketで繋ぎにいけない理由は様々だと思いますが, 今回は単にメモリ不足が原因な気がしました。

$ free
             total       used       free     shared    buffers     cached
Mem:       1017764     944412      73352        336       1580      23072
-/+ buffers/cache:     919760      98004
Swap:      1046524    1046524          0

freeでusedが高いので占有しているプロセスを調べみると, 大量のXvfb(Display server)が残っていました。

$ ps -xl --sort -vsize
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1000 12275     1  20   0 226284  2744 poll_s Sl   ?          0:23 Xvfb -br -screen 0 1024x768x
0  1000 11596     1  20   0 226276   116 poll_s Sl   ?          0:35 Xvfb -br -screen 0 1024x768x
0  1000  5902     1  20   0 225308  9656 poll_s Sl   ?          0:08 Xvfb -br -screen 0 1024x768x
0  1000 29754     1  20   0 225300    72 poll_s Sl   ?          0:15 Xvfb -br -screen 0 1024x768x
0  1000 20389     1  20   0 225024 11956 poll_s Sl   ?          3:09 Xvfb -br -screen 0 1024x768x
0  1000 13461     1  20   0 224996 17928 poll_s Sl   ?          0:03 Xvfb -br -screen 0 1024x768x
...

とりあえず killall しましたが, ちゃんとエラー時も display.stop() と driver.close() を忘れないにします。

MongoDBでJournalファイルのサイズ制限

さらに, MongoDBの Journalファイルがディスク容量を喰ってしまう問題もありました。
さくらのVPS メモリ1GB/SSD30GB プランではあまり MongoDB にディスク容量を割きたくありません。
/etc/mongod.conf で smallfilesを trueにすることで, Journalファイルのサイズを制限させます。

$ sudo vim /etc/mongodb.conf
# Enable journaling, https://www.mongodb.org/display/DOCS/Journaling
journal=true
smallfiles=true

設定後, mongod を再起動します。

$ sudo service mongodb restart
mongodb stop/waiting
mongodb start/running, process 17473

[追記] Firefox と Selenium のアップデート

Ubuntu14.04 で Firefox (49.02) と Selenium (3.0.1) の組み合わせにアップデートしたのでコマンドのメモを残しておきます。

$ firefox --version
Mozilla Firefox 38.0
$ sudo apt-get update
$ sudo apt-get install firefox
$ firefox --version
Mozilla Firefox 49.0.2

$ sudo pip install -U pip
$ sudo pip install -U  selenium==3.0.1

$ wget https://github.com/mozilla/geckodriver/releases/download/v0.12.0/geckodriver-v0.12.0-linux64.tar.gz
$ tar -xvzf  geckodriver-v0.12.0-linux64.tar.gz
$ chmod +x geckodriver
$ sudo cp geckodriver /usr/bin/

[1] pypi/selenium
[2] 明示的な待機 と 暗黙的な待機
[3] mozilla/geckodriver
[4] WebDriver click() vs JavaScript click()