PythonとBeautiful Soupでスクレイピング

  • 450
    いいね
  • 0
    コメント

Pythonでスクレイピングというネタはすでに世の中にもQiitaにもたくさん溢れていますが、なんとなくpyqueryが使いやすいという情報が多い気がします。個人的にはBeautiful Soupの良さも知ってもらいたいと思うのでここではBeautiful Soupを使っていきたいと思います。

ちなみにこのエントリーはほとんどの部分がBeautiful Soup4のドキュメントの要約です。もっと詳しい情報が知りたい場合はドキュメントをご覧ください。

英語
http://www.crummy.com/software/BeautifulSoup/bs4/doc/

日本語
http://kondou.com/BS4/

よくある勘違い

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"))

そのほかにもinsertextractなど操作用メソッドが充実しているのでコンテンツやタグを追加したり除去したり柔軟に行えます。

出力

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が優れているのではないかと思います。