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を使って必要なライブラリを入手します。
1 2 3 4 | pip install mechanize beautifulsoup4 # 必要に応じてsudoしてください # beautifulsoup とするとBeautiful Soup3系がインストールされてしまうので注意 # ちなみに私の環境では、だいたいどこも3と4が同居している状態です |
Mechanizeで拾ったHTMLソースをBeautiful Soupで解析する方法
Mechanizeで取得したHTMLをBeautiful Soupに取り込んで解析するのは、とても簡単です。
1 2 | soup = BeautifulSoup(br.response().read()) |
上記のコマンドで、https://beyondjapan.comからHTMLを取得し、Beautiful Soupによる解析が完了したオブジェクトがsoupの中に入ります。
Beautiful Soupのパーサーエンジンを切り替える
Beautiful Soupでは、解析に使うパーサーエンジンを状況に応じて切り替えて使う事ができます。
Beautiful Soupの公式ページには、lxmlを使うのを、速度面からおすすめしているようです。
今回はlxmlを使って解析していきますので、lxmlライブラリをpipを使ってインストールします。
1 2 | pip install lxml # 必要に応じてsudoしてください |
コード内でBeautiful Soupを呼び出す時にパーサーエンジンの指定を行う訳ですが
何も指定しない場合は、Python標準のHTMLパーサーを使用するようです。
標準エラーに何やらエラーが出ますので、気になる方は標準パーサーを使う場合でも
パーサーエンジンの指定はした方がよいかと思います。
1 2 | # パーサーを指定せずに実行した場合 soup = BeautifulSoup(br.response().read()) |
問題なくパースできますが、以下のようなエラーが表示されます
1 2 3 4 5 6 7 8 9 10 11 | /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を解析するには、以下のように指定します。
1 | soup = BeautifulSoup(br.response().read(), "lxml" ) |
Beautiful Soupで解析したHTMLをMechanizeに戻してクロールを続ける
Mechanizeで解析できなかったHTMLをBeautiful Soupで解析したはいいけれど
パースした結果を元に更に先のページへ遷移したということはよくあるかと思います。
例えば、フォームでデータ送信したい場合、よくわからないけれど、以下の箇所で
パースエラーがでて困る、なんて事はよくある話です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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に戻してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 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の使い方について詳しく載っていて、色々と勉強になります。
以上です。