Pythonを活用したデータ収集(ウェブスクレイピング)において、HTML要素を効率的に取得するスキルは必須です。
その中でも、BeautifulSoupライブラリのfind_allメソッドは、特定の条件に合致する要素をすべて抽出するための最も基本的かつ強力なツールです。
単に要素を取得するだけでなく、クラス名、属性、さらには正規表現を組み合わせることで、複雑な構造を持つWebサイトからも目的のデータを正確に抜き出すことが可能になります。
本記事では、2026年現在の開発現場でも標準的に利用されているfind_allの基本的な使い方から、実務で役立つ応用テクニックまでを詳しく解説します。
BeautifulSoupのfind_allとは
BeautifulSoupにおけるfind_allは、ドキュメント内を検索して指定したフィルタに一致するすべての要素を取得するためのメソッドです。
スクレイピングの現場では、リストページから記事のタイトルを一覧取得したり、商品一覧から価格情報を抜き出したりする際に頻繁に利用されます。
find_allが返す結果は、Pythonのリストに近い性質を持つResultSetというオブジェクトです。
この中には、条件にマッチしたすべての要素(Tagオブジェクト)が格納されており、ループ処理(for文)などを用いて一つずつデータを取り出すのが一般的な流れとなります。
findメソッドとの決定的な違い
よく比較されるメソッドにfindがありますが、これらには明確な違いがあります。
| メソッド名 | 取得できる要素の数 | 戻り値の型 |
|---|---|---|
find() | 条件に一致する最初の一つのみ | Tagオブジェクト(見つからない場合はNone) |
find_all() | 条件に一致するすべて | ResultSet(リスト形式、見つからない場合は空のリスト) |
例えば、ページ内に複数の<a>タグ(リンク)がある場合、findを使うと最初の1件しか取得できませんが、find_allを使えばページ内のすべてのリンクを網羅できます。
find_allの基本的な使い方
まずは、最もシンプルなHTML構造を例に、特定のタグを取得する基本操作を確認しましょう。
from bs4 import BeautifulSoup
# サンプルHTML
html_content = """
<html>
<body>
<h1>プログラミング言語一覧</h1>
<ul>
<li class="lang">Python</li>
<li class="lang">JavaScript</li>
<li class="lang">Go</li>
</ul>
</body>
</html>
"""
# BeautifulSoupオブジェクトの作成
soup = BeautifulSoup(html_content, 'html.parser')
# すべての <li> タグを取得
items = soup.find_all('li')
# 取得した結果を表示
for item in items:
print(item.text)
Python
JavaScript
Go
上記のコードでは、soup.find_all('li')によってHTML内のすべての<li>要素をリスト形式で取得しています。
その後、forループで各要素にアクセスし、.text属性を使ってタグ内の文字列のみを抽出しています。
属性を指定して絞り込む方法
実際のWebサイトでは、単にタグ名だけで検索すると、不要なデータまで取得してしまうことが多々あります。
そのため、class(クラス名)やid(ID)などの属性を活用した絞り込みが重要になります。
クラス名による指定
HTMLのclass属性で絞り込む場合、Pythonの予約語であるclassとの衝突を避けるため、引数にはclass_(最後にアンダースコアを付ける)を使用します。
# 特定のクラスを持つ要素のみを取得
target_elements = soup.find_all('li', class_='lang')
IDによる指定
特定のIDを持つ要素を検索する場合は、直接id引数に値を指定します。
# 特定のIDを持つ要素を取得
header_content = soup.find_all(id='main-header')
複数の属性を組み合わせる
attrs引数に辞書形式で条件を渡すことで、複数の属性を同時に指定することも可能です。
これにより、より精度の高い抽出が可能になります。
# 複数の属性条件(classとdata-categoryなど)を指定
results = soup.find_all('div', attrs={'class': 'item-box', 'data-status': 'active'})
このように、タグ名 + 属性の組み合わせを使いこなすことが、スクレイピングを効率化する第一歩です。
高度なテクニック:複数のタグや正規表現の活用
さらに柔軟な検索を行うために、リスト形式での指定や正規表現(regex)を用いたパターンマッチングについても解説します。
複数のタグを同時に取得する
「見出しタグ(h1)と段落タグ(p)の両方をまとめて取得したい」というケースでは、リストを使用してタグ名を指定します。
# h1 と p をまとめて取得
tags = soup.find_all(['h1', 'p'])
この方法を使うと、HTML内の出現順に要素が格納されるため、記事の構成を維持したままテキストを抽出したい場合に非常に便利です。
正規表現(reモジュール)を利用する
「特定の文字列から始まるクラス名」や「特定のパターンを含むリンク」を探す際には、Pythonの標準ライブラリであるreモジュールを組み合わせます。
import re
# クラス名が 'post-' で始まる div 要素をすべて取得
posts = soup.find_all('div', class_=re.compile(r'^post-'))
# href属性に 'python' という文字列を含む a タグを取得
python_links = soup.find_all('a', href=re.compile(r'python'))
正規表現を利用することで、動的に変化するIDやクラス名に対しても柔軟に対応できるようになります。
これはモダンなWebサイトのスクレイピングにおいて非常に重要なテクニックです。
検索範囲と結果の制限
大規模なHTMLファイルを解析する場合、すべての要素を検索対象にすると処理に時間がかかることがあります。
これを回避するためのパラメータがlimitとrecursiveです。
取得件数を制限する(limit)
「最初の5件だけ取得できれば十分」という場合は、limit引数を指定します。
# 最初の3つのリンクだけを取得
top_links = soup.find_all('a', limit=3)
この指定により、指定した件数に達した時点で検索を終了するため、実行パフォーマンスの向上が期待できます。
直下の階層のみを検索する(recursive)
デフォルトでは、find_allは指定した要素の子要素、孫要素へと再帰的に検索を行います。
しかし、直下の子要素のみを対象にしたい場合は、recursive=Falseを設定します。
# soupの直下にあるタグのみを検索(深い階層は無視)
direct_children = soup.find_all('div', recursive=False)
このオプションは、複雑な入れ子構造を持つHTMLから特定のブロックだけを正確に分離したい時に役立ちます。
実践例:ニュースサイトからの情報抽出
ここでは、より実践的な例として、ニュースサイトのHTML構造から「記事タイトル」と「リンクURL」をセットで抽出するプログラムを紹介します。
from bs4 import BeautifulSoup
html = """
<div id="news-section">
<article class="news-item">
<h2 class="title"><a href="/news/001">2026年のAIトレンド</a></h2>
<p class="date">2026-05-01</p>
</article>
<article class="news-item">
<h2 class="title"><a href="/news/002">Python 3.13の新機能解説</a></h2>
<p class="date">2026-05-02</p>
</article>
</div>
"""
soup = BeautifulSoup(html, 'html.parser')
# 記事ブロックをすべて取得
articles = soup.find_all('article', class_='news-item')
news_list = []
for article in articles:
# 記事内のaタグを取得
link_tag = article.find('a')
if link_tag:
title = link_tag.text
url = link_tag.get('href')
news_list.append({'title': title, 'url': url})
# 結果の表示
for news in news_list:
print(f"タイトル: {news['title']} / URL: {news['url']}")
タイトル: 2026年のAIトレンド / URL: /news/001
タイトル: Python 3.13の新機能解説 / URL: /news/002
この例のように、find_allで大きな親要素を取得し、その中でfindを使って子要素を特定するという二段構えの処理は、スクレイピングにおける鉄板のパターンです。
これにより、データ同士の関連性(どのタイトルがどのURLに対応するか)を崩さずに抽出できます。
find_allを使用する際の注意点とエラー回避
find_allを使いこなす上で、初心者が陥りやすいポイントがいくつかあります。
ResultSetは「リストのように振る舞う」が、タグではない
最も多いエラーは、find_allで取得した結果に対して、直接.textや.get()を呼び出そうとすることです。
# 誤ったコード例
results = soup.find_all('a')
print(results.text) # エラーが発生!
find_allの戻り値は「要素のリスト」です。
個々の要素からテキストを取得したい場合は、必ずループで回すか、インデックス([0]など)で指定する必要があります。
要素が見つからない場合の挙動
find_allは、条件に一致する要素が一つもなかった場合、Noneではなく空のリスト([])を返します。
そのため、戻り値に対してそのままfor文を回してもエラーにはなりませんが、処理はスキップされます。
一方で、findの場合は見つからないとNoneを返すため、その後のプロパティ参照でAttributeErrorが発生する可能性があります。
この挙動の違いを理解しておくことで、デバッグがスムーズになります。
パフォーマンスを最適化するために
大量のデータを扱う際、BeautifulSoupの解析スピードがボトルネックになることがあります。
以下の工夫を取り入れることで、効率的なスクレイピングが可能になります。
- lxmlパーサーの利用: 標準の
html.parserよりも高速なlxmlを利用することを検討してください。 - soup_strainerの活用: 読み込み時に必要な部分だけを解析する「SoupStrainer」という機能があります。
- 適切な検索条件: なるべく具体的(IDなど)な条件で検索を開始し、探索範囲を絞り込みます。
まとめ
BeautifulSoupのfind_allメソッドは、Webスクレイピングにおいて最も汎用性の高いツールの一つです。
本記事で解説した以下のポイントを押さえることで、効率的なデータ収集が可能になります。
- find_allは、条件に一致するすべての要素をリスト形式で返す。
class_やattrsを用いることで、特定のクラスや属性を持つ要素を精密に絞り込める。- 正規表現やリスト指定を活用すれば、複雑な検索パターンにも対応可能。
- 取得結果は
ResultSetであるため、各要素へのアクセスにはループ処理が必要。
これらのテクニックを組み合わせることで、構造が複雑な現代のWebサイトからも、必要な情報だけをスマートに抽出できるようになります。
まずはシンプルなサイトでfind_allを試し、徐々に属性指定や正規表現を組み合わせた高度な抽出に挑戦してみてください。
