ユーニックス総合研究所

  • home
  • archives
  • bs4-find-all

BeautifulSoup4のfind_allでタグを全てぶんまわす

  • 作成日: 2020-11-19
  • 更新日: 2023-12-24
  • カテゴリ: BeautifulSoup4

BeautifulSoup4のfind_allの使い方

プログラミング言語のPython(パイソン)にはHTML/XMLをパースする外部ライブラリがあります。
その名も「BeautifulSoup4(ビューティフル・スープ・フォー)」です。

BeautifulSoup4は指定したタグを検索してすべて取得するfind_all(ファインド・オール)メソッドを持っています。
このメソッドを使うと、指定したタグをまとめて取得し、for文などでタグを回すことが可能です。

この記事ではBeautifulSoup4のfind_all()メソッドの使い方を詳しく解説します。
具体的には↓を見ていきます。

  • find_allの構造
  • タグ名を指定して要素を全て得る
  • 複数のタグ名を指定して要素を全て得る
  • 属性を指定して要素を得る
  • 再帰をやめる
  • テキストを指定して要素を得る
  • 再帰上限数を指定する
  • タグ名とクラス名を指定して要素を得る
  • タグ名とID名を指定して要素を得る
  • クラス名を正規表現で指定して検索し要素を得る

find_allの構造

find_all()の構造について解説します。
公式ドキュメントは↓です。

find_all()は↓のような構造になっています。

find_all(  
    self,  
    name=None,  # タグ名を表す文字列  
    attrs={},  # 属性を表す辞書  
    recursive=True,  # 再帰をするならTrue  
    text=None,  # 要素内のテキストを表す文字列  
    limit=None,  # 再帰上限数  
    **kwargs  # キーワード引数  
)  

↓から引数と返り値の解説です。

name(第1引数)

タグ名を表す文字列またはリスト、タプルを指定します。
または整数やboolも指定できます。
find_all()で特定のタグを取得したい、たとえばdivのタグを取得したい場合は、この引数nameに文字列でdivを指定します。

soup.find_all('div')  

リストまたはタプルを指定した場合は、その要素の文字列がタグ名として参照されます。
複数のタグを取得したい場合はこの引数nameにリストを指定するといいでしょう。

soup.find_all(['p', 'span'])  

Trueを指定した場合、find_all()は全ての要素を取得するようになります。
class名やid名で検索するタグを絞りたい時に使われます。

soup.find_all(True)  

attrs(第2引数)

タグの属性値を表す辞書を指定します。
たとえば値birdを持つ属性classを表現したい場合は

soup.find_all('p', attrs={ 'class': 'bird' })  

↑のような指定になります。
辞書には複数の属性を指定することも可能です。

{  
    'class': 'bird',  
    'id': 'dog',  
}  

複数の属性を指定した場合は検索はAND検索になります。

# classとidでAND検索  
soup.find_all('p', attrs={'class': 'bird', 'id': 'dog'})  

辞書のキーに対する値にはリストまたはタプルも指定することが出来ます。
リスト内に複数の文字列を指定した場合はOR検索になります。

# classをOR検索  
soup.find_all('p', attrs={ 'class': ['a', 'b'] })  

attrsのキーに対する値にはコンパイル済みの正規表現オブジェクトも指定できます。
検索に正規表現を使いたい場合は↓のようにします。

import re  
soup.find_all('p', attrs={ 'class': re.compile(r'Dr.*') })  

recursive(第3引数)

再帰的に検索するならTrue, 非再帰的に検索するならFalseを指定します。
デフォルトではTrueです。つまりfind_all()はデフォルトでは再帰的に検索します。

再帰と言うのは、ツリー構造を再帰的、つまり、親の要素から子の要素を辿るという意味になります。
再帰的検索をFalseにすると、親の要素から子の要素へ辿らなくなります。

text(第4引数)

タグの持つテキストまたはリストやタプル、正規表現オブジェクトを指定します。
タグの内容を表す文字列です。

たとえば↓のようなタグがあったとして、

<p>Happy!</p>  

↑のタグを取得したい場合はfind_all()の引数textに文字列でHappy!と指定します。

soup.find_all('p', text='Happy!')  

複数のテキストを指定してOR検索したい場合はtextにリストやタプルを指定します。

soup.find_all('p', text=['Bob', 'Alice'])  

また、コンパイルされた正規表現オブジェクトを渡し、正規表現で指定することも可能です。

import re  
soup.find_all('p', text=re.compile(r'The.*$'))  

limit(第5引数)

検索における再帰上限数を正数で指定します。
デフォルトではNoneです。

「再帰上限」とは「どれぐらいの深さまで検索するか」という意味になります。
たとえば↓のようなHTMLの場合、ツリー全体の深さは2です。

<div>  
  <p>I'm here!</p>  
</div>  

このツリーを深さ1まで、つまりpタグまで辿らないようにしたい場合は、このlimit引数に1を指定します。

soup.find_all('div', limit=1)  

kwargs(第6引数)

オプションの引数です。
よく使われるのはclass_idです。
これらの引数はattrsのショートカットとして使われます。

soup.find_all('p', class_='bird')  
soup.find_all('p', id='pig')  

find_all()の返り値

find_all()の返り値はbs4.element.ResultSetです。
ResultSetlistを継承したfor文で回せるオブジェクトです。

from bs4 import BeautifulSoup  

html = '''  
<p>1</p>  
<p>2</p>  
<p>3</p>  
'''  

soup = BeautifulSoup(html, 'html.parser')  
resultset = soup.find_all('p')  
print(type(resultset))  

↑のコードの実行結果↓。

<class 'bs4.element.ResultSet'>  

BeautifulSoup4にけるResultSetの実装は非常にシンプルです。
実装は↓のようになっています。

class ResultSet(list):  
    """A ResultSet is just a list that keeps track of the SoupStrainer  
    that created it."""  
    def __init__(self, source, result=()):  
        super(ResultSet, self).__init__(result)  
        self.source = source  

タグ名を指定して要素を全て得る

find_all()の引数nameにタグ名を指定すると、そのタグを検索して取得することが可能です。
↓はliタグを取得している所です。

from bs4 import BeautifulSoup  

html = '''  
<ul>  
  <li>1</li>  
  <li>2</li>  
  <li>3</li>  
</ul>  
'''  

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

for el in soup.find_all('li'):  # <- タグ名に li を指定  
    # ResultSetをfor文で回し、要素のtext属性を参照  
    print(el.text)  
1  
2  
3  

find_all()はデフォルトで再帰的に検索します。
ですのでliが入れ子になっていたらその入れ子になっているタグも取得されます。
例えば↓のようなHTMLです

<ul>  
  <li>1</li>  
  <li>2</li>  
  <li>  
    <ul>  
      <li>3</li>  
      <li>4</li>  
    </ul>  
  </li>  
</ul>  

複数のタグ名を指定して要素を全て得る

find_all()の引数nameにリストを指定し、リスト内の要素にタグ名を書くと、それらのタグ名の要素を取得することが出来ます。
↓はpタグとpreタグを取得している所です。

from bs4 import BeautifulSoup  

html = '''  
<p>1</p>  
<pre>2</pre>  
<span>3</span>  
'''  

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

# タグ名pとpreを持つ要素を検索する  
for el in soup.find_all(['p', 'pre']):  
    # ResultSetをfor文で回し、要素のtext属性を参照  
    print(el.text)  

↑の出力結果↓。

1  
2  

リストはタプルでも大丈夫です。

soup.find_all(('p', 'pre'))  

属性を指定して要素を得る

タグの属性を指定して検索したい場合はfind_all()の引数attrsに辞書を指定します。
↓はclass属性を指定して検索しています。

from bs4 import BeautifulSoup  

html = '''  
<ul>  
  <li class="bird">1</li>  
  <li class="dog">2</li>  
  <li class="bird">3</li>  
</ul>  
'''  

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

# class属性にbirdを持つliタグを検索  
for el in soup.find_all('li', attrs={'class': 'bird'}):  
    # ResultSetをfor文で回し、要素のtext属性を参照  
    print(el.text)  

↑の出力結果↓。

1  
3  

ちなみにattrsAttributes(属性群)の略です。

再帰をやめる

find_all()はデフォルトでは↓のようにコンテンツを再帰的に検索します。

from bs4 import BeautifulSoup  

html = '''  
<span id="1">  
  <span id="2">  
    <span id="3"></span>  
  </span>  
</span>  
'''  

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

for el in soup.find_all('span'):  
    # ResultSetをfor文で回し、要素のid属性を参照  
    print(el.get('id'))  

↑の出力結果↓。

1  
2  
3  

この再帰的検索をやめて、表面のレイヤーだけの検索をしたい場合はfind_all()recursive引数にFalseを指定します。

for el in soup.find_all('span', recursive=False):  
    print(el.get('id'))  

↑の出力結果↓。

1  

テキストを指定して要素を得る

タグの持つテキストを指定して検索したい場合はfind_all()の引数textにテキストを表す文字列を指定します。

from bs4 import BeautifulSoup  

html = '''  
<p id="a">1</p>  
<p>2</p>  
<div>  
  <p id="b">1</p>  
  <p>2</p>  
</div>  
'''  

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

# textに「1」を持つタグpを検索する  
for el in soup.find_all('p', text='1'):  
    # ResultSetをfor文で回し、要素のid属性を参照  
    print(el.get('id'))  

↑の出力結果↓。

a  
b  

再帰上限数を指定する

再帰の上限数を指定すると、特定の階層まで検索して、以降は検索しないという動作を得ることができます。
この再帰上限数の指定はfind_all()の引数limitに正数で指定します。

from bs4 import BeautifulSoup  

html = '''  
<div id="1">  
  <div id="2">  
    <div id="3"></div>  
  </div>  
</div>  
'''  

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

# 再帰は2まで  
for el in soup.find_all('div', limit=2):  
    # ResultSetをfor文で回し、要素のid属性を参照  
    print(el.get('id'))  

↑の出力結果↓。

1  
2  

タグ名とクラス名を指定して要素を得る

クラス名の指定にはfind_all()の引数attrsにクラス名を指定すればいけますが、ショートカットとしてclass_引数に指定することも出来ます。

from bs4 import BeautifulSoup  

html = '''  
<p class="bird">1</p>  
<p class="dog">2</p>  
<p class="bird">3</p>  
'''  

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

# クラス名がbirdの要素を得る  
for el in soup.find_all('p', class_='bird'):  
    # ResultSetをfor文で回し、要素のtext属性を参照  
    print(el.text)  

↑の出力結果↓。

1  
3  

タグ名とID名を指定して要素を得る

ID名もクラス名と同様にショートカットが使えます。
find_all()の引数idにID名を指定すれば、そのID属性を持つタグを検索します。

from bs4 import BeautifulSoup  

html = '''  
<p id="bird">1</p>  
<p id="dog">2</p>  
<p id="bird">3</p>  
'''  

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

# クラス名がbirdの要素を得る  
for el in soup.find_all('p', id='bird'):  
    # ResultSetをfor文で回し、要素のtext属性を参照  
    print(el.text)  

↑の出力結果↓。

1  
3  

クラス名を正規表現で指定して検索し要素を得る

すべてのタグを対象に検索します。
クラス名がblue-.*という正規表現にマッチしたらその要素を取得します。

from bs4 import BeautifulSoup  
import re  

html = '''  
<p class="blue-mountain" id="1"></p>  
<div class="red-sea" id="2">  
    <p class="red-mountain" id="3"></p>  
    <span class="blue-ocean" id="4"></span>  
</div>  
'''  

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

for el in soup.find_all(True, class_=re.compile(r'blue-.*')):  
    # ResultSetをfor文で回し、要素のtext属性を参照  
    print(el.get('id'))  

↑の出力結果↓。

1  
4  

おわりに

BeautifulSoup4を使ったパースは最近のPythonでは非常に一般的になっています。
その中でもfind_all()はとても人気があるメソッドです。
find_all()をマスターすることはBeautifulSoup4のマスターにも繋がるかと思います。
この記事で要点を押さえてマスターを目指しましょう。

🦝 < フォースの力を信じよ

参考