beautifulsoup4==4.12.3 で動作確認しています。
まとめ
Beautiful Soup 4 で特定の HTML 要素を取得するには、要素のタグ名・属性・内部文字列に条件指定する .find() .find_all() と、要素を CSS セレクタで指定する .select_one() .select() があります。
-
find系はタグ名・属性・内部文字列への条件を関数で渡すこともできるので柔軟です。 -
select系で内部文字列への判定をするには Beautiful Soup 4 用 CSS セレクタライブラリ Soup Sieve の独自疑似クラス:-soup-contains(-own)を使う必要があります。ただし、判定できるのは「文字列を含むか」までで、先頭一致や正規表現マッチはとれません。select系で要素を取得するのは CSS セレクタの利用が便利な場合になると思います。
スニペット
以下の HTML は 7 つの div があります (6, 7 番目の div は 5 番目の div の子要素です)。
from bs4 import BeautifulSoup
text = (
'<div class="red fruit">APPLE</div>'
'<div class="red fruit">FIG</div>'
'<div class="yellow fruit">BANANA</div>'
'<div class="yellow fruit">LEMON</div>'
'<div class="red vehicle">'
'FIRE ENGINE'
'<div>HOSE</div>'
'<div>LADDER</div>'
'</div>'
)
soup = BeautifulSoup(text, 'html.parser')
# すべての div を取得する
elms = soup.find_all('div')
assert len(elms) == 7
elms = soup.select('div')
assert len(elms) == 7
# 黄色い果物クラスである div を一つ取得する (黄色いと果物は順不同)
elm = soup.find('div', class_=lambda v: v and ('yellow' in v) and ('fruit' in v))
assert elm.string == 'BANANA'
elm = soup.select_one('div.yellow.fruit')
assert elm.string == 'BANANA'
# 赤い車クラスである div の2番目の子 div を取得する (赤いと車は順序指定)
elm = soup.find('div', class_=lambda v: v == 'red vehicle').find_all('div', recursive=False)[1]
assert elm.string == 'LADDER'
elm = soup.select_one('div[class="red vehicle"] > div:nth-of-type(2)')
assert elm.string == 'LADDER'
# 中身が文字列のみでかつその文字列が L で始まる div を取得する
# ※ 中身が文字列のみのとき .string でアクセス可 (文字列のみでないときこれは None)
elms = soup.find_all('div', string=lambda v: v and v.startswith('L'))
assert len(elms) == 2
assert elms[0].string == 'LEMON'
assert elms[1].string == 'LADDER'
elms = soup.select('div:-soup-contains-own("L")') # 「直下に L を含む」は取れるが先頭かは判定不可
assert len(elms) == 3 # 先頭かまでは判定不可なので APPLE も取得される
elms = [elm for elm in elms if elm.string and elm.string.startswith('L')] # 改めて先頭が L かで絞る
assert len(elms) == 2
assert elms[0].string == 'LEMON'
assert elms[1].string == 'LADDER'
# 中身が文字列のみでなくても F で始まる div を取得する
elms = soup.find_all(lambda tag: (tag.name == 'div') and tag.get_text().startswith('F'))
assert len(elms) == 2
assert elms[0].string == 'FIG'
assert elms[1].contents[0].string == 'FIRE ENGINE'
elms = soup.select('div:-soup-contains-own("F")') # 「直下に F を含む」は取れるが先頭かは判定不可
elms = [elm for elm in elms if elm.get_text().startswith('F')] # 改めて先頭が F かで絞る
assert len(elms) == 2
assert elms[0].string == 'FIG'
assert elms[1].contents[0].string == 'FIRE ENGINE'
参考文献
-
Beautiful Soup 4 のドキュメント
- .find() の記述
-
.select() の記述 :
.css.select()のように.css属性経由で.select()を呼んでいますが、便利のためにTagから直接呼べるようにしてあると最後にあります。また、「CSS セレクタによる解析をするだけなら lxml が高速です」とあります。 - .string の記述
-
Soup Sieve の疑似クラスのドキュメント
- :-soup-contains() の記述 : 子要素内の文字列までみるときはこちらです。
- :-soup-contains-own() の記述 : 直下の文字列だけみるときはこちらです。