PythonでWebスクレイピングを行う際、特定の要素の直前にある情報を取得したい場面は頻繁に発生します。
BeautifulSoupライブラリには、そのための便利なプロパティとしてprevious_siblingが用意されています。
しかし、実際にコードを書いてみると、期待した要素ではなく「改行」や「空白」が取得されてしまい、戸惑う方も少なくありません。
この記事では、previous_siblingの基本的な使い方から、空白ノードを回避して目的の要素を確実に取得するテクニックまでを詳しく解説します。
2026年現在のモダンなスクレイピング手法を取り入れながら、実務で役立つ知識を身につけていきましょう。
BeautifulSoupにおける「兄弟要素」の考え方
BeautifulSoupで解析されたHTML(DOMツリー)において、同じ親要素を持つ隣り合った要素同士を「兄弟要素(Siblings)」と呼びます。
例えば、ある<div>タグの中に複数の<p>タグが並んでいる場合、それらは互いに兄弟の関係にあります。
previous_siblingは、その名の通り「前の兄弟」を指し示すプロパティです。
しかし、ここで注意が必要なのは、BeautifulSoupがHTML内の改行や半角スペースも一つの要素(NavigableString)としてカウントするという点です。
人間にとっては何もないように見えるソースコード上の隙間が、プログラムにとっては立派な「前の要素」として認識されます。
この仕様を理解していないと、タグを取得したいつもりが空の文字列を取得してしまうといったバグに繋がります。
previous_siblingの基本的な使い方
まずは、previous_siblingを使用して直前の要素にアクセスする基本的なコードを確認しましょう。
以下の例では、シンプルなHTML構造を用いて動作を検証します。
from bs4 import BeautifulSoup
html_doc = """
<div>
<h2 id="title">見出し</h2><p id="content">本文のテキストです。</p>
</div>
"""
soup = BeautifulSoup(html_doc, 'html.parser')
content_p = soup.find(id="content")
# 直前の要素を取得
prev_element = content_p.previous_sibling
print(f"取得した内容: {prev_element}")
print(f"型: {type(prev_element)}")
取得した内容: <h2 id="title">見出し</h2>
型: <class 'bs4.element.Tag'>
この例では、<h2>タグと<p>タグの間に改行や空白を一切入れていないため、正しく直前のタグが取得できています。
しかし、実際のWebサイトのソースコードでこのような記述は稀であり、通常は可読性のために改行が含まれています。
空白ノードによる「取得失敗」のメカニズム
多くの初心者が直面するのが、意図しない改行コードの取得です。
HTMLが整形されている場合、ソースコード上ではタグの間に改行が入ることが一般的です。
次のコード例で、改行が含まれる場合にどのような結果になるかを見てみましょう。
html_with_space = """
<div>
<h2>見出し</h2>
<p id="target">ターゲットの要素</p>
</div>
"""
soup = BeautifulSoup(html_with_space, 'html.parser')
target = soup.find(id="target")
# 期待は <h2> だが...
print(f"previous_siblingの結果: {repr(target.previous_sibling)}")
previous_siblingの結果: '\n '
実行結果を見ると、<h2>タグではなく、改行コードと空白が含まれる文字列が返ってきていることがわかります。
これは、BeautifulSoupがHTMLを解析する際に、タグとタグの間のテキスト(ホワイトスペース)をNavigableStringオブジェクトとして保持しているためです。
この問題を回避するためには、取得した要素がタグ(Tag)であるかどうかを判定するか、別のメソッドを使用する必要があります。
find_previous_siblingで確実にタグを取得する
空白ノードを無視して、「直前にある特定のタグ」をピンポイントで取得したい場合は、find_previous_sibling()メソッドの使用を強く推奨します。
このメソッドは、兄弟要素を遡りながら、指定した条件に合致する最初の「タグ」を返してくれます。
# find_previous_siblingを使用した例
# 空白ノードを自動的にスキップして、直前のタグを探します
real_prev_tag = target.find_previous_sibling('h2')
if real_prev_tag:
print(f"見つかったタグ: {real_prev_tag.text}")
見つかったタグ: 見出し
find_previous_sibling()を使えば、間にどれだけ改行や空白があっても問題ありません。
また、引数にタグ名だけでなく、クラス名やIDなどの属性を指定することで、より詳細な絞り込みが可能です。
これにより、複雑な構造を持つWebページでも、目的のデータに関連するヘッダー情報などを正確に特定できます。
複数の要素を遡る previous_siblings
直前の1つだけでなく、自分より前にあるすべての兄弟要素を順番に取得したいケースもあります。
その場合は、previous_siblingsプロパティ(複数形)を利用します。
これはジェネレータを返すため、forループで回すことで、要素を一つずつ遡りながら処理することが可能です。
html_list = """
<ul>
<li>項目1</li>
<li>項目2</li>
<li>項目3</li>
<li id="start">項目4(ここから遡る)</li>
</ul>
"""
soup = BeautifulSoup(html_list, 'html.parser')
start_node = soup.find(id="start")
# 前にある要素をすべて走査
for sibling in start_node.previous_siblings:
# タグのみを表示(改行などは除外)
if sibling.name is not None:
print(f"要素発見: {sibling.text}")
要素発見: 項目3
要素発見: 項目2
要素発見: 項目1
このように、previous_siblingsは自分に近い順(下から上へ)に要素を走査します。
特定の要素を起点にして、その上部にある複数の見出しやリスト項目をまとめて収集したい場合に非常に強力なツールとなります。
previous_sibling と find_previous_sibling の使い分け比較
それぞれの機能の違いを理解し、適切に使い分けることが効率的なコーディングの鍵となります。
以下の表に、主な違いと推奨される利用シーンをまとめました。
| 機能名 | 主な特徴 | 推奨される用途 |
|---|---|---|
previous_sibling | 直前のノードを1つだけ取得。空白や改行も含む。 | HTML構造が完全に固定されており、極めて高速な処理が求められる場合。 |
find_previous_sibling() | 条件に合う「タグ」を遡って検索。空白を無視する。 | 通常のスクレイピングで最も推奨。確実に特定のタグを取得したい時。 |
previous_siblings | 前にある全ての要素をジェネレータとして返す。 | 起点より上にある複数のデータを一括で取得・解析したい場合。 |
find_previous_siblings() | 条件に合う全ての前のタグをリストで返す。 | 特定のクラスを持つ前の要素だけを全て抜き出したい場合。 |
基本的には、find_previous_sibling()を選択しておけば、空白ノードに悩まされることなく安全に実装できます。
previous_element との違いについて知っておく
よく混同されがちなプロパティに previous_element があります。
previous_sibling が「同じ階層の前の要素」を探すのに対し、previous_element は「HTMLソースコード上で直前に出現する要素」を探します。
つまり、previous_element は親要素の中に潜り込んだり、親要素そのものを指したりすることがあります。
ドキュメントの構造(木構造)を辿るのか、パースされた順番を遡るのかという決定的な違いがあるため、混同しないよう注意が必要です。
兄弟関係にあるデータだけを扱いたい場合は、必ず sibling という単語が含まれる方を使用しましょう。
実戦テクニック:複数の条件を組み合わせて前の要素を特定する
実際のスクレイピング現場では、ターゲットとなる要素にIDが振られていないことが多々あります。
「このテキストが含まれるスパンタグの、2つ前のテーブルを取得したい」といった複雑な条件にも、BeautifulSoupなら柔軟に対応できます。
# 複雑な条件での取得例
target_span = soup.find('span', text="価格")
# 直近の親要素へ移動してから、その前のテーブルタグを探すなどの応用が可能
target_table = target_span.find_parent('div').find_previous_sibling('table')
このように、find_parent() や find_previous_sibling() をメソッドチェーンで繋ぐことで、DOMツリーを自在に移動できるようになります。
2026年現在のモダンなPython開発環境では、こうしたコードの可読性を高めるために、型ヒントを併用してTag | NavigableString | Noneといった戻り値の型を意識することも重要です。
find_previous_sibling() が None を返す可能性(前の要素が存在しない場合)を常に考慮し、属性アクセスの前にチェックを入れることで、実行時エラーを防ぐ堅牢なコードになります。
まとめ
BeautifulSoupのprevious_siblingは、直前の要素にアクセスするための基本機能ですが、HTML内の改行や空白をノードとして拾ってしまう特性があります。
この挙動によるミスを防ぐためには、タグを確実にフィルタリングできる find_previous_sibling() の活用がベストプラクティスです。
DOMツリーの兄弟関係を正しく理解し、プロパティとメソッドを使い分けることで、ノイズに強い安定したスクレイピングプログラムが作成できるようになります。
今回解説したテクニックを駆使して、目的のデータを正確に、そして効率的に抽出していきましょう。
