Pythonを用いたスクレイピングにおいて、特定の要素内から目的のデータを取り出す際、親子関係を意識したナビゲーションは非常に重要です。
その中でも「子要素の取得」は最も頻繁に利用される操作の一つです。
本記事では、BeautifulSoupで子要素を取得するための「.children」プロパティの使い方を中心に、「.contents」との違いや効率的なフィルタリング手法について詳しく解説します。
BeautifulSoupにおける要素取得の基礎知識
BeautifulSoupは、HTMLやXMLドキュメントをパースして、プログラムから扱いやすいツリー構造に変換するライブラリです。
このツリー構造において、あるタグの内側に含まれる要素を「子要素」と呼びます。
スクレイピングの実務では、例えば<ul>タグの中から個々の<li>タグだけを抽出したり、特定の<div>ブロック直下にある情報だけを抜き出したりする場面が多々あります。
このとき、単にfind_all()を使うだけでは、深い階層にある孫要素まで取得してしまい、データの整合性が崩れる原因になることがあります。
そのため、「直下の子要素のみ」を正確に指定して取得する技術が必要となります。
.childrenプロパティの使い方
.childrenは、特定の要素の直下にある子要素を順番に取得するためのプロパティです。
最大の特徴は、「イテレータ(生成器)」として値を返す点にあります。
基本的な実装例
まずは、簡単なHTML構造を例に、どのように子要素が取得されるかを確認してみましょう。
from bs4 import BeautifulSoup
html_doc = """
<div id="parent">
<p>一番目の段落</p>
<span>スパン要素</span>
<p>二番目の段落</p>
</div>
"""
soup = BeautifulSoup(html_doc, 'html.parser')
parent_div = soup.find(id="parent")
# .childrenを使って子要素をループで取得
print("子要素の抽出を開始します:")
for child in parent_div.children:
# repr()を使って改行文字なども見えるように出力
print(repr(child))
子要素の抽出を開始します:
'\n'
<p>一番目の段落</p>
'\n'
<span>スパン要素</span>
'\n'
<p>二番目の段落</p>
'\n'
この結果を見るとわかる通り、.childrenはHTMLタグだけでなく、タグの間に存在する「改行」や「空白」も一つの要素(NavigableString)として取得します。
これは初心者が最初につまずきやすいポイントです。
.childrenと.contentsの決定的な違い
子要素を取得する方法には、.childrenの他に.contentsというプロパティも存在します。
これら2つの動作は似ていますが、メモリ効率や戻り値の型において重要な違いがあります。
1. 戻り値の型
「.contents」は、子要素をすべて含んだ「リスト」を返します。
一方で「.children」は「リスト」ではなく「リストイテレータ」を返します。
| 特徴 | .contents | .children |
|---|---|---|
| 型 | list | list_iterator |
| メモリ消費 | 要素数に比例して増加 | 低い(逐次処理) |
| インデックス指定 | 可能 (obj[0]など) | 不可 (ループが必要) |
| 長さの取得 | len()で取得可能 | 変換しない限り不可 |
2. メモリ効率とパフォーマンス
2026年現在のWebサイトは、動的なコンテンツ生成や巨大なDOM構造を持つものが増えています。
数千、数万という子要素を持つ親要素を処理する場合、一度にすべてのリストを作成する.contentsよりも、必要な分だけを順番に生成する.childrenの方がメモリ消費を大幅に抑えることができます。
特に大規模なデータスクレイピングや、リソースが制限されたクラウド環境での実行においては、イテレータを活用することがベストプラクティスとされています。
特定のタグのみを抽出するテクニック
先ほどの結果で見たように、.childrenをそのまま使うと、改行コード(\n)などの不要なテキスト要素が混入します。
実務では「HTMLタグのみ」を取り出したいことがほとんどです。
タグ要素のみをフィルタリングする
以下のコードは、bs4.element.Tag型であるかどうかを判定し、純粋なタグ要素だけを取り出す方法です。
from bs4 import BeautifulSoup
from bs4.element import Tag
html_doc = """
<ul id="target-list">
<li>項目 1</li>
<li>項目 2</li>
<li>項目 3</li>
</ul>
"""
soup = BeautifulSoup(html_doc, 'html.parser')
ul_tag = soup.find(id="target-list")
# タグ要素のみをリストアップ
tags_only = [child for child in ul_tag.children if isinstance(child, Tag)]
for i, tag in enumerate(tags_only):
print(f"{i + 1}番目のタグ: {tag.text}")
1番目のタグ: 項目 1
2番目のタグ: 項目 2
3番目のタグ: 項目 3
このように、isinstance(child, Tag)を利用することで、空白や改行を完全に排除した状態で子要素を扱うことが可能になります。
これにより、データのクリーニング作業がスムーズに進みます。
.children vs .descendants(子要素と子孫要素)
子要素の取得において混同しやすいのが「子要素(children)」と「子孫要素(descendants)」の違いです。
子要素(children)
直下の一階層のみを対象とします。
例えば、<div>の中に<ul>があり、その中に<li>がある場合、<div>の.childrenには<ul>は含まれますが、<li>は含まれません。
子孫要素(descendants)
すべての階層を再帰的に取得します。
入れ子構造になっているすべての要素を一つずつ順番に辿りたい場合に利用します。
html_nested = """
<div id="root">
<p>親の直下のテキスト <span>孫のテキスト</span></p>
</div>
"""
soup = BeautifulSoup(html_nested, 'html.parser')
root = soup.find(id="root")
print("--- children の場合 ---")
for c in root.children:
if isinstance(c, Tag):
print(f"Tag: {c.name}")
print("\n--- descendants の場合 ---")
for d in root.descendants:
if isinstance(d, Tag):
print(f"Tag: {d.name}")
--- children の場合 ---
Tag: p
--- descendants の場合 ---
Tag: p
Tag: span
複雑な表組みや記事本文の中から、特定の構造を保ったままデータを抽出したい場合は、この二つの違いを明確に使い分ける必要があります。
find_all(recursive=False) による代替案
実は、.childrenと同様の効果を得る方法がもう一つあります。
それはfind_all()メソッドでrecursive=Falseという引数を指定することです。
.childrenとの使い分け
find_all(recursive=False)を使うと、「特定条件に合致する直下の子要素のみ」を検索できます。
.childrenは条件なしですべての直下要素を走査しますが、特定のクラス名や属性を持つ子要素だけが欲しい場合はfind_allの方が簡潔に記述できます。
# id="parent" 直下の、class="target" を持つ pタグだけを取得
targets = parent_div.find_all("p", class_="target", recursive=False)
この方法は、.childrenでループを回してif文で判定するよりもコードが短くなり、可読性が向上します。
用途に合わせて、これら二つの手法を選択するのが賢明です。
実践例:複雑な製品リストからのデータ抽出
最後に、実際のWebスクレイピングでよく遭遇する「カード形式のリスト」から情報を抽出する実践的なコード例を紹介します。
html_products = """
<div class="product-container">
<div class="product-card">
<h3>製品A</h3>
<p class="price">1,000円</p>
</div>
<div class="product-card">
<h3>製品B</h3>
<p class="price">2,000円</p>
</div>
<!-- 広告などの不要な要素 -->
<div class="ads">
<p>おすすめ広告</p>
</div>
</div>
"""
soup = BeautifulSoup(html_products, 'html.parser')
container = soup.find(class_="product-container")
# childrenを使って各カードを処理
for item in container.children:
if isinstance(item, Tag) and "product-card" in item.get("class", []):
# カード内の情報をさらに取得
name = item.find("h3").text
price = item.find(class_="price").text
print(f"商品名: {name} | 価格: {price}")
商品名: 製品A | 価格: 1,000円
商品名: 製品B | 価格: 2,000円
この例では、.childrenで直下を走査しつつ、isinstanceによるタグ判定と、class属性のチェックを組み合わせることで、目的のデータのみを安全かつ確実に抽出しています。
まとめ
BeautifulSoupで子要素を取得するための手法について、それぞれの特性を整理しました。
- .children:メモリ効率が良いイテレータ形式。大量のデータを扱う際に最適。
- .contents:リスト形式。インデックスでアクセスしたい場合に便利。
- 注意点:どちらも空白や改行を要素として含むため、
Tag判定によるフィルタリングが推奨される。 - 代替案:条件指定が必要な場合は
find_all(recursive=False)が有効。
HTMLの構造を正確に捉え、最適なプロパティを選択することは、バグの少ない保守性の高いスクレイピングコードを書くための第一歩です。
まずは小さなプログラムから.childrenを活用し、その挙動に慣れていくことをおすすめします。
