概要
Beautiful Soup で条件を指定して要素を検索する方法を解説します。
関連記事
[blogcard url=”https://pystyle.info/scraping-beautiful-soup-how-to-edit-dom-tree”]
[blogcard url=”https://pystyle.info/scraping-beautiful-soup-how-to-refer-elements”]
機能一覧
引数で検索条件を指定する find
系のメソッドと CSS セレクタで検索条件を指定する select
系のメソッドがあります。
find(), find_parent(), find_next_sibling(), find_previous_sibling(), select_one()
は条件に該当する最初に見つかった要素を返します。find_all(), find_parents(), find_next_siblings(), find_previous_siblings(), select()
は条件に該当するすべての要素を一覧で返すようになっています。find
系のメソッドは検索範囲によって使い分けます。
検索範囲 | 返り値 | |
---|---|---|
find() |
子孫要素 | 条件に該当する最初に見つかった要素 |
find_all() |
子孫要素 | 条件に該当するすべての要素 |
find_parent() |
先祖要素 | 条件に該当する最初に見つかった要素 |
find_parents() |
先祖要素 | 条件に該当するすべての要素 |
find_next_sibling() |
この要素より前のすべての兄弟要素 | 条件に該当する最初に見つかった要素 |
find_next_siblings() |
この要素より前のすべての兄弟要素 | 条件に該当するすべての要素 |
find_previous_sibling() |
この要素より後のすべての兄弟要素 | 条件に該当する最初に見つかった要素 |
find_previous_siblings() |
この要素より後のすべての兄弟要素 | 条件に該当するすべての要素 |
select_one() |
子孫要素 | 条件に該当する最初に見つかった要素 |
select() |
子孫要素 | 条件に該当するすべての要素 |
サンプルの HTML
以下の HTML をサンプルとして使用します。
html = """
<html>
<head>
<title>ニュース</title>
</head>
<body>
<h1>ニュース</h1>
<div class="topic">
<h2>トピック一覧</h2>
<ul class="list" id="menu">
<li>トピック1</li>
<li>トピック2</li>
</ul>
</div>
<div class="images">
<h2>注目の画像</h2>
<ul class="list">
<li><img src="sample1.jpg" alt="画像1"></li>
<li><img src="sample2.jpg" alt="画像2"></li>
</ul>
</div>
<p>お気に入りに<a href="sample.com">このサイト</a>を登録してください。</p>
</body>
</html>
"""
Beautiful Soup では、HTML テキストを解析し、以下のような DOM ツリーで表現します。要素の検索、追加はこの階層構造を元に行いますので、解析する HTML の階層構造がどうなっているかをまず理解するようにしましょう。
import re
import bs4
from anytree import Node, RenderTree
from bs4 import BeautifulSoup
html = re.sub(r"^\s+", "", html, flags=re.MULTILINE).replace("\n", "")
soup = BeautifulSoup(html)
root = Node("root", type_=soup.__class__.__name__)
def traverse(parent, soupNode, depth=0):
if isinstance(soupNode, bs4.NavigableString):
name = repr(soupNode.string)
else:
name = soupNode.name
anyNode = Node(name, parent=parent, type_=soupNode.__class__.__name__)
if hasattr(soupNode, "children"):
for child in soupNode.children:
traverse(anyNode, child, depth + 1)
traverse(root, soup.html)
for pre, fill, node in RenderTree(root):
print("{}{} ({})".format(pre, node.name, node.type_))
root (BeautifulSoup) └── html (Tag) ├── head (Tag) │ └── title (Tag) │ └── 'ニュース' (NavigableString) └── body (Tag) ├── h1 (Tag) │ └── 'ニュース' (NavigableString) ├── div (Tag) │ ├── h2 (Tag) │ │ └── 'トピック一覧' (NavigableString) │ └── ul (Tag) │ ├── li (Tag) │ │ └── 'トピック1' (NavigableString) │ └── li (Tag) │ └── 'トピック2' (NavigableString) ├── div (Tag) │ ├── h2 (Tag) │ │ └── '注目の画像' (NavigableString) │ └── ul (Tag) │ ├── li (Tag) │ │ └── img (Tag) │ └── li (Tag) │ └── img (Tag) └── p (Tag) ├── 'お気に入りに' (NavigableString) ├── a (Tag) │ └── 'このサイト' (NavigableString) └── 'を登録してください。' (NavigableString)
find 系の関数
find
系の関数は、検索範囲が異なるだけで、引数の指定方法は共通です。
find() と find_all()
Tag.find_all()
は、子孫要素を検索し、条件に該当するすべての要素を bs4.element.ResultSet
オブジェクトで返します。これはリストと同様で iterate やスライスが行えます。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html)
# 名前が "li" の要素を検索する。
ret = soup.find_all("li")
print(ret)
# スライスなどインデックス操作もリスト同様に行える。
print(ret[0])
print(ret[1:])
[<li>トピック1</li>, <li>トピック2</li>, <li><img alt="画像1" src="sample1.jpg"/></li>, <li><img alt="画像2" src="sample2.jpg"/></li>] <li>トピック1</li> [<li>トピック2</li>, <li><img alt="画像1" src="sample1.jpg"/></li>, <li><img alt="画像2" src="sample2.jpg"/></li>]
条件に該当する要素が1つしかない場合でも返り値は bs4.element.ResultSet
オブジェクトになります。
# 名前が "p" の要素を検索する。
print(soup.find_all("p"))
[<p>お気に入りに<a href="sample.com">このサイト</a>を登録してください。</p>]
一方、Tag.find()
は Tag.find_all()
と異なり、条件に該当する最初に見つかった要素を返します。
# 名前が "li" の要素を検索し、最初に見つかった要素を返す。
print(soup.find("li"))
<li>トピック1</li>
4つの指定方法
find 系のメソッドは条件の指定方法として、次の4つがあります。
- 名前で検索:
name
引数で指定 - 属性で検索:
attrs
引数または属性=値
で指定 - 値で検索:
string
で指定
各引数では、次の指定方法があります。
- True
- 値
- 値のリスト
- 正規表現 (
re.Pattern
オブジェクト) - bool 関数
指定した名前の要素を検索する
検索する要素名を name
引数で指定します。要素名はリストで複数指定できます。
# 名前が "p" の要素を検索する。
print(soup.find_all("p"))
# 名前が "p" または "img" の要素を検索する。
print(soup.find_all(["p", "img"]))
[<p>お気に入りに<a href="sample.com">このサイト</a>を登録してください。</p>] [<img alt="画像1" src="sample1.jpg"/>, <img alt="画像2" src="sample2.jpg"/>, <p>お気に入りに<a href="sample.com">このサイト</a>を登録してください。</p>]
要素名の指定には、正規表現が利用できます。
import re
# 名前が h から始まる要素を検索する。
for tag in soup.find_all(re.compile("^h")):
print(tag.name, end=" ")
html head h1 h2 h2
True
を指定することで、すべての要素を取得できます。
for tag in soup.find_all(True):
print(tag.name, end=" ")
html head title body h1 div h2 ul li li div h2 ul li img li img p a
要素を受け取り、bool 値を返す関数を指定すると、関数が True
を返す要素を検索します。
# 名前の長さが4の要素を検索する
for tag in soup.find_all(lambda x: x and len(x.name) == 4):
print(tag.name, end=" ")
html head body
指定した属性を持つ要素を検索する
引数に 属性名=値
を指定することで、その属性値を持つ要素を検索します。
ただし、属性 class
を指定する場合、class
は Python の予約語なので、代わりに class_
を使用します。
# "class" 属性の値が "list" の要素を検索する。
for tag in soup.find_all(True, class_="list"):
print(tag.name)
ul ul
複雑の属性を指定できます。
# "class" 属性の値が "list" で "id" 属性の値が "menu" の要素を検索する。
for tag in soup.find_all(True, class_="list", id="menu"):
print(tag.name)
ul
正規表現を使用できます。
# "class" 属性の値が "l" で始まる要素を検索する。
for tag in soup.find_all(True, class_=re.compile(r"^l")):
print(tag.name)
ul ul
属性値を True
にすることで、その属性を持つ要素を検索します。
# "class" 属性を持つ要素を検索する。
for tag in soup.find_all(class_=True):
print(tag.name)
div ul div ul
属性値を受け取り、bool 値を返す関数を指定すると、関数が True
を返す要素を検索します。
# "class" 属性の値の長さが4の要素を検索する。
print(soup.find_all(class_=lambda x: x and len(x) == 4))
[<ul class="list" id="menu"><li>トピック1</li><li>トピック2</li></ul>, <ul class="list"><li><img alt="画像1" src="sample1.jpg"/></li><li><img alt="画像2" src="sample2.jpg"/></li></ul>]
attrs
引数でも属性の条件を指定できます。
# "class" 属性の値が "list" で "id" 属性の値が "menu" の要素を検索する。
print(soup.find_all(True, attrs={"class": "list", "id": "menu"}))
[<ul class="list" id="menu"><li>トピック1</li><li>トピック2</li></ul>]
指定した値を持つ要素を検索する
string
引数のみ指定した場合は、指定した値をもつ NavigableString
オブジェクトを返します。
name
引数で要素名も指定した場合、指定した値を Tag.string
に持つ要素を返します。値はリストで複数指定できます。
# 値が "トピック1" である要素を検索し、その値を NavigableString オブジェクトで返す。
print(soup.find_all(string="トピック1"))
# 値が "トピック1" で名前が "li" である要素を取得する。
print(soup.find_all("li", string="トピック1"))
# 値が "トピック1" または "トピック2" で名前が "li" である要素を取得する。
print(soup.find_all("li", string=["トピック1", "トピック2"]))
['トピック1'] [<li>トピック1</li>] [<li>トピック1</li>, <li>トピック2</li>]
正規表現も使用できます。
# 値が "トピック" で始まる要素を検索する。
print(soup.find_all(string=re.compile(r"^トピック")))
['トピック一覧', 'トピック1', 'トピック2']
値を受け取り、bool 値を返す関数を指定すると、関数が True
を返す要素を検索します。
# 値の長さが5である要素を取得する。
print(soup.find_all("li", string=lambda x: x and len(x) == 5))
[<li>トピック1</li>, <li>トピック2</li>]
返り値の要素数の上限を指定する
limit
引数で上限を設定できます。
# 条件に該当する要素を検索し、最初の3つの要素を返す。
print(soup.find_all("li", limit=3))
[<li>トピック1</li>, <li>トピック2</li>, <li><img alt="画像1" src="sample1.jpg"/></li>]
再帰的に検索する深さを指定する
デフォルトでは、find_all()
を呼び出した要素の子孫要素がすべて検索となりますが、recursive=False
を指定した場合は、その子要素のみを検索対象とします。
print(soup.find_all("h1"))
# 検索対象を子要素に限定する。
print(soup.find_all("h1", recursive=False))
[<h1>ニュース</h1>] []
Tag オブジェクトの呼び出しメソッド
bs4.BeautifulSoup
オブジェクトまたは bs4.element.Tag
オブジェクトの呼び出しメソッドは、find_all()
と同じ意味になります。
print(soup.find_all("h1"))
print(soup("h1"))
print(soup.html("h1"))
[<h1>ニュース</h1>] [<h1>ニュース</h1>] [<h1>ニュース</h1>]
select
系のメソッド
select()
及び select_one()
は、検索条件を CSS セレクタで指定します。CSS セレクタを使用することで、「div
タグの子要素の h1
タグ」「div
タグの2番目の子要素」といった find
系のメソッドでは複数行のコードが必要となる検索処理を1つの関数呼び出しで実現できます。
CSS セレクタの指定方法は沢山あるので、「CSS セレクタ」で検索してでてくる情報を参照してください。
コメント