ぽよメモ

レガシーシステム考古学専攻

BeautifulSoupとMechanizeでwebスクレイピング

Pythonの勉強をはじめました。
手始めに大学の学務課のサイトから休講情報などを取ってこれるようにしたいなぁと思い、webスクレイピングから始めてみることにしました。
使うのはPython(今回は2.7.10)、BeautifulSoup、Mechanize、lxmlです。
pyenvでPythonのバージョンを管理しているのですが、ここでpipの挙動が何やら怪しく、少し手間取りました。どうもpipが2つインストールされていたらしく、何も指定しないままだと~/.pyenv/shims以下にあるpipではなくなぜか入っていたpython-pipが動くようになっていたようでパッケージ導入に失敗したことも有りましたがそれは割愛。

pip install BeautifulSoup4
pip install mechanize
sudo apt-get install libxml2-dev
sudo apt-get install libxslt
pip install lxml

とりあえずまぁこれで大丈夫です。

# coding:UTF-8
import re
from bs4 import BeautifulSoup
import mechanize
br = mechanize.Browser()
br.set_handle_equiv(True)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)
br.addheaders = [('User-agent', 'Mozila/5.0(X11; U; Linux i686; en-us; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]

とりあえず下準備は完了。
これからmechanizeにしてもらう仕事は、

  1. サイトを開く
  2. フォームを選択
  3. ユーザ名とパスワードを選択
  4. データを送信

ここまでがmechanizeの仕事です。
ここからBeautifulSoupにお任せして、

  1. 該当部分を抽出、整形

してもらいます。

urllogin = "学務課のurl"
username = "ユーザ名"
password = "パスワード"
br.open(urllogin)

とりあえずこれでサイトを開けました。なお、今回用いるサイトは認証ポータルサイトに一旦リダイレクトされます。urlloginのurlとは異なるサイトに飛ばされることになっています。
ここで、このポータルサイトのソースを確認し、select_formを使って入力フォームを決定します。

<form id="login" action="/idp/Authn/UserPassword" method="post">
<section>
ユーザ名/Username
<label for="username">Username</label>
<input name="j_username" type="text" value="">
</section>
<section>
パスワード/Password
<label for="password">Password</label>
<input name="j_password" type="password" value="">
</section>
<br>
<section>
   <button type="submit">ログイン/Login</button>
</section>
</form>

幸い認証サイトは非常に簡単なページで、フォームも一つしか存在しませんでした。(適当にタグを削除整形してわざと簡単にして表示しています)

br.select_form(nr = 0)
br['j_username'] = username
br['j_password'] = password
br.submit()

これで認証サイトのフォームに入力、および送信が完了しました。
ここから更に一旦リダイレクトのページを挟んで学務課に飛ばされるはずなのですが、なぜかうまく行きませんでした。そのままではリダイレクトされないようで、続行ボタンを押してやる必要があるようでした。
そこでコードを追加。

br.select_form(nr = 0)
br.submit()

これでログインが完了。学務課の個人用ページにリダイレクトされるので後はスクレイピングするだけです。

html = br.response().read()
bs = BeautifulSoup(html, 'lxml')
for t in bs.findAll('p', {'class':'info_message'}):
    print(t.text.strip())
else:
    print('done')

ここで何も考えずprint(t)するとhtmlのタグごとごっそり表示されます。なのでt.text.strip()とすることで余計な空白及びタグを除去しています。(ここで躓いた話は後日)
というわけで上記のものを全て合わせて、わかりやすいように進捗状況の報告も加え、

# coding:UTF-8
import re
from bs4 import BeautifulSoup
import mechanize
[f:id:pudding_info:20160822202402p:plain]
br = mechanize.Browser()
br.set_handle_equiv(True)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)
br.addheaders = [('User-agent', 'Mozila/5.0(X11; U; Linux i686; en-us; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]
urllogin = "学務課のurl"
username = "ユーザ名"
password = "パスワード"
br.open(urllogin)
print('シボレス認証にアクセスしました')
br.select_form(nr = 0)
br['j_username'] = username
br['j_password'] = password
br.submit()
print('ユーザ名及びパスワードを入力、送信しました。')
print('リダイレクト中です。')
br.select_form(nr = 0)
br.submit()
print('学務課公式サイトにアクセスしました。')
html = br.response().read()
bs = BeautifulSoup(html, 'lxml')
print('通知を検索中です。')
print('-'*20)
for t in bs.findAll('p', {'class':'info_message'}):
    print(t.text.strip())
else:
    print('-'*20)
    print('現在上記の通知が出ています。')

実行結果は以下の様な感じです。 f:id:pudding_info:20160823130152p:plain 正直思ったよりもmechanizeの挙動がわかりやすく、通常のサイトならおそらく難しいことはなにもないだろうと思います(今回はリダイレクトされるページがいくつかあり良くわからないところが多かった)。 いまのところログインできなかった場合などを想定していないので、今後そういった処理も追加していこうと思っています。