BeautifulSoup4の引数と要素の絞り込みについて

2019年12月14日

みなさまおはこんばんにちは、せなです

今回はBeautifulSoup4でfind_all()メソッドなどの引数の使い方や要素を絞り込む方法について詳しく解説したいと思います

find_all()メソッドなどについて知りたいという方は以下の記事も参考にしてみてください

リンク:BeautifulSoup4で検索を行うときに使うメソッドについて

HTML構成

以下のソースコードを前提に説明しています

from bs4 import BeautifulSoup
import re

html = """<html><head><title>せなブログ</title></head>
<p id="site"><a href="https://senablog.com/">せなブログ</a></p>
<p class="desc">疑問に思ったことをサクッと解説!一日一記事22時更新です</p>
<p class="item">
<a href="https://senablog.com/category/programming/" class="category" id="tag1">プログラミング</a>
<a href="https://senablog.com/category/programming/python/" class="category" id="tag2">Python</a>
<a href="https://senablog.com/category/zakki/" class="category" id="tag3">雑記</a>
</p>
<p class="item sub beta">
<b class="category" id="tag4">プログラム</b>
<b class="category library" id="tag5">Beautiful Soup</b>
</p>"""

soup = BeautifulSoup(html, 'html.parser')

引数の種類

ここではfind_all()メソッドを使用したときに使用できる引数で説明したいと思います
引数には以下の種類があります

  • name
  • attrs
  • recursive
  • text
  • limit

ほかのメソッドもrecursiveとlimit以外は同じ引数が用意されています

※recursiveはfind_allのみ、limitは複数を対象にしたメソッドのみ

name引数

name引数には文字列、正規表現、リスト、関数、ブーリアンを指定することができます
使い方の一例として以下をご覧ください

# 文字列
print(soup.find_all(name='title'))  # titleタグの要素を取得(name=を書かなくても同じ要素を取得できる)

# 正規表現
print(soup.find_all(re.compile('a')))  # aがつくタグの要素を取得できる

# リスト
print(soup.find_all(['title', 'a']))  # titleタグとaタグのどちらかと一致した要素を取得できる

# ブーリアン
print(soup.find_all(True))  # すべての要素を取得する

nameで指定できるのはタグのみです
classを指定したい場合はattrs引数かclass_についてをご覧ください

attrs引数

attrs引数は現在ではあまり使用する機会はありません
(使用する方がわずかに読み込むコード量が短くなります)

Beautiful Soup 3より前のバージョンでは「class」タグを受け取る手段がなかったので、使用されていました(classはPythonの予約語
現在は「class_」とすることで取得できるようになったので使いどころは限られています

使い方の一例を以下に記述しておきます

# attrs引数
print(soup.find_all(attrs='item'))  # 内部的に{'class': 'item'}に変換

# class_を使用する
print(soup.find_all(class_='item'))  # 内部的に{'class': 'item'}に変換

# 正しいclassの取得法
print(soup.find_all(attrs={'class': 'item'}))  # 最も早く処理を行える

recursive引数

find_all()メソッドでのみ使用できる引数で、デフォルトではTrueとなっています
find_all()メソッドはタグに対して子孫要素に当たる所までを検索します

recursive引数にFlaseを設定することで、タグの直下の子要素までを検索対象に変更できます

print(soup.find_all('title', recursive=False))  # 直下ではないので表示されない
print(soup.head.find_all('title', recursive=False))  # headの直下にtitleがあるので表示される

text引数

text引数はタグに書かれている文字列を対象に検索をかけることができます
文字列、正規表現、リスト、関数、ブーリアンを指定できます

# 文字列
print(soup.find_all(text='Python'))  # 「Python」を取得

# 正規表現
print(soup.find_all(text=re.compile('Py*')))  # 先頭が「Py」の文字を取得

# リスト
print(soup.find_all(text=['プログラミング', 'Python', '雑記']))  # リスト内の文字を取得

# ブーリアン
print(soup.find_all(text=True))  # 全ての文字を取得

注意点として、HTMLの一行ごとの終わりにあるエスケープシーケンス(\n)は文字列として認識されます
そのため、「text=True」では「\n」も一緒に取得されます

limit引数

limit引数は複数の結果を取得してくるメソッドにしか使えません
設定できるのは数値のみで、設定した数値の数まで結果を取得してきます

print(soup.find_all('a', limit=3))  # aタグの要素を3つ取得

class_について

attrs引数で少し触れていますが、「class」はPythonの予約語であるためそのままでは指定できません

それを解消するためにBeautifulSoup4からは「class_」を使用することで「class」へアクセスすることが可能となっています

以下に使い方の一例を記述します

# 基本的なclass_
print(soup.find_all(class_='item'))  # class名がitemの要素

# nameとの併用
print(soup.find_all('a', class_='category'))  # aタグ かつ クラス名がcategory

# 複数のクラス名を指定可能
print(soup.find_all(class_='item sub'))  # class名が「item sub」の要素 ※「sub item」とかは不可

# 複数のクラス名から結果を取得したい場合
print(soup.select('p.item.sub'))  # pタグでクラス名にitemとsubが含まれている

ちょっと補足というか説明を少し

class_='item sub'」の所ですが、これで一致するのは「class=’item sub’」だけで「class=’sub item’」とか「class=’item sub ***’」は一致しないので要素の取得はできません

それを解決する方法がその下の「.select('p.item.sub')」です
この方法だと順序は関係なくその値がクラス名に含まれているかで取得できます

idなどの指定について

classについては「class_」を使用するなどと書きました
では「id」とか「href」(指定することがあるのかな?)はどうするのかというと

そのまま「id="」とかで指定します
その時に文字列、正規表現、リスト、関数、ブーリアンを使用することもできます

# 文字列
print(soup.find_all(id='tag1'))

# 正規表現
print(soup.find_all(href=re.compile('https://*')))

# リスト
print(soup.find_all(id=['tag1', 'tag2']))

# ブーリアン
print(soup.find_all(href=True))

このような指定は全て「**keargs」に拾われ、処理されています

どう考えてもこんなのないよってのも拾ってくれます(ないものは取得できませんが…)