BeautifulSoup4で親子、兄弟、前後要素の検索方法

2019年12月12日

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

今回はBeautifulSoup4で親要素や子要素への移動と検索の方法について説明したいと思います

始めに

BeautifulSoup4の基本について知りたい方は以下の記事も参考にしてみてください

リンク:BeautifulSoup4の基本的な使い方
リンク:BeautifulSoup4のタグオブジェクトの基本的な使い方

HTML構成

以下のコードを使用することを前提に説明しています

from bs4 import BeautifulSoup

html = """<html><head><title>せなブログ</title></head>
<p id="site"><a href="https://senablog.com/">せなブログ</a></p>
<p class="desc">疑問に思ったことをサクッと解説!一日一記事22時更新です</p>
<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 class="item"></p>"""

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

タグ名での検索

Beautiful Soupでの最も簡単な検索方法はタグ名を使うことです

例えばtitleタグを取得したい場合は、soup.titleで取得できます

print(soup.title)

----結果----
<title>せなブログ</title>

また、タグの子要素を取り出したいなら、soup.a.stringとすれば以下のように取得することもできます

s = soup.a.string
print(s)

----結果----
せなブログ

ただし、以上の方法では一番最初のタグしか取得できません
複数の同じタグを取得したい場合は、find_all()などのメソッドを使用する必要があります

[print(tag) for tag in soup.find_all('a')]

----結果----
<a href="https://senablog.com/">せなブログ</a>
<a class="category" href="https://senablog.com/category/programming/" id="tag1">プログラミング</a>
<a class="category" href="https://senablog.com/category/programming/python/" id="tag2">Python</a>
<a class="category" href="https://senablog.com/category/zakki/" id="tag3">雑記</a>

親要素の検索

parent

親要素へアクセスするには.parentを使用します

title_tag = soup.title
print(title_tag)
print(title_tag.parent)

----結果----
<title>せなブログ</title>
<head><title>せなブログ</title></head>

titleタグのテキストはtitleタグの子要素に当たり、テキストの親要素になります

title_tag = soup.title
print(title_tag.string)
title = title_tag.string
print(title.parent)

----結果----
せなブログ
<title>せなブログ</title>

parents

そのタグの全ての祖先を取得するには、.parentsを使用します

dep = soup.a
print(dep)
# aタグから上の親要素を取得
[print(parent.name) for parent in dep.parents]

----結果----
<a href="https://senablog.com/">せなブログ</a>
p
html
[document]

子要素の検索

contents

子要素は.contentsを使用することでリストとして取得できます

head = soup.head
print(head)
print(head.contents)
title = head.contents[0]
print(title)
print(title.contents[0])

----結果----
<head><title>せなブログ</title></head>
[<title>せなブログ</title>]
<title>せなブログ</title>
せなブログ

また、.childrenを使用することでタグの子要素を取得することができます

[print(text) for text in title.children]

----結果----
せなブログ

descendants

.descendants子孫要素全てをジェネレーターオブジェクトとして取得します

head = soup.head
print(type(head.descendants))

----結果----
<class 'generator'>

.descendantsの子孫要素を全て取得するには以下のようforなどを使用します

head = soup.head
[print(tag) for tag in head.descendants]

----結果----
<title>せなブログ</title>
せなブログ

.children.descendantsとよく似ているように思うかもしれません
ですが、以下を見れば分かる通り取得する要素数は明確に違います

print(len(list(soup.children)))
print(len(list(soup.descendants)))

----結果----
1
22

.childrenは子要素を取得します(今回の場合は「html」のみ)
.descendantsは子孫要素まで取得します(「html」~「最後のpタグまで全て」)

string

.stringは子要素がNavigableStringオブジェクトであれば使用できます

title.contents[0]」でも同様の結果を期待することができます

title = soup.title
print(title.string)

----結果----
せなブログ

また、.stringタグが複数の子要素を持っている場合はNoneが結果として返却されます

strings

.stringsはタグの子孫要素からNavigableStringオブジェクトを全て取得することができます

[print(tag, end='') for tag in soup.strings]

----結果----
せなブログ

せなブログ

疑問に思ったことをサクッと解説!一日一記事22時更新です

プログラミング

Python

雑記

.stringsの場合は余計な空白などが表示されてしまいます
それらを取り除きたい場合には.stripped_stringsを使用します

[print(tag) for tag in soup.stripped_strings]

----結果----
せなブログ
せなブログ
疑問に思ったことをサクッと解説!一日一記事22時更新です
プログラミング
Python
雑記

兄弟要素の検索

兄弟要素はインデントを揃えたときに同じ階層に属しているタグのことを言います

next_siblingとprevious_sibling

.next_sibling.previous_siblingは同じ階層に属するタグを辿るときに使用します

まずは、以下をみてください

print(soup.p.next_sibling)
print(soup.p.previous_sibling)

----結果----
\n
\n

これは最初のpタグを指定していますので、普通は次(前)のタグが表示されるはずです
ですが、パースしたHTMLの前後には改行が含まれているため正しい結果を得られません

その場合にどうするかというとsiblingを再度使用します

print(soup.p.next_sibling.next_sibling)
print(soup.p.previous_sibling.previous_sibling)

----結果----
<p class="desc">疑問に思ったことをサクッと解説!一日一記事22時更新です</p>
<head><title>せなブログ</title></head>

next_siblingsとprevious_siblings

.next_siblings.previous_siblingsはイテレーターとして使うことができ、複数の兄弟要素を全て取得したいときに使用します

[print(tag, end='') for tag in soup.p.next_siblings]

----結果----
<p class="desc">疑問に思ったことをサクッと解説!一日一記事22時更新です</p>
<a class="category" href="https://senablog.com/category/programming/" id="tag1">プログラミング</a>
<a class="category" href="https://senablog.com/category/programming/python/" id="tag2">Python</a>
<a class="category" href="https://senablog.com/category/zakki/" id="tag3">雑記</a>
<p class="item"></p>
[print(tag, end='') for tag in soup.find(id='tag3').previous_siblings]

----結果----
<a class="category" href="https://senablog.com/category/programming/python/" id="tag2">Python</a>
<a class="category" href="https://senablog.com/category/programming/" id="tag1">プログラミング</a>
<p class="desc">疑問に思ったことをサクッと解説!一日一記事22時更新です</p>
<p id="site"><a href="https://senablog.com/">せなブログ</a></p>
<head><title>せなブログ</title></head>

前後要素の検索

next_elementとprevious_element

.next_element.previous_elementはタグの前後の要素を取得するときに使用します

「siblingと同じでは?」と思われるかもしれませんがelementとsiblingは全く違います
以下を見てください

print(soup.p.previous_sibling.previous_sibling)
print(soup.p.previous_element.previous_element)
print(soup.p.previous_element.previous_element.previous_element)
print(soup.p.previous_element.previous_element.previous_element.previous_element)

----結果----
<head><title>せなブログ</title></head>
せなブログ
<title>せなブログ</title>
<head><title>せなブログ</title></head>

上はsiblingを下3行はelementを使用しています
結果を見てみると内容が違います

なぜなら.previous_elmentはタグの一つ後のタグを取得するからです

これは.next_element.next_sibilingでも同じことが言えます

next_elementsとprevious_elements

.next_elementsと.previous_elementsはイテレーターとして使用することができ、前後の要素を全て取得することができます

[print(tag, end='') for tag in soup.p.next_elements]

----結果----
<a href="https://senablog.com/">せなブログ</a>せなブログ
<p class="desc">疑問に思ったことをサクッと解説!一日一記事22時更新です</p>疑問に思ったことをサクッと解説!一日一記事22時更新です
<a class="category" href="https://senablog.com/category/programming/" id="tag1">プログラミング</a>プログラミング
<a class="category" href="https://senablog.com/category/programming/python/" id="tag2">Python</a>Python
<a class="category" href="https://senablog.com/category/zakki/" id="tag3">雑記</a>雑記
<p class="item"></p>
[print(tag, end='') for tag in soup.find(id='tag3').previous_elements]

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