Pythonによるスクレイピング&機械学習
Pythonによるスクレイピング&機械学習 開発テクニック BeautifulSoup,scikit-learn,TensorFlowを使ってみようより
学習ログ。
前回
2-1では、
- ログイン機能(Cookie)のあるサイトでのスクレイプ
を行いました。
この章で得られるもの
2-2では、ブラウザを経由したスクレイピングを行っていきます。
環境
Python 3.6.0
コード
こちら(Git)にて
2-2 ブラウザを経由したスクレイピング
Ajax的なデータの取得などの、Javascriptを多用したサイトでは、ブラウザを使用しないと、正しくスクレイプできません。
例えば、PrimeVideoとか、Youtubeとかは、スクロールによってデータを動的に読み込んでいます。
このようなサイトでは、requestモジュールでは、jsが動作せず、ページが更新されないので、スクレイプできない場合があります。
ので、Webブラウザを遠隔操作する手法を用いることでスクレイピングを行います。
この手法を使えば、原則どのようなWebページでもスクレイピングできます。(robot判定のあるページとかは厳しいだろうけれど)
この遠隔操作を行うツールとしてSeleniumが有名らしいです。詳細はこちらから。
また、スクレイプごとにchromeやらfirefoxのブラウザが立ち上がるのは鬱陶しいので、画面なしブラウザのPhantomJSを用います。
環境構築
今回はDockerを用いて、仮想環境でテストしていきます。Dockerの知識は、ものすごく浅いのですが、仮想環境で開発、テストすることにより、他のマシン(AWSとか)に移行する際の環境構築コストを軽減してくれる認識です。参考サイト
そのうち、ちゃんと勉強したい。。
# DockerにUbuntuのイメージを挿入
$ docker pull ubuntu:16.04
# イメージの確認
$ docker images
# Ubuntuを実行しシェルにログイン
$ docker run -it ubuntu:16.04
docker内のシェルにログインができたので、Python3とSeleniumをインストールしていきます。
# Python3 と pip3
$ apt-get update
$ apt-get install -y python3 python3-pip
# Selenium
$ pip3 install selenium
# BeautifulSoup4
$ pip3 install beautifulsoup4
PhantomJSもインストール。
# PhantomJS動かす用のライブラリ
$ apt-get install -y wget libfontconfig
# バイナリをダウンロード、インストール
$ mkdir -p /home/root/src && cd $_
$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
$ tar jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2
$ cd phantomjs-2.1.1-linux-x86_64/bin/
$ cp phantomjs /usr/local/bin/
日本語フォントが表示されるように調整。
# 日本語のフォントのインストール
$ apt-get install -y fonts-migmix
#フォントの設定書き換え
$ cat <<EOF > /etc/fonts/local.conf
> <?xml version="1.0"?>
> <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
> <fontconfig>
> <match target="pattern">
> <test qual="any" name="family">
> <string>serif</string>
> </test>
> <edit name="family" mode="assign" binding="strong">
> <string>MigMix 2P</string>
> </edit>
> </match>
> </fontconfig>
> EOF
ここまでの変更をDockerにubuntu-phantomjsという名前で保存。
# コンテナを終了
$ exit
# 先ほどまで起動していたコンテナを確認 -> COUNTAINER ID(89826230d4f1)を確認
$ docker ps -a
# コンテナをイメージとして保存
$ docker commit 89826230d4f1 ubuntu-phantomjs
# イメージの確認
$ docker images
コミットするとイメージが作成されるので、以下のコマンドで実行できます。
この際、エンコーディングの関係で環境変数を指定しておきます。
どうやってコンテナ内のファイルを編集するのかな、と思ったけれど、-v $HOME:$HOME ubuntu-phantomjs /bin/bash
でホストのディレクトリをマウントしているので、ホスト側で開発すしてコンテナ内で実行すればOKです。違うマシンがデータだけ共有してるみたいですね。ホストディレクトリのマウントの参考
$ docker run -it -v $HOME:$HOME \
-e LANG=ja_JP.UTF-8 \
-e PYTHONIOENCODING=utf_8 \
ubuntu-phantomjs /bin/bash
疲れた。。Dockerfileでひょひょいとイメージ生成できるようにしておきたいです。上のめんどくさいオプションもDockerfileに収められるようなので、いつか書きたい。
(追記:2017/12/20)
Dockerfile書きました。DockerHubにもプッシュしたので、以下のコマンドで環境構築できます。
docker run -it -v $HOME:$HOME syunyooo/python-selenium
作成したコードなどは以下より。
画面キャプチャ
環境構築できたので、Selenium+Phantomjsで画面キャプチャを行います。
コピー不可設定の歌詞サイトとかでやってみます。
from selenium import webdriver
url = "http://j-lyric.net/artist/a0006b5/l00e6aa.html"
# PhantomJSのドライバを得る
browser = webdriver.PhantomJS()
# 暗黙的な待機を最大3秒。ドライバの初期化待ち
browser.implicitly_wait(3)
# URLの読み込み
browser.get(url)
# 画面キャプチャしてファイルに保存
browser.save_screenshot("Website.png")
# ブラウザ終了
browser.quit()
実行結果
以下のようなWebsite.pngが生成されます。
できた。長いから切りましたが、ページ全範囲のスクショが取れています。
あと、なぜかスマホっぽい幅。windowのサイズのデフォルト値っぽいですね。調べるとwindowサイズも設定できるようです。
会員制Webサイトにログイン
前回の作詞サイトにログインしてみます。
前回は、パラメータを明示的に指定していましたが、今回は直接入力->ボタンクリックというようにユーザライクに動かしていきます。
ここで確認する必要があるのは、テキストボックスDOM要素のidと、フォーム要素へのパス。
以下のソースから、ユーザー名はid=user、パスワードはid=pass。
フォーム要素へは、#loginForm form
で特定ができそうです。
<div id="loginForm">
<form action="users.php?action=login&m=try" method="post">
<table>
<tr><td>ユーザー名</td><td><input id="user" name="username_mmlbbs6" type="text" size="12" value=""/></td></tr>
<tr><td>パスワード</td><td><input id="pass" name="password_mmlbbs6" type="password" size="12" /></td></tr>
<tr><td></td><td><input type="submit" value="ログイン" size="8" /></td></tr>
</table>
<input type="hidden" name="back" value="index.php" />
<input type="hidden" name="mml_id" value="0" />
</form>
</div>
以下実装。
from selenium import webdriver
USER = "山田太郎"
PASS = "password"
# PhantomJSのドライバーを得る
browser = webdriver.PhantomJS()
browser.implicitly_wait(3)
# ログインページにアクセス
url_login = "http://uta.pw/sakusibbs/users.php?action=login"
browser.get(url_login)
print("ログインページにアクセスしました")
# テキストボックスに文字を入力
e = browser.find_element_by_id("user")
e.clear()
e.send_keys(USER)
e = browser.find_element_by_id("pass")
e.clear()
e.send_keys(PASS)
# フォームを送信
frm = browser.find_element_by_css_selector("#loginForm form")
frm.submit()
print("情報を入力してログインボタンを押しました")
# マイページのURLを得る
a = browser.find_element_by_css_selector(".islogin a")
url_mypage = a.get_attribute('href')
print("マイページのURL=", url_mypage)
# マイページを表示
browser.get(url_mypage)
# お気に入りのタイトルを列挙
links = browser.find_elements_by_css_selector(
"#favlist li > a")
for a in links:
href = a.get_attribute('href')
title = a.text
print("-", title, ">", href)
Seleniumについて
Seleniumではブラウザごとにドライバが用意されていて、主要なブラウザのドライバは揃っています。
また、bsなしでスクレイピングを行うこともできます。
メソッドとしては、大きく分けて
- Selenium標準のメソッド
- ブラウザごとに用意されているメソッド
があるので、少し注意。
詳細はドキュメント(英語)より。
JSの実行
Seleniumは十分いろいろなメソッドが用意されていますが、やりたいことにメソッドが用意されていない場合、直接JavaScriptを実行できるexecute_script()
メソッドを使えば良いらしい。これでかゆいところに手が届きます。
from selenium import webdriver
# PhantomJSのドライバーを得る
browser = webdriver.PhantomJS()
browser.implicitly_wait(3)
# 適当なWebサイトを開く
browser.get("https://google.com")
# JavaScriptを実行
r = browser.execute_script("return 100 + 50")
print(r)
実行結果
150
次回
2-3章の内容は、
- 色々なスクレイプを行ってみる
です。csvに直したり、tableタグを解析してみたり。
つまり応用なので自分で題材を探して実装しました。
ただ、その題材が自分の大学の学生用ページ(ログインあり)のスクレイピングにしてしまったので、ちょっと怒られるかなと思って、現在、検索にかからないように限定公開に設定しています。
あと、かなりニッチなスクレイピングになるので、まあ良いかあと思っています。
一応、こちらから飛べるので、物好きな方はよろしくお願いいたします。
2-4章は、下の被リンク先から飛んでください。
Appendix(追記:2017/12/20)
前回、以下のリンクでは、値がウニョウニョ動くので BeautifulSoup じゃ対応できないよね的なことを書きました。
Seleniumでは可能です。実際にスクレイプしてみましょう。
まず、取得する要素を特定しましょう。
ここにします。
from selenium import webdriver
import time
browser = webdriver.PhantomJS()
browser.implicitly_wait(3)
browser.get("https://inagoflyer.appspot.com/btcmac")
for i in range(10):
# 3秒ごとに取得
time.sleep(3)
bc_value = browser.find_element_by_id("bitFlyer_BTCJPY_lastprice").text
print(bc_value)
実行結果
1972680
1972744
1972682
1972682
1972682
1972206
1970280
1971176
1969190
1970280
とれてます。いい感じですね。
※ ブラウザ生成後、一定時間たつと、取得する値が変化しない場合がありました。未解決です。。