どんな事でもお気軽にお問い合わせください
0120-803-656
24時間受付いたします

Mechanizeが動かないよ~! そんな時はBeautiful Soupでハッピーハッピー☆


こんにちは。
開発チームのワイルド担当、萬代です。

今回は、Pythonでクローリングする際によく利用されるMechanizeと、HTMLを解析するためのライブラリ、Beautiful Soupを使ってハッピースクレイピング!という趣旨で進めていきたいと思います。
Beautiful Soup自体がMechanizeの問題を解決するものではありませんので、タイトルは多少盛ってありますが悪しからず。

Beautiful Soupとは

Beautiful Soupは、HT(X)MLパーサー機能を提供するPythonライブラリです。
パーサー機能だけを提供するものなので、Mechanize単品でどうにかなる部分もあるのですが
Mechanizeでは解析できないHTMLも解析できるといったところがBeautiful Soupの良さでしょうか。

今回は、Beautiful SoupとMechanizeをうまく使って、Mechanizeでは解析できないHTMLを
Beautiful Soupでバッサバッサとやっつける方法について考えてみます。

今回は、Python2.7系を使って進めていきます。

必要なライブラリの用意

pipを使って必要なライブラリを入手します。

pip install mechanize beautifulsoup4
# 必要に応じてsudoしてください
# beautifulsoup とするとBeautiful Soup3系がインストールされてしまうので注意
# ちなみに私の環境では、だいたいどこも3と4が同居している状態です

 

Mechanizeで拾ったHTMLソースをBeautiful Soupで解析する方法

Mechanizeで取得したHTMLをBeautiful Soupに取り込んで解析するのは、とても簡単です。

br.open('https://beyondjapan.com')
soup = BeautifulSoup(br.response().read())

 

上記のコマンドで、https://beyondjapan.comからHTMLを取得し、Beautiful Soupによる解析が完了したオブジェクトがsoupの中に入ります。

 

Beautiful Soupのパーサーエンジンを切り替える

Beautiful Soupでは、解析に使うパーサーエンジンを状況に応じて切り替えて使う事ができます。
Beautiful Soupの公式ページには、lxmlを使うのを、速度面からおすすめしているようです。
今回はlxmlを使って解析していきますので、lxmlライブラリをpipを使ってインストールします。

pip install lxml
# 必要に応じてsudoしてください

 

コード内でBeautiful Soupを呼び出す時にパーサーエンジンの指定を行う訳ですが
何も指定しない場合は、Python標準のHTMLパーサーを使用するようです。
標準エラーに何やらエラーが出ますので、気になる方は標準パーサーを使う場合でも
パーサーエンジンの指定はした方がよいかと思います。

# パーサーを指定せずに実行した場合
soup = BeautifulSoup(br.response().read())

 

問題なくパースできますが、以下のようなエラーが表示されます

/usr/local/lib/python2.7/site-packages/bs4/__init__.py:166: UserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

To get rid of this warning, change this:

 BeautifulSoup([your markup])

to this:

 BeautifulSoup([your markup], "lxml")

  markup_type=markup_type))

 

lxml を指定してHTMLを解析するには、以下のように指定します。

soup = BeautifulSoup(br.response().read(), "lxml")

 

Beautiful Soupで解析したHTMLをMechanizeに戻してクロールを続ける

Mechanizeで解析できなかったHTMLをBeautiful Soupで解析したはいいけれど
パースした結果を元に更に先のページへ遷移したということはよくあるかと思います。

例えば、フォームでデータ送信したい場合、よくわからないけれど、以下の箇所で
パースエラーがでて困る、なんて事はよくある話です。

br.select_form(nr=0)

Traceback (most recent call last):
  File "/home/vagrant/workspace/mechanize_test/test3.py", line 13, in <module>
    br.select_form(nr=0)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_mechanize.py", line 499, in select_form
    global_form = self._factory.global_form
  File "/usr/local/lib/python2.7/site-packages/mechanize/_html.py", line 544, in __getattr__
    self.forms()
  File "/usr/local/lib/python2.7/site-packages/mechanize/_html.py", line 557, in forms
    self._forms_factory.forms())
  File "/usr/local/lib/python2.7/site-packages/mechanize/_html.py", line 237, in forms
    _urlunparse=_rfc3986.urlunsplit,
  File "/usr/local/lib/python2.7/site-packages/mechanize/_form.py", line 844, in ParseResponseEx
    _urlunparse=_urlunparse,
  File "/usr/local/lib/python2.7/site-packages/mechanize/_form.py", line 981, in _ParseFileEx
    fp.feed(data)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_form.py", line 758, in feed
    _sgmllib_copy.SGMLParser.feed(self, data)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_sgmllib_copy.py", line 110, in feed
    self.goahead(0)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_sgmllib_copy.py", line 144, in goahead
    k = self.parse_starttag(i)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_sgmllib_copy.py", line 302, in parse_starttag
    self.finish_starttag(tag, attrs)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_sgmllib_copy.py", line 347, in finish_starttag
    self.handle_starttag(tag, method, attrs)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_sgmllib_copy.py", line 387, in handle_starttag
    method(attrs)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_form.py", line 735, in do_option
    _AbstractFormParser._start_option(self, attrs)
  File "/usr/local/lib/python2.7/site-packages/mechanize/_form.py", line 480, in _start_option
    raise ParseError("OPTION outside of SELECT")
mechanize._form.ParseError: OPTION outside of SELECT

 

こういう場合、大抵パースできず、諦めて他の方法を探すという事になってしまうので
一旦、Beautiful Soupに解析させて、必要なフォームだけを抜き出して加工し、Mechanizeに戻してやると、しれっと先へ進めます。
(OPTION outside of SELECTというエラー自体は、selectタグの外にoptionタグがあるよ(もしくはselectタグにoptionタグがないよ)的なエラーのようです)

今回は、selectタグ内にoptionタグがなかったので、Beautiful Soupでoptionタグを追加した上で、Mechanizeに戻してみます。

# Beautiful SoupでHTMLを解析
soup = BeautifulSoup(br.response().read(), 'lxml')

# formタグ部分を抽出
f = soup.find('form')

# 追加したいHTMLタグのBeautiful Soupオブジェクトを生成
o = BeautifulSoup('<option value="hogehoge" selected>fugafuga</option>', 'lxml')

# optionタグ部分の抽出
option = o.option

# form内のselectタグに対して、optionタグを追加
f.find(id='target_select').append(option)

# Mechanizeのレスポンスオブジェクトを作成して、Mechanizeに登録
response = mechanize.make_response(str(f), [("Content-Type", "text/html")], br.geturl(), 200, "OK")
br.set_response(response)

# Mechanizeでformを指定してみる
br.select_form(nr=0)

 

mechanize.make_response() と set_response() でMechanizeにBeautiful Soupで解析したHTMLを戻す事ができると書いてある日本語のサイトが見つからず四苦八苦しましたが、こちらのサイトを参考に、何とか解決できました。

Form Handling With Mechanize And Beautifulsoup · Todd Hayton

英語のサイトですが、他にも主にMechanizeの使い方について詳しく載っていて、色々と勉強になります。

以上です。


お問い合わせ 採用情報 エンジニアブログ
ISO27001認証
Contact PageTop
株式会社ビヨンド

© beyond Co., Ltd. All rights reserved.