スクレイピング – Beautiful Soup の要素の検索方法まとめ

目次

概要

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 をサンプルとして使用します。

In [1]:
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 の階層構造がどうなっているかをまず理解するようにしましょう。

In [2]:
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 やスライスが行えます。

find_all()

In [3]:
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 オブジェクトになります。

In [4]:
# 名前が "p" の要素を検索する。
print(soup.find_all("p"))
[<p>お気に入りに<a href="sample.com">このサイト</a>を登録してください。</p>]

一方、Tag.find()Tag.find_all() と異なり、条件に該当する最初に見つかった要素を返します。

find()

In [5]:
# 名前が "li" の要素を検索し、最初に見つかった要素を返す。
print(soup.find("li"))
<li>トピック1</li>

4つの指定方法

find 系のメソッドは条件の指定方法として、次の4つがあります。

  • 名前で検索: name 引数で指定
  • 属性で検索: attrs 引数または 属性=値 で指定
  • 値で検索: string で指定

各引数では、次の指定方法があります。

  • True
  • 値のリスト
  • 正規表現 (re.Pattern オブジェクト)
  • bool 関数

指定した名前の要素を検索する

検索する要素名を name 引数で指定します。要素名はリストで複数指定できます。

In [6]:
# 名前が "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>]

要素名の指定には、正規表現が利用できます。

In [7]:
import re

# 名前が h から始まる要素を検索する。
for tag in soup.find_all(re.compile("^h")):
    print(tag.name, end=" ")
html head h1 h2 h2 

True を指定することで、すべての要素を取得できます。

In [8]:
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 を返す要素を検索します。

In [9]:
# 名前の長さが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_ を使用します。

In [10]:
# "class" 属性の値が "list" の要素を検索する。
for tag in soup.find_all(True, class_="list"):
    print(tag.name)
ul
ul

複雑の属性を指定できます。

In [11]:
# "class" 属性の値が "list" で "id" 属性の値が "menu" の要素を検索する。
for tag in soup.find_all(True, class_="list", id="menu"):
    print(tag.name)
ul

正規表現を使用できます。

In [12]:
# "class" 属性の値が "l" で始まる要素を検索する。
for tag in soup.find_all(True, class_=re.compile(r"^l")):
    print(tag.name)
ul
ul

属性値を True にすることで、その属性を持つ要素を検索します。

In [13]:
# "class" 属性を持つ要素を検索する。
for tag in soup.find_all(class_=True):
    print(tag.name)
div
ul
div
ul

属性値を受け取り、bool 値を返す関数を指定すると、関数が True を返す要素を検索します。

In [14]:
# "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 引数でも属性の条件を指定できます。

In [15]:
# "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 に持つ要素を返します。値はリストで複数指定できます。

In [16]:
# 値が "トピック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>]

正規表現も使用できます。

In [17]:
# 値が "トピック" で始まる要素を検索する。
print(soup.find_all(string=re.compile(r"^トピック")))
['トピック一覧', 'トピック1', 'トピック2']

値を受け取り、bool 値を返す関数を指定すると、関数が True を返す要素を検索します。

In [18]:
# 値の長さが5である要素を取得する。
print(soup.find_all("li", string=lambda x: x and len(x) == 5))
[<li>トピック1</li>, <li>トピック2</li>]

返り値の要素数の上限を指定する

limit 引数で上限を設定できます。

In [19]:
# 条件に該当する要素を検索し、最初の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 を指定した場合は、その子要素のみを検索対象とします。

In [20]:
print(soup.find_all("h1"))

# 検索対象を子要素に限定する。
print(soup.find_all("h1", recursive=False))
[<h1>ニュース</h1>]
[]

Tag オブジェクトの呼び出しメソッド

bs4.BeautifulSoup オブジェクトまたは bs4.element.Tag オブジェクトの呼び出しメソッドは、find_all() と同じ意味になります。

In [21]:
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 セレクタ」で検索してでてくる情報を参照してください。

セレクタの種類-CSSの基本

コメント

コメントする

目次