Webサイトの自動操作やスクレイピングをPythonとSeleniumで実現する際、特定のボタンをクリックしたり、入力フォームに値を渡したりする操作は基本中の基本です。
しかし、実務レベルのプログラムでは「ページ内にあるすべての商品名を取得したい」「特定のクラスを持つ要素をすべてリスト化して処理したい」といった、複数の要素をまとめて扱うシーンが数多く存在します。
このような場面で活躍するのが、Seleniumが提供する find_elements メソッドです。
このメソッドは、単一の要素を取得する find_element とは異なり、条件に合致するすべての要素をリスト形式で返してくれる非常に便利なツールです。
本記事では、この find_elements の基本的な実装方法から、取得したリストを効率的にループ処理するテクニック、さらには実務で陥りやすい注意点までを詳しく解説します。
find_elements メソッドの基本概念
Seleniumでブラウザ上の要素を特定する際、最も頻繁に使われるのは find_element です。
しかし、このメソッドは「最初に見つかった1つの要素」しか取得できません。
これに対して、今回解説する find_elements (末尾に s が付く複数形) は、指定した条件に合致するHTML要素をすべて探し出し、それらをPythonのリストとして格納します。
find_element と find_elements の決定的な違い
初心者の方が最初につまずきやすいポイントが、この2つのメソッドの挙動の違いです。
単に「1つか複数か」という違いだけでなく、要素が見つからなかった時の挙動が大きく異なります。
| 特徴 | find_element (単数) | find_elements (複数) |
|---|---|---|
| 戻り値 | WebElement オブジェクト | WebElement オブジェクトのリスト |
| 一致する要素がない場合 | NoSuchElementException エラーが発生 | 空のリスト ([]) を返す (エラーにならない) |
| 主な用途 | 特定のボタンや入力欄の操作 | 検索結果一覧、メニュー項目、表データの取得 |
この表からも分かる通り、find_elements は要素が存在するかどうかの確認(存在チェック)にも利用できるという利点があります。
エラーを出さずにプログラムを継続させたい場合に重宝するメソッドです。
Pythonでの基本的な実装方法
それでは、具体的なPythonコードを見ていきましょう。
まずは、Seleniumを動かすための基本設定と、find_elements を使って複数のリンクを取得する例を紹介します。
Byクラスのインポートと基本構文
Selenium 4以降では、要素の指定方法として By クラスを使用することが推奨されています。
from selenium import webdriver
from selenium.webdriver.common.by import By
# ブラウザの起動 (Chromeの例)
driver = webdriver.Chrome()
# ターゲットとなるサイトへアクセス
driver.get("https://example.com")
# すべての a タグ (リンク要素) を取得
elements = driver.find_elements(By.TAG_NAME, "a")
# 取得した要素の数を確認
print(f"取得した要素数: {len(elements)}")
# ブラウザを閉じる
driver.quit()
取得した要素数: 15
このように、find_elements の引数には、検索対象とする属性 (By.ID, By.CLASS_NAME, By.CSS_SELECTOR など) と、その値を指定します。
戻り値は常にリスト型になるため、要素が1つしかない場合でも、要素数が1のリストとして返されます。
取得した要素リストのループ処理
find_elements で取得したリストを最大限に活用するには、Pythonの for 文を用いたループ処理が不可欠です。
各要素からテキストを取り出したり、属性値を取得したりする流れを解説します。
for文を用いたテキストの一括取得
例えば、ニュースサイトの見出し一覧をすべて取得し、コンソールに表示したい場合は以下のように記述します。
# class属性が 'news-title' であるすべての要素を取得
titles = driver.find_elements(By.CLASS_NAME, "news-title")
# ループ処理で一つずつ中身を取り出す
for i, title_element in enumerate(titles, 1):
# .text 属性で要素内の文字列を取得
text = title_element.text
print(f"記事{i}: {text}")
要素の属性値を抽出する
テキストだけでなく、リンクのURL (href属性) や画像のソース (src属性) を取得したい場合は、get_attribute() メソッドを併用します。
# すべてのアンカータグを取得
links = driver.find_elements(By.TAG_NAME, "a")
for link in links:
# href属性の値を取得
url = link.get_attribute("href")
if url:
print(f"リンク先URL: {url}")
実践的な活用シーン
ここからは、実際のWeb開発やデータ収集の現場でよく使われる、より具体的な実装例を見ていきましょう。
1. 特定のキーワードを含む要素だけを操作する
すべての要素を取得した後に、Python側で条件分岐を行うことで、特定の条件に合致する要素だけをクリックするといった高度な操作が可能になります。
buttons = driver.find_elements(By.TAG_NAME, "button")
for btn in buttons:
# ボタンのテキストに「送信」が含まれている場合のみクリック
if "送信" in btn.text:
btn.click()
break # 目的の操作を終えたらループを抜ける
2. テーブル(表)データのスクレイピング
HTMLの <table> タグ内のデータを1行ずつ取得する処理は、find_elements の得意分野です。
# テーブルの行(tr)をすべて取得
rows = driver.find_elements(By.XPATH, "//table[@id='data-table']//tr")
for row in rows:
# 各行の中にあるセル(td)を取得
cols = row.find_elements(By.TAG_NAME, "td")
# リスト内包表記で各セルのテキストをリスト化
row_data = [col.text for col in cols]
print(row_data)
この例では、row.find_elements のように、特定の要素(この場合は1行分のtr要素)の中からさらに子要素を探すというテクニックを使っています。
これにより、構造化されたデータを正確に抽出できます。
find_elements を使う際の注意点とベストプラクティス
強力な find_elements ですが、実装の際にはいくつか注意すべき落とし穴があります。
これらを理解しておくことで、実行時エラーの少ない安定したスクリプトを記述できるようになります。
要素が存在しない場合のハンドリング
前述の通り、find_elements は要素が見つからない場合にエラーを出しません。
これはメリットでもありますが、「要素がある前提」でコードを書くと、空のリストに対して処理を行おうとして意図しない結果になることがあります。
elements = driver.find_elements(By.ID, "non-existent-id")
if len(elements) > 0:
# 要素が存在する場合のみ処理を行う
print("要素が見つかりました")
else:
# 要素が見つからなかった場合の処理
print("要素が見つかりませんでした。スキップします。")
このように、len() を使って要素数のチェックを挟むのが、プロフェッショナルな実装の第一歩です。
動的なページ読み込みへの対応
最近のWebサイトは、スクロールに応じて要素が追加されたり、非同期通信(Ajax)でデータが表示されたりすることが一般的です。
ブラウザを開いた直後に find_elements を実行しても、まだ要素がHTML上に描画されておらず、空のリストが返ってくるケースが多々あります。
この問題を解決するには、WebDriverWait と expected_conditions を組み合わせて、要素が表示されるまで待機する処理を入れましょう。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 最大10秒間、特定のクラスを持つ要素が1つ以上現れるのを待つ
wait = WebDriverWait(driver, 10)
elements = wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "item-list")))
StaleElementReferenceException への対策
ループ処理中にページが遷移したり、JavaScriptによって要素が書き換えられたりすると、StaleElementReferenceException というエラーが発生することがあります。
これは、取得済みの要素(リスト内の各要素)が、ブラウザ上の最新の状態とリンクしなくなった(古くなった)ことを意味します。
このエラーを回避するためには、「ループの中で毎回要素を取得し直す」か、「必要なデータ(テキストなど)を先にリストに格納してしまい、その後は要素自体を操作しない」といった工夫が必要です。
推奨される対策例(テキストのみ先に抽出)
# 1. 最初に要素を取得
elements = driver.find_elements(By.CLASS_NAME, "item")
# 2. テキストなどの必要な情報だけをPythonのリストにコピーする
item_names = [el.text for el in elements]
# 3. あとはSeleniumの要素ではなく、単なる文字列リストとして処理する
for name in item_names:
print(name)
リスト内包表記によるスマートな実装
Pythonの強力な機能である「リスト内包表記」を使うと、find_elements で取得した結果を非常に短く、かつ読みやすく加工できます。
# 全リンクのURLリストを一行で作成
urls = [el.get_attribute("href") for el in driver.find_elements(By.TAG_NAME, "a")]
# 空のURLやNoneを除外する場合
valid_urls = [url for url in urls if url]
このように記述することで、コードの行数を減らしつつ、意図が明確なプログラムを書くことができます。
まとめ
PythonとSeleniumを用いた自動化において、find_elements は単なる「複数取得用メソッド」以上の価値を持っています。
- リスト形式で結果が返されるため、Pythonの標準的なループ処理やリスト操作との相性が抜群であること。
- 要素が見つからない場合でも例外が発生しないため、安全な条件分岐(存在チェック)が可能であること。
- 子要素の探索と組み合わせることで、複雑なテーブル構造なども効率的に解析できること。
これらの特性を理解し、適切に WebDriverWait による待機処理やエラーハンドリングを組み合わせることで、堅牢でメンテナンス性の高い自動化スクリプトを構築できるようになります。
まずは、身近なWebサイトのリンク一覧や見出し一覧を取得するところから練習を始めてみてください。
取得したリストを for 文で回し、必要な情報を自在に抽出できるようになれば、Seleniumを活用したWebスクレイピングの幅は飛躍的に広がるはずです。
