Python で HTML をパースする (Beautiful Soup)

Beautiful Soup とは

Python 標準の html.parser モジュールは、SAX 形式のイベントドリブンなパーサなため、若干扱いにくいところがあります。 Beautiful Soup ライブラリを使用することで、HTML の要素に簡単にアクセスすることができるようになります。

Beautiful Soup パッケージは次のようにインストールできます。

$ pip install beautifulsoup4

HTML をパースする

最初に、HTML 文字列や HTML ファイルから bs4.BeautifulSoup オブジェクトを生成する必要があります。

HTML 文字列から soup を作成
from bs4 import BeautifulSoup

soup = BeautifulSoup("<html>Hello</html>", features="html.parser")
HTML ファイルから soup を作成
from bs4 import BeautifulSoup

soup = BeautifulSoup(open("input.html"), features="html.parser")

Beautiful Soup 自体には Web 上のリソースをダウンロードする機能は備わっていないので、そのようなケースでは、requests モジュールなどで HTML リソースをダウンロードしておいて、BeautifulSoup コンストラクタに渡してやります。

Web 上の HTML リソースから soup を作成(requests モジュールを使用)
from bs4 import BeautifulSoup
import requests

res = requests.get("https://example.com/")
if res.status_code != requests.codes.ok:
    print("Failed to fetch data")
    exit(1)
soup = BeautifulSoup(res.text, features="html.parser")

最初に登場する要素を見つける

BeautifulSoup オブジェクトを生成したら、各要素の検索を行えるようになります。 一番簡単なのは、BeautifulSoup オブジェクトのプロパティで HTML 要素のタグ名を指定する方法です。 次の例では、最初に登場する p 要素(bs4.element.Tag オブジェクト)を取得しています。

from bs4 import BeautifulSoup

html_doc = """<html><body>
<h1>Title</h1>
<p class="foo">This is <b>bold</b> text.</p>
</body></html>
"""

soup = BeautifulSoup(html_doc, features="html.parser")
print(type(soup.p))  # => <class 'bs4.element.Tag'>

HTML 要素の Tag オブジェクトを取得できたら、次のように直感的にその内容を参照できます。

print(soup.p)               # => <p class="foo">This is <b>bold</b> text.</p>
print(soup.p.name)          # => 'p'
print(soup.p.text)          # => 'This is bold text.'
print(soup.p["class"])      # => ['foo']
print(soup.p.get("class"))  # => ['foo']

属性値の取得方法には [] を使う方法と、get() 使う方法があることに注意してください。 指定した属性値が存在しない場合、[]KeyError を発生させるのに対し、get()None を返します。

いろいろな条件で要素を見つける

前述の例では、p 要素を参照するときに soup.p のように記述していました。 その代わりに find メソッドを使用すると、いろいろな条件で HTML 要素を検索することができます。

# 最初の p 要素
elem = soup.find("p")

# id 属性が sidebar である要素
elem = soup.find(id="sidebar")

# class 属性に comment を含む要素
elem = soup.find(class_=re.compile("comment"))

# href 属性に特定のドメイン名を含む a 要素
elem = soup.find("a", href=re.compile("^https://example.com/"))

class キーワードは Python の予約語のため、HTML 要素の class 属性値を検索するには、末尾に _ の付いた class_ というパラメータ名を使用することに注意してください。

すでに find メソッドによって見つけた要素がある場合は、その要素を基準にして子要素を検索することができます。

title = soup.find("head").find("title")

要素が見つからない場合、find メソッドは None を返します。

特定の条件に一致する要素をすべて見つける

指定したタグ名の要素をすべて取得するには、find_all メソッドを使用します。 戻り値は、bs4.element.ResultSet オブジェクトで、for ループを使って見つかった要素を順番に処理できます。 次の例では、すべての a 要素を取得しています。

a 要素をすべて取得する
from bs4 import BeautifulSoup

html_doc = '''<html><body>
<h1>Title</h1>
<a href="https://google.com/">Google</a>
<a href="https://yahoo.com/">Yahoo</a>
</body></html>'''

soup = BeautifulSoup(html_doc, features="html.parser")
links = soup.find_all("a")
for link in links:
    print(link.text)
実行結果
Google
Yahoo

見つかった要素の属性は、attrs プロパティで参照できます。 次の例では、HTML 内のすべての a 要素を検索し、その href 属性を出力しています。

links = soup.find_all("a")
for link in links:
    if "href" in link.attrs:
        print(link.text, ":", link.attrs["href"])
実行結果
Google : http://google.com/
Yahoo : http://yahoo.com/

CSS セレクタによる要素の検索

find_all の代わりに、select メソッドを使用すると、CSS セレクタによる要素の検索を行えます。

elems = soup.select("a")           # すべての a 要素
elems = soup.select("table tr")    # table 要素以下の tr 要素
elems = soup.select(".hoge")       # class 属性に hoge を含む要素
elems = soup.select("table.hoge")  # class 属性に hoge を含む table 要素
elems = soup.select("#nav")        # id 属性が nav である要素
elems = soup.select("#nav a")      # id 属性が nav である要素以下の a 要素