この記事では、Beautiful Soupを使ったデータの抽出を解説します。
LinkBeautiful Soup: We called him Tortoise because he taught us.
Beautiful Soup
は標準モジュールではないのでインストールが必要です。以下のコマンドをターミナルで実行することでインストールできます。
pip install beautifulsoup4
解析に使用するパーサーもインストールします。lxml
がお勧めされています。
pip install lxml
Beautiful Soup
を使用するには、まず以下のようにbs4.BeautifulSoup
をインポートし、
from bs4 import BeautifulSoup
BeautifulSoup()
にHTMLドキュメントを渡してインスタンスを生成します。
soup = BeautifulSoup(doc, "lxml")
指定するドキュメントは文字列でもファイルオブジェクトでも可能です。
# 文字列を指定
doc = '''<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ブラウザタブに表示される文字</title>
</head>
<body>
<h1>ページタイトル</h1>
<p>あい<b>うえ</b>お</p>
</body>
</html>'''
soup = BeautifulSoup(doc, "lxml")
# ファイルを指定
f = open('BeautifulSoup/index.html')
soup = BeautifulSoup(f, "lxml")
使用するパーサーは明示的に指定しなくても適したものが選択されますが、実行時に以下のような警告が表示されます。
/Python/BeautifulSoup/main.py:21: GuessedAtParserWarning: 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.
The code that caused this warning is on line 21 of the file /Python/BeautifulSoup/main.py. To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.
soup = BeautifulSoup(f)
別の環境で実行した際に異なった挙動をすることがあるので明示的に指定した方が安全です。
Beautiful Soup
では、以下の4つのオブジェクトを扱います。
スープの作成で生成したオブジェクト。解析したドキュメントがツリー状に収められている。
# スープの生成
soup = BeautifulSoup('<html>あいうえお</html>', "lxml")
print(type(soup))
# <class 'bs4.BeautifulSoup'>
Tag
は、ドキュメント内のXMLやHTMLのタグに対応したオブジェクトです。
# スープの生成
soup = BeautifulSoup('<p class="underline">あいうえお</p>', "lxml")
# タグをタグ名で取得
tag = soup.p
# タグのタイプ
print(type(tag)) # <class 'bs4.element.Tag'>
name
属性でタグ名を取得できます。
# タグをタグ名で取得
tag = soup.p
print(tag.name) # p
タグを書き換えることもできます。
tag.name = "b"
print(tag)
# <b class="underline">あいうえお</b>
変数名の後ろの[]
内に属性名を文字列で指定することで属性を取得できる。
print(tag['class'])
# ['underline']
タグに挟まれている文字列は、NavigableString
というクラスで表現されます。タグの内側の文字列はstring
属性で取得できます。
print(tag.string)
# あいうえお
print(type(tag.string))
# <class 'bs4.element.NavigableString'>
replace_with()
を使うことで文字列を置換可能。
tag.string.replace_with("abc")
print(tag)
# <p class="underline">abc</p>
抽出した文字列をBeautiful Soup
外で使いたい場合はただの文字列に変換する。
string = tag.string
print(type(string))
# <class 'bs4.element.NavigableString'>
s = str(string)
print(type(s))
# <class 'str'>
特別な書式で表現される CData
、 ProcessingInstruction
、 Declaration
、 Doctype
、 Comment
クラスは、NavigableString
のサブクラスとして定義されています。
soup = BeautifulSoup('<p><!--コメント--></p>', "lxml")
comment = soup.p.string
print(comment, type(comment))
# コメント <class 'bs4.element.Comment'>
以下のようなコードを用いてパースツリーを探索する方法を解説します。
from bs4 import BeautifulSoup
doc = '''<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ブラウザタブに表示される文字</title>
</head>
<body>
<h1>ページタイトル</h1>
<p>あい<b>うえ</b>お</p>
</body>
</html>'''
soup = BeautifulSoup(doc, "lxml")
様々な方法で子要素を取得することができます。
取得したいタグを属性名として指定することで最初に見つけたそのタグの要素を取得できます。
print(soup.body)
# <body>
# <h1>ページタイトル</h1>
# <p>あい<b>うえ</b>お</p>
# </body>
タグ名を繋げて子要素を取得することが可能です。
print(soup.body.p)
# <p>あい<b>うえ</b>お</p>
contents
属性で子要素をリストで取得できます。
print(soup.body.contents)
# ['\n', <h1>ページタイトル</h1>, '\n', <p>あい<b>うえ</b>お</p>, '\n']
タグに囲まれていない要素(コンテンツ)は、NavigableString
というPythonのUnicode
型のような型となるので注意してください。
p = soup.body.contents[3]
print(p.contents)
# ['あい', <b>うえ</b>, 'お']
children
ジェネレーターで子要素をイテレーターとして取得できます。
for child in soup.body.children:
print(child, end='')
#
# <h1>ページタイトル</h1>
# <p>あい<b>うえ</b>お</p>
descendants
属性でタグの全ての子要素を再帰的に取得することができます。
for child in soup.p.descendants:
print(child)
# あい
# <b>うえ</b>
# うえ
# お
子要素を1つしか持っていない、かつ その子要素がNavigableString
オブジェクトの場合、string
属性で要素の内容を取得可能です。
print(soup.h1.string)
# ページタイトル
子要素が複数存在する場合はNone
が返される。
print(soup.p.string)
# None
タグの全ての要素の内容を取得するにはstrings
ジェネレーターを使います。
for content in soup.strings:
print(content, end='')
#
#
#
# ブラウザタブに表示される文字
#
#
# ページタイトル
# あいうえお
#
余分な空白や改行が邪魔な場合は、stripped_strings
ジェネレーターを使うと良い。
for content in soup.stripped_strings:
print(content)
# ブラウザタブに表示される文字
# ページタイトル
# あい
# うえ
# お
様々な方法で親要素を取得することができます。
parent
属性で親要素を取得することができます。
print(soup.title.parent)
# <head>
# <meta charset="utf-8"/>
# <title>ブラウザタブに表示される文字</title>
# </head>
<html>
タグの様なトップレベルのタグの親はBeautifulSoup
オブジェクトとなります。
print(type(soup.html))
# <class 'bs4.element.Tag'>
print(type(soup.html.parent))
# <class 'bs4.BeautifulSoup'>
BeautifulSoup
オブジェクトのparent
はNone
となります。
print(soup.parent)
# None
全ての親要素を取得するにはparents
ジェネレーターを使います。以下のコードではname
属性を用いて取得した親要素のタグ名のみを出力しています。
for parent in soup.title.parents:
if parent is None:
print(parent)
else:
print(parent.name)
# head
# html
# [document]
様々な方法で兄弟要素を取得することができます。
next_sibling
属性で同じレベルの次の要素を取得できる。ただし、改行も1つの要素として取得されるので注意してください。
print(repr(soup.h1.next_sibling))
# '\n'
print(soup.h1.next_sibling.next_sibling)
# <p>あい<b>うえ</b>お</p>
previous_sibling
属性で同じレベルの前の要素を取得できる。こちらも改行が1つの要素として取得される。
print(repr(soup.p.previous_sibling))
# '\n'
print(soup.p.previous_sibling.previous_sibling)
# <h1>ページタイトル</h1>
Beautiful Soup
に定義されているメソッドを使って要素を検索することができます。結構な数のメソッドが定義されていますが引数はほとんど同じです。
ここからは以下のようなコードを使っていきます。
from bs4 import BeautifulSoup
doc = '''<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ブラウザタブに表示される文字</title>
</head>
<body>
<h1 id="page_title">ページタイトル</h1>
<p class="red">あいうえお</p>
<p class="blue">かきくけこ</p>
<h2 id="toc1">見出し1</h2>
<p class="red">さし<span class="bold">すせ</span>そ</p>
<h2 id="toc2">見出し2</h2>
<p>たちつてと</p>
<h2 id="toc3">見出し3</h2>
<p>なにぬねの</p>
</body>
</html>'''
# スープの生成
soup = BeautifulSoup(doc, "lxml")
Beautiful Soup
には以下のようなメソッドが定義されています。
find_all()
メソッドは、子要素の中から引数で指定した条件に合致する全ての要素を検索し、リストとして返します。
find_all(name, attrs, recursive, text, limit, **kwargs)
find()
メソッドは、子要素の中から引数で指定した条件に合致する最初の要素を返します。
find(name, attrs, recursive, text, **kwargs)
find_parents()
メソッドは、親要素の中から引数で指定した条件に合致する全ての要素を検索し、リストとして返します。
find_parents(name, attrs, text, limit, **kwargs)
find_parent()
メソッドは、親要素の中から引数で指定した条件に合致する最初の要素を返します。
find_parent(name, attrs, text, **kwargs)
find_next_siblings()
メソッドは、後方にある兄弟要素の中から引数で指定した条件に合致する全ての要素を検索し、リストとして返します。
find_next_siblings(name, attrs, text, limit, **kwargs)
find_next_sibling()
メソッドは、後方にある兄弟要素の中から引数で指定した条件に合致する最初の要素を返します。
find_next_sibling(name, attrs, text, **kwargs)
find_previous_siblings()
メソッドは、前方にある兄弟要素の中から引数で指定した条件に合致する全ての要素を検索し、リストとして返します。
find_previous_siblings(name, attrs, text, limit, **kwargs)
find_previous_sibling()
メソッドは、前方にある兄弟要素の中から引数で指定した条件に合致する最初の要素を返します。
find_previous_sibling(name, attrs, text, **kwargs)
find_all_next()
メソッドは、後方にある要素の中から引数で指定した条件に合致する全ての要素を検索し、リストとして返します。
find_all_next(name, attrs, text, limit, **kwargs)
find_next()
メソッドは、後方にある要素の中から引数で指定した条件に合致する最初の要素を返します。
find_next(name, attrs, text, **kwargs)
find_all_previous()
メソッドは、前方にある要素の中から引数で指定した条件に合致する全ての要素を検索し、リストとして返します。
find_all_previous(name, attrs, text, limit, **kwargs)
find_previous()
メソッドは、前方にある要素の中から引数で指定した条件に合致する最初の要素を返します。
find_previous(name, attrs, text, **kwargs)
渡せるフィルターは以下の通りです。
検索メソッドに文字列を渡すと一致するタグを返します。
# 文字列
print(soup.find_all('h2'))
# [<h2 id="toc1">見出し1</h2>, <h2 id="toc2">見出し2</h2>, <h2 id="toc3">見出し3</h2>]
正規表現で条件を指定することもできます。以下では、「h」が入っているタグとマッチします。
# 正規表現
import re
for tag in soup.find_all(re.compile('h')):
print(tag.name)
# html
# head
# h1
# h2
# h2
# h2
複数の条件を指定したい場合はリストで指定できます。
# リスト
print(soup.find_all(['h1', 'h2']))
# [<h1 id="page_title">ページタイトル</h1>, <h2 id="toc1">見出し1</h2>, <h2 id="toc2">見出し2</h2>, <h2 id="toc3">見出し3</h2>]
True
を指定すると全ての要素にマッチする。
for tag in soup.find_all(True):
print(tag.name)
# html
# head
# meta
# title
# body
# ...
定義した関数を指定することができます。指定する関数は引数に要素を取り、マッチさせたい時にTrue
、そうでない時にFalse
を返す必要があります。
def method(tag):
# class属性を持つp要素
return tag.has_attr('class') and tag.name == "p"
print(soup.find_all(method))
# [<p class="red">あいうえお</p>, <p class="blue">かきくけこ</p>, <p class="red">さし<span class="bold">すせ</span>そ</p>]
検索メソッドに定義されている引数について見ていきましょう!
name
引数に文字列を指定することでその名前のタグを検索できます。
# h2要素の取得
print(soup.find_all('h2'))
# [<h2 id="toc1">見出し1</h2>, <h2 id="toc2">見出し2</h2>, <h2 id="toc3">見出し3</h2>]
キーワード引数を指定することで属性を検索することができる。
# id = "toc2" の要素
print(soup.find(id="toc2"))
# <h2 id="toc2">見出し2</h2>
キーワード引数の値としてTrue
を指定することでその属性に値が入っている全てのタグを検索します。
# id = "何らかの値" の要素
print(soup.find_all(id=True))
# [<h1 id="page_title">ページタイトル</h1>, <h2 id="toc1">見出し1</h2>, <h2 id="toc2">見出し2</h2>, <h2 id="toc3">見出し3</h2>]
class
で検索したい場合はclass_
を指定します。
# class = "red" の要素
print(soup.find_all(class_="red"))
# [<p class="red">あいうえお</p>, <p class="red">さし<span class="bold">すせ</span>そ</p>]
また、attrs
引数に辞書として指定することも可能です。
# class = "red" の要素
print(soup.find_all(attrs={"class": "red"}))
# [<p class="red">あいうえお</p>, <p class="red">さし<span class="bold">すせ</span>そ</p>]
string
引数に文字列を指定することでタグに挟まれている文字列を検索することができます。
# class = "red" の要素
print(soup.find_all(string="かきくけこ"))
# ['かきくけこ']
タグとして受け取りたい場合は、タグ名を指定すると良い。
tag = soup.find_all("p", string="かきくけこ")
print(type(tag[0]))
# <class 'bs4.element.Tag'>
find_all()
などの検索結果を全てを取得するメソッドにはlimit
という引数が定義されている。limit
引数に整数を指定することで取得する要素の最大数を設定できる。
# 検索結果を2個だけ取得する
print(soup.find_all("p", limit=2))
# [<p class="red">あいうえお</p>, <p class="blue">かきくけこ</p>]
find_all()
とfind()
メソッドは、直下の子要素だけでなく、さらに深い子要素(子要素の子要素など)まで検索するようになっています。
直下の子要素のみ検索したい場合はrecursive
引数にFalse
を指定します。
# 直下より深い子要素も検索
print(soup.find_all("h1"))
# [<h1 id="page_title">ページタイトル</h1>]
# 直下の子要素のみ検索
print(soup.find_all("h1", recursive=False))
# []
find_all()
はよく使用されるメソッドなのでショートカットが用意されています。Beautiful Soup
、またはTag
を関数のように呼び出すことでfind_all()
メソッドとして実行できます。
soup.find_all("p")
soup("p")
Tag
オブジェクトやBeautifulSoup
オブジェクトに定義されているselect()
メソッドを使うことでCSSセレクタを使って要素を取得すことができます。
以下のコードでは、h1の次のp要素のみ取得します。
print(soup.select("h1 + p"))
# [<p class="red">あいうえお</p>]
この記事では、BeautifulSoup
の使い方を解説しました。
軽く解説しようと思ったら1万文字ぐらいのボリュームになってしまいました。機能は多いですが難しいことはないので是非是非使ってみてください。
それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ