Pythonでスクレイピングというネタはすでに世の中にもQiitaにもたくさん溢れていますが、なんとなくpyqueryが使いやすいという情報が多い気がします。個人的にはBeautiful Soupの良さも知ってもらいたいと思うのでここではBeautiful Soupを使っていきたいと思います。
ちなみにこのエントリーはほとんどの部分がBeautiful Soup4のドキュメントの要約です。もっと詳しい情報が知りたい場合はドキュメントをご覧ください。
英語
http://www.crummy.com/software/BeautifulSoup/bs4/doc/
よくある勘違い
pyqueryはjQueryのようにcssセレクタを使ってHTMLを扱うことができる点がBeautiful Soupよりも使い易いという意見がありますが、それBeautiful Soupでもできます。 (古いバージョンはわかりません) 方法については下の方で説明します。
バージョンについて
現行のバージョンはBeautiful Soup4です。古いバージョンについての解説記事も多いので注意が必要です。ただBeautiful Soup3で動いていたコードは多くの場合Beautiful Soup4に置き換えても動くはずです。
インストール
$ pip install beautifulsoup4
簡単な使い方
BeautifulSoupオブジェクトの作成
平文のHTMLを扱う場合は下のようになります。
from bs4 import BeautifulSoup
html = """
<html>
...
</html>
"""
soup = BeautifulSoup(html)
またURLは直接扱えないのでwebサイトを扱う際はurllib2などと組み合わせると良いでしょう。
import urllib2
from bs4 import BeautifulSoup
html = urllib2.urlopen("http://example.com")
soup = BeautifulSoup(html)
ここでHTMLパーサーについての警告が出る場合はメッセージに従ってパーサーを指定してください。(詳しくはHTMLパーサーについてを参照。)
soup = BeautifulSoup(html, "html.parser")
シンプルなタグの取得方法
HTMLの中からAタグをすべて取得するには
soup.find_all("a")
これで取得できるオブジェクト<class 'bs4.element.ResultSet'>
はlistのように扱うことができます。
すべてのタグではなく先頭の一つだけ取得するには
soup.find("a")
もしくは
soup.a
soup.find("a")
やsoup.a
は、タグがそのHTMLに存在しない場合はNoneが返ります。
取得したタグの情報
取得したタグの属性を取得するには
soup.a.get("href")
取得したタグの中の文字を取得するには
soup.a.string
当然入れ子になっているタグを取得することもできます
soup.p.find_all("a")
条件を絞ったタグの取得
属性などで条件を絞ってタグを取得することも簡単にできます。例えば<a href="/link" class="link">
のようにclassがlinkでhrefが/linkのaタグをすべて取得するには
soup.find_all("a", class_="link", href="/link")
または
soup.find_all("a", attrs={"class": "link", "href": "/link"})
上記一つ目の書き方で注意するのはclassはPythonでは予約語なのでclass_になります。
また、タグを指定しなくても構いません。
soup.find_all(class_="link", href="/link")
soup.find_all(attrs={"class": "link", "href": "/link"})
正規表現を使ったタグの取得
BタグやBODYタグなどbで始まるタグをすべて取得するには
import re
soup.find_all(re.compile("^b"))
”link”を含むhref属性を持っているタグをすべて取得するには
import re
soup.find_all(href=re.compile("link"))
タグの中の文字列に"hello"を含むAタグをすべて取得するには
import re
soup.find_all("a", text=re.compile("hello"))
cssセレクタを使ったタグの取得
find_all
の代わりにselect
を使うとcssセレクタを使ってタグを取得することができます。
soup.select("#link1")
soup.select('a[href^="http://"]')
###書き換え
タグに属性を追加する
a = soup.find("a")
a["target"] = "_blank"
タグを外すのはunwrap
を使います
html = '''
<div>
<a href="/link">spam</a>
</div>
'''
soup = BeautifulSoup(html)
soup.div.a.unwrap()
soup.div
# <div>spam</div>
逆に新しくタグをつける場合はsoup.new_tag
でタグを作ってwrap
で追加します。
html = '''
<div>
<a href="/link">spam</a>
</div>
'''
soup = BeautifulSoup(html)
soup.div.a.wrap(soup.new_tag("p"))
そのほかにもinsert
やextract
など操作用メソッドが充実しているのでコンテンツやタグを追加したり除去したり柔軟に行えます。
###出力
prettify
を呼び出すことできれいに整形して文字列として出力が出来ます。
soup.prettify()
# <html>
# <head>
# <title>
# Hello
# </title>
# </head>
# <body>
# <div>
# <a href="/link">
# spam
# </a>
# </div>
# <div>
# ...
# </div>
# </body>
# </html>
soup.div.prettify()
# <div>
# <a href="/link">
# spam
# </a>
# </div>
HTMLパーサーについて
HTMLパーサーは通常Python標準のhtml.parserが使用されますが、lxmlやhtml5libがインストールされている場合はそちらが優先されて使われます。明示的に指定する場合は下のように指定します。
soup = BeautifulSoup(html, "lxml")
Pythonのバージョンが古い場合、html.parserでは正しくパースできない場合があります。自分の環境の場合Python2.7.3ではパースできてPython2.6系ではできないということがありました。
正しくパースするには可能な限りlxmlやhtml5libをインストールするのが無難です。ただしlxmlなどは外部のCライブラリに依存しているので環境によってはそれらをインストールしなければならないかもしれません。
余談
Beautiful Soupの用途
自分の場合の用途は、複数のブログ記事をまとめてDBに保存している自分のサイトがあるのですが、普段はRSSから取得しますがRSSは件数が少ないのでその場合はHTMLをBeautiful Soupで読み取って内容を保存しています。
また、保存したブログのボディを表示する際に不要な広告を取り除いたりリンクを新しいタブで開くようにaタグにtargetを指定したりなどといった使い方をしています。
参考: http://itkr.net
そういった用途ではBeautiful Soupが優れているのではないかと思います。