自動化

Seleniumの使い方|Selenium 4でWebDriverの手動設定が不要になった書き方

Seleniumの解説記事を読みながら手を動かすと、最初のコードで止まることがあります。webdriver.Chrome(executable_path="...")TypeError になる、find_element_by_id()AttributeError になる。書き方が間違っているのではなく、その書き方がSelenium本体から削除されているだけです。ChromeDriverの手動ダウンロード手順も、いまは大半のケースで不要になりました。

この記事は、Python版Selenium 4.46.0(2026年7月11日公開)時点の仕様に合わせています。古い記事のコードをどう書き換えれば動くのかは、削除バージョン付きの対応表で示します。

まとめ

  • ChromeDriverの手動ダウンロードは不要:Selenium 4.6以降に同梱されるSelenium Managerが、ブラウザのバージョンに合うdriverを自動で取得・キャッシュします。pip install selenium だけで webdriver.Chrome() が動きます。
  • 古い記事のコードは実行できませんfind_element_by_id() 系はSelenium 4.3.0で、executable_pathdesired_capabilities といったコンストラクタ引数は4.10.0で削除済みです。
  • 待機に time.sleep() を使わない:固定秒数の待機はテストを遅くし、しかも不安定にします。WebDriverWait に置き換えます。
  • テスト自動化はpytest+ヘッドレスが基本形:GitHub Actionsのubuntu-24.04ランナーにはChromeとChromeDriverが同梱済みで、追加インストールなしにCIで回せます。
  • ログイン後の画面遷移が無い処理にSeleniumは過剰:HTTPリクエストで取得できるデータなら、requestsのほうが速く壊れにくい構成になります。

以下、根拠と実装を順に見ていきます。

Seleniumで自動化できる範囲と、使わないほうがよい場面

Seleniumは、人がブラウザで行う操作をプログラムから再現するツールです。画面を開き、要素を探し、クリックし、文字を入力する。実際のChromeやFirefoxをそのまま動かすため、JavaScriptで後から描画される要素も、ログインが必要なページも扱えます。W3CのWebDriver仕様に沿った通信でブラウザを制御しており、特定のブラウザ専用の仕組みではありません。

スクリプト・driver・ブラウザの3層構造

Seleniumを使うとき、動いているプログラムは1つではありません。Pythonスクリプトがライブラリを呼び、Seleniumが chromedrivergeckodriver といったdriverプロセスへHTTPで命令を送り、driverがブラウザを操作する3層構造です。ここを意識しておくと、エラーの発生箇所を切り分けやすくなります。「driverとブラウザのバージョンが合わない」という頻出の失敗も、この構造から生まれるものです。

なお、Python版Seleniumは4.46.0でPython 3.10以上が必要です(PyPIの requires_python 表記)。3.9以前の環境には最新版が入りません。

requestsで足りる処理にSeleniumを使わない判断基準

結論から言えば、ブラウザでしか取れない情報が無いなら、Seleniumは選ばないほうがよいです。実ブラウザとdriverの2プロセスぶんメモリを消費し、ページのレンダリング完了を待つ時間が処理ごとに乗り、ブラウザの自動更新で組み合わせが変わるたびに壊れる可能性を抱えます。

判断は単純です。対象ページのHTMLを curl で取得し、欲しいデータが含まれているか見てください。

確認したこと 選ぶ道具
JSON APIを直接呼べる requests(最速)
curlのHTMLに目的のデータがある requests+BeautifulSoup
開発者ツールのDOMにしかない Selenium/Playwright
ログイン後の画面遷移が必要 Selenium/Playwright

ブラウザ操作が必要な場合も、選択肢はSeleniumだけではありません。同じくブラウザを自動化するPlaywrightは待機処理が既定で組み込まれており、新規プロジェクトでは有力な候補になります。Pythonでのスクレイピング用途に限れば、driver不要で動作するDrissionPageという選択肢もあります。

Seleniumのインストールとセットアップ|ChromeDriver手動ダウンロードの不要化

いまも多くの入門記事が「ChromeDriverをダウンロードしてPATHに置く」という手順から始まります。2022年11月のSelenium 4.6以降、この作業はほとんどのケースで不要です。

pip install seleniumだけで動く仕組み(Selenium Manager)

Selenium 4.6から、公式ドライバマネージャのSelenium Managerがリリースに同梱されています。Rustで実装されたコマンドラインツールで、公式ドキュメントには「Selenium bindings use this tool by default, so you do not need to download it or add anything to your code」(Seleniumのバインディングはこのツールを既定で使うため、ダウンロードもコードへの追記も不要)と記載されています。

必要な作業はこれだけです。

pip install selenium
from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://www.example.com")
print(driver.title)
driver.quit()

driverが見つからないとき、Selenium Managerはインストール済みブラウザのバージョンを調べ、対応するdriverを各ベンダー公開のメタデータから解決し、ダウンロードして ~/.cache/selenium に保存します。次回以降はキャッシュからの読み出しです。Chromeが自動更新でバージョンを上げてもdriverが追従するため、「session not created: This version of ChromeDriver only supports Chrome version 113」といった噛み合わせのエラーが起きにくくなります。

Selenium 4.11.0からはブラウザ本体の自動取得にも対応し、Chromeが入っていない環境ではChrome for Testingが取得されます(Firefoxは4.12.0、Edgeは4.14.0以降)。

Selenium Managerに任せず手動でdriverを渡すケース

ひとつ注意点があります。Selenium Managerによるdriver管理は公式ドキュメント上「opt-in」と説明されており、あくまでフォールバックという位置づけです。PATH上にdriverが存在すればそちらが優先され、明示的にパスを渡した場合もその指定が使われます。なお、公式ドキュメントの表題は現在も「Selenium Manager (Beta)」のままです。

外部ネットワークへ出られない環境や、driverのバージョンを固定して検証したい場合は、Service オブジェクトにパスを渡します。削除された executable_path 引数の代替がこれにあたります。

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

service = Service(executable_path="/usr/local/bin/chromedriver")
driver = webdriver.Chrome(service=service)

動かなくなった旧コードの書き換え|削除済みAPIと現行の書き方

2023年より前に書かれた記事のコードは、そのままでは動きません。Seleniumのリリースノート(py/CHANGES)とソースを照合すると、削除された時期が特定できます。

旧い書き方 削除されたバージョン 現行の書き方
find_element_by_id("x") 4.3.0 find_element(By.ID, "x")
find_elements_by_class_name("x") 4.3.0 find_elements(By.CLASS_NAME, "x")
find_element_by_xpath("...") 4.3.0 find_element(By.XPATH, "...")
Chrome(executable_path="...") 4.10.0 Chrome(service=Service("..."))
Chrome(desired_capabilities=caps) 4.10.0 Chrome(options=options)
Chrome(chrome_options=opts) 4.10.0 Chrome(options=opts)
Chrome(service_log_path="...") 4.10.0 Service(log_output="...")

find_element_by_* 系の削除は、リリースノートに「Deprecated find_element_by_* and find_elements_by_* are now removed (#10712)」と記載されたSelenium 4.3.0です。コンストラクタ引数の削除は4.10.0の「Remove significant amounts of deprecated code (#12030)」にあたり、該当プルリクエスト「[py] Remove deprecated code in driver classes」は2023年6月1日にマージされました。現行ソースでChromeのコンストラクタが受け取るのは optionsservicekeep_alive の3つだけです。

手元のコードの世代を判定するには、find_element_by という文字列を検索してください。1件でもヒットすれば、そのコードは4.3.0以降で動きません。

ブラウザ操作の基本|要素取得・入力・クリック・スクロールの実装

ここから先は driver が生成済みである前提のコードです。要素の取得、入力とクリック、複合操作の順に見ていきます。

Byによる要素取得とXPathの使い分け

要素の取得は find_element(最初の1件)と find_elements(リスト)の2つに集約されています。第1引数には By クラスの定数を渡します。

from selenium.webdriver.common.by import By

driver.find_element(By.ID, "search-box").send_keys("selenium")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
rows = driver.find_elements(By.XPATH, "//table[@id='result']/tbody/tr")
print(len(rows))

指定方法には優先順位があります。By.ID が使えるなら最優先。IDが無ければ By.CSS_SELECTOR。XPathは「テキスト内容で選ぶ」「親要素をたどる」など、CSSセレクタで書けない条件だけに絞ってください。開発者ツールが自動生成した長いXPathをコピーして貼ると、画面構造が少し変わっただけで壊れます。

find_elements は要素が見つからないとき、例外ではなく空リストを返します。有無を判定したいだけなら、例外処理よりこちらのほうが読みやすくなります。

入力・クリック・スクロール・ウインドウ操作の実装

入力とクリックは要素オブジェクトのメソッドで完結します。ウインドウサイズとスクロールはdriver側の操作です。

driver.maximize_window()

field = driver.find_element(By.NAME, "username")
field.clear()
field.send_keys("test_user")

# ページ末尾までスクロール
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

# 特定の要素までスクロール
target = driver.find_element(By.ID, "footer-link")
driver.execute_script("arguments[0].scrollIntoView(true);", target)

要素が画面外にあってクリックできないときは、scrollIntoView で位置を合わせます。window.scrollTo による末尾スクロールは、無限スクロールのページでコンテンツを読み込ませる用途にも使えます。

ActionChainsが必要になる操作

ホバーで開くメニュー、ドラッグ&ドロップ、修飾キーとの同時押しは、単発のクリックでは再現できません。こうした複合操作には ActionChains を使います。

from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys

menu = driver.find_element(By.ID, "menu")
item = driver.find_element(By.ID, "sub-item")

ActionChains(driver).move_to_element(menu).click(item).perform()
ActionChains(driver).key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL).perform()

perform() を呼ぶまで操作は実行されません。メソッドをつないだだけで動かない、という詰まり方をしがちな箇所です。上の例では item をホバー前に取得しているため、サブメニューが遅れて描画される画面では move_to_element(menu).perform() を先に実行し、WebDriverWaititem を取得してから2本目のチェーンを組んでください。なお、Selenium 4.10.0でActionsクラスのポインタ種別変更に対応しており(リリースノート「Add support for changing pointers in Actions class (#11521)」)、マウス以外の入力もこのクラスから扱えます。

検索から一覧取得・CSV保存までの一連の実装

ここまでの部品をつなぐと、1本のスクリプトになります。検索欄に入力し、結果一覧をCSVへ書き出すまでの例です。

import csv

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)

try:
    driver.get("https://example.com/search")

    box = wait.until(EC.element_to_be_clickable((By.NAME, "q")))
    box.send_keys("selenium")
    box.send_keys(Keys.RETURN)

    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".result")))
    rows = driver.find_elements(By.CSS_SELECTOR, ".result")

    with open("results.csv", "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.writer(f)
        writer.writerow(["title", "url"])
        for row in rows:
            link = row.find_element(By.CSS_SELECTOR, "a")
            writer.writerow([link.text, link.get_attribute("href")])
finally:
    driver.quit()

要素を row.find_element() と行から辿ると、行ごとの対応が崩れません。文字コードの utf-8-sig はExcelで開いたときの文字化け対策、try/finally は例外時にブラウザプロセスを残さないための囲みです。

待機処理の設計|time.sleepからWebDriverWaitへの置き換え

入門記事で最も多く見かける誤りが time.sleep(2) です。この書き方は二正面で損をします。ページが0.3秒で表示されても2秒待つため無駄が積み上がり、逆にネットワークが遅れて3秒かかった日にはテストが落ちる。待ち時間を長くすれば安定はしますが、今度はテスト全体の実行時間が膨らみます。

Seleniumには条件が満たされるまで待つ仕組みが用意されています。

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)

# クリックできる状態になるまで最大10秒待つ
button = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
button.click()

# 要素が消えるまで待つ(ローディング表示の終了など)
wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, "loading")))

条件が満たされた時点で先へ進むため、速く、かつ安定します。要素の存在だけを見る presence_of_element_located と、クリック可能かまで見る element_to_be_clickable は用途が違う点に注意してください。DOM上に存在していても他の要素に覆われていればクリックは失敗するので、ボタンを押す前は後者を使います。

暗黙的待機(driver.implicitly_wait())と明示的待機の併用は避けてください。Selenium公式ドキュメントも「Do not mix implicit and explicit waits」と警告しており、暗黙的待機10秒と明示的待機15秒を設定すると20秒後にタイムアウトが起きうる、という例を挙げています。どちらか一方に統一してください。実務では明示的待機を推奨します。

テスト自動化への組み込み|pytestとヘッドレス実行

ここまでの操作をテストとして回すには、driverの生成と後始末を仕組み側に寄せ、CIで画面なしに実行できる形にします。

pytestでのテスト記述とフィクスチャ

Seleniumのテストは、Python標準の unittest でも書けますが、実務ではpytestと組み合わせる構成が扱いやすくなります。driverの生成と後始末をフィクスチャに寄せると、テスト本体は検証したいことだけを書く形に整理できます。

import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

@pytest.fixture
def driver():
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--window-size=1920,1080")
    d = webdriver.Chrome(options=options)
    yield d
    d.quit()

def test_login_shows_dashboard(driver):
    driver.get("https://example.com/login")
    driver.find_element(By.ID, "username").send_keys("test_user")
    driver.find_element(By.ID, "password").send_keys("secret")
    driver.find_element(By.ID, "login").click()

    heading = driver.find_element(By.CSS_SELECTOR, "h1").text
    assert heading == "ダッシュボード"

yield の後に quit() を置けば、テストが失敗してもブラウザプロセスが残りません。テストが増えたら、ページごとの操作を Page Object Model のクラスへ切り出すと、画面変更時の修正箇所が1か所に収まります。キーワード駆動で書きたい場合は Robot Framework のSeleniumLibraryも選択肢です。

ヘッドレスモードとGitHub ActionsでのCI実行

CIでは画面を表示しないヘッドレスモードで動かします。Selenium公式ドキュメントは代表的な引数として --headless=new を挙げています。Chrome 132.0.6793.0以降では旧ヘッドレス実装が chrome-headless-shell という別バイナリに分離されたため、古い記事にある --headless=old は現行のChromeでは使えません。

GitHub Actionsで動かすなら、ブラウザを自分でインストールする必要はありません。ubuntu-24.04ランナーにはGoogle Chrome 150.0.7871.128とChromeDriver 150.0.7871.124、Firefox 152.0.6とGeckodriver 0.37.0が同梱済みです(driverのパスは環境変数 CHROMEWEBDRIVER で参照できます)。

jobs:
  e2e:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install selenium pytest
      - run: pytest -v

この構成なら、Selenium Managerによるdriverのダウンロードすら発生しません。ランナーイメージの構築時にchromedriverが /usr/bin へシンボリックリンクされ、PATH上で解決できるからです。Selenium Managerはdriverが見つからないときだけ動くフォールバックなので、この状況では起動しません。

失敗時のスクリーンショット取得

CIでテストが落ちたとき、ログの例外メッセージだけでは原因が分からないことがあります。失敗した瞬間の画面を保存しておくと、切り分けが短くなります。

import os

from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By

def test_checkout(driver):
    driver.get("https://example.com/cart")
    try:
        assert driver.find_element(By.ID, "total").text == "3,000円"
    except (AssertionError, WebDriverException):
        os.makedirs("artifacts", exist_ok=True)
        driver.save_screenshot("artifacts/checkout_failed.png")
        raise

捕捉する例外に WebDriverException を含めている点が肝心です。テストが落ちる原因は検証の失敗より、要素が見つからない NoSuchElementExceptionWebDriverException の派生クラス)であることのほうが多く、AssertionError だけを捕まえる書き方では最も撮りたい場面で画像が残りません。保存先を事前に作るのは、save_screenshot() がディレクトリ不在のとき例外ではなく False を返して静かに失敗するためです。

テスト全体へ一律に効かせるなら、conftest.pypytest_runtest_makereport フックで成否をフィクスチャから参照し、失敗時だけ撮影します。保存した画像は actions/upload-artifact で回収してください。

よく詰まるエラーと切り分け手順

Seleniumのエラーは、例外の型を見れば原因の当たりがつきます。頻出する4つです。

  • NoSuchElementException:要素が見つからない。多くは「まだ描画されていない」だけなので、まず WebDriverWait を挟みます。それでも出るなら、要素がiframe内にある可能性があります(driver.switch_to.frame() が必要)。Shadow DOM内の要素も通常の検索では取得できません(Shadow DOMのカプセル化の仕組みを参照)。
  • ElementClickInterceptedException:クリック対象が他の要素に覆われている。Cookie同意バナーや固定ヘッダーが原因の定番です。バナーを閉じるか、scrollIntoView で位置を調整します。
  • StaleElementReferenceException:取得済みの要素が、その後の再描画で無効になった。要素を変数に保持したまま画面を更新すると起こります。操作の直前に取得し直すのが確実です。
  • SessionNotCreatedException:ブラウザとdriverの組み合わせが不正。PATH上に古いdriverが残っていると、Selenium Managerではなくそちらが使われて発生します。which chromedriver で確認し、不要なら削除してください。

原因の場所が分からないときは、ヘッドレスを一時的に外して実際の画面を見るのが最短です。--headless=new の指定をコメントアウトするだけで、何が起きているか目視できます。

スクレイピング用途で守る範囲

収集の前に確認すべきは対象サイトの利用規約です。自動アクセスや複製を禁止している場合、技術的に可能かどうかとは無関係に実施できません。あわせて robots.txt のクロール可否も確認します。アクセス頻度はリクエストごとに待機を入れて抑えてください。短時間の大量アクセスは業務妨害と評価されうる行為です。取得データに個人情報が含まれる場合は、個人情報保護法上の取り扱い義務が発生します。

Seleniumで操作したブラウザは通常の操作と挙動が異なるため、CAPTCHAや「人間であることを確認してください」という画面で止まることがあります。検知を回避する行為はサイト側の防御を破ることになり、規約違反や不正アクセスの問題に直結します。検知された時点で、そのサイトは自動収集を想定していないと判断してください。公式APIの有無を先に確認するほうが、結果的に安全で安定します。

よくある質問

Seleniumを使って何ができますか?

ページを開く、リンクをクリックする、フォームに入力して送信する、スクロールする、スクリーンショットを撮る。ブラウザで人が行う操作をほぼそのままプログラム化できます。主な用途はWebアプリケーションの回帰テスト、ログインが必要なページのデータ取得、定型的な社内システム操作の自動化です。

Seleniumで使用できる言語は?

公式バインディングはJava・Python・JavaScript(Node.js)・C#・Rubyの5つです。KotlinはJVM上でJavaのバインディングをそのまま呼び出せ、公式ドキュメントにもコード例があります。どの言語もWebDriver仕様に沿ったAPIなので操作の考え方は同じです。テスト資産が既にあるなら、その言語に合わせるほうが移行コストは下がります。

Seleniumの弱点は何ですか?

対象がWebブラウザに限られる点が構造的な制約です。デスクトップアプリは操作できず、スマートフォンの実機やネイティブアプリにはAppiumのような別ツールが要ります。1台のマシンで直列に回すと本数分の時間がかかるため、規模が大きくなるとSelenium Gridによる分散実行が前提です。加えて、Seleniumは画面のレンダリング完了を自動では待たないため、待機処理の設計を誤るとテストが不安定になります。

Seleniumのインストールに必要なものは?

Python 3.10以上と pip install selenium、操作対象のブラウザ本体だけです。driverの手動ダウンロードとPATH設定は要りません。ブラウザすら入っていない環境でも、Selenium 4.11.0以降はChrome for Testingが自動で取得されます。

Selenium 4.6以降でWebDriverの設定はどう変わりましたか?

driverのパスをコードに書く必要がなくなりました。webdriver.Chrome() を引数なしで呼ぶと、Selenium Managerがブラウザのバージョンを判定して適合するdriverを取得し、~/.cache/selenium にキャッシュします。さらに4.10.0で executable_path 引数自体が削除されたため、パスを指定するなら Service オブジェクト経由になります。

関連記事

資料請求

RELATED POSTS 関連記事