Python Selenium完全ガイド – Web自動化・動的サイトスクレイピングの決定版

 

Seleniumは、Webブラウザを自動操作するための強力なツールです。JavaScriptで動的に生成されるコンテンツのスクレイピング、Webアプリケーションのテスト自動化、繰り返し作業の自動化など、幅広い用途で活用されています。この記事では、Python SeleniumとWebDriverの基本から高度なテクニックまで、実践的なサンプルコードとともに詳しく解説します。

Seleniumとは

Seleniumは、Webブラウザを自動制御するためのフレームワークです。実際のブラウザを操作するため、JavaScriptによる動的コンテンツも取得でき、ユーザーの操作を完全に再現できます。

主な特徴

  • 実ブラウザ操作: Chrome、Firefox、Safari、Edgeなどをサポート
  • JavaScript対応: 動的に生成されるコンテンツも取得可能
  • 多様な操作: クリック、入力、スクロール、スクリーンショットなど
  • 待機機能: 要素の読み込み完了まで自動待機

インストールと環境設定

ライブラリのインストール

pip install selenium webdriver-manager

WebDriverの自動管理

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager

driver = webdriver.Chrome(ChromeDriverManager().install())

基本的なセットアップ

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

# ブラウザの起動
driver = webdriver.Chrome()
driver.get("https://example.com")
print(driver.title)
driver.quit()

基本的な操作

ページの読み込み

driver = webdriver.Chrome()
driver.get("https://www.google.com")
print(f"現在のURL: {driver.current_url}")
print(f"ページタイトル: {driver.title}")

要素の検索

# IDで検索
element = driver.find_element(By.ID, "search-box")

# クラス名で検索
element = driver.find_element(By.CLASS_NAME, "button")

# CSS セレクタで検索
element = driver.find_element(By.CSS_SELECTOR, "div.content > p")

# XPathで検索
element = driver.find_element(By.XPATH, "//input[@type='text']")

複数要素の検索

# すべてのリンクを取得
links = driver.find_elements(By.TAG_NAME, "a")
print(f"リンク数: {len(links)}")

for link in links[:5]:
    print(f"テキスト: {link.text}, URL: {link.get_attribute('href')}")

フォーム操作

テキスト入力

search_box = driver.find_element(By.NAME, "q")
search_box.clear()
search_box.send_keys("Python Selenium")

ボタンのクリック

search_button = driver.find_element(By.NAME, "btnK")
search_button.click()

セレクトボックスの操作

from selenium.webdriver.support.ui import Select

select_element = driver.find_element(By.ID, "dropdown")
select = Select(select_element)

# インデックスで選択
select.select_by_index(1)
# 値で選択
select.select_by_value("option2")
# 表示テキストで選択
select.select_by_visible_text("オプション3")

チェックボックス・ラジオボタン

checkbox = driver.find_element(By.ID, "agree")
if not checkbox.is_selected():
    checkbox.click()

radio_button = driver.find_element(By.NAME, "gender")
radio_button.click()

待機処理

明示的待機

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 要素が表示されるまで最大10秒待機
wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((By.ID, "dynamic-content")))

よく使用される待機条件

# 要素がクリック可能になるまで待機
clickable = wait.until(EC.element_to_be_clickable((By.ID, "submit-btn")))

# テキストが含まれるまで待機
text_present = wait.until(EC.text_to_be_present_in_element((By.ID, "status"), "完了"))

# 要素が非表示になるまで待機
invisible = wait.until(EC.invisibility_of_element_located((By.ID, "loading")))

暗黙的待機

driver.implicitly_wait(10)  # 10秒まで要素の出現を待機
element = driver.find_element(By.ID, "delayed-element")

ブラウザオプション

Chrome オプション

from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')          # ヘッドレスモード
options.add_argument('--no-sandbox')        # サンドボックス無効
options.add_argument('--disable-dev-shm-usage')  # /dev/shm使用無効
options.add_argument('--window-size=1920,1080')  # ウィンドウサイズ

driver = webdriver.Chrome(options=options)

Firefox オプション

from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument('--headless')
driver = webdriver.Firefox(options=options)

User-Agentの設定

options = Options()
options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
driver = webdriver.Chrome(options=options)

JavaScript の実行

基本的な JavaScript 実行

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

# 要素を強制的にクリック
element = driver.find_element(By.ID, "hidden-button")
driver.execute_script("arguments[0].click();", element)

戻り値のある JavaScript

# ページの高さを取得
height = driver.execute_script("return document.body.scrollHeight;")
print(f"ページ高さ: {height}px")

# ローカルストレージの操作
driver.execute_script("localStorage.setItem('key', 'value');")
value = driver.execute_script("return localStorage.getItem('key');")

非同期 JavaScript の実行

# 非同期処理の実行
script = """
var callback = arguments[arguments.length - 1];
setTimeout(function() {
    callback('非同期処理完了');
}, 2000);
"""
result = driver.execute_async_script(script)
print(result)  # 非同期処理完了

ウィンドウとタブの管理

新しいタブの開始

# 新しいタブを開く
driver.execute_script("window.open('');")
driver.switch_to.window(driver.window_handles[1])
driver.get("https://example.com")

タブの切り替え

# 最初のタブに戻る
driver.switch_to.window(driver.window_handles[0])

# すべてのタブを閉じる
for handle in driver.window_handles[1:]:
    driver.switch_to.window(handle)
    driver.close()
driver.switch_to.window(driver.window_handles[0])

ウィンドウサイズの操作

# ウィンドウサイズの変更
driver.set_window_size(1200, 800)

# ウィンドウの最大化
driver.maximize_window()

# 現在のウィンドウサイズを取得
size = driver.get_window_size()
print(f"幅: {size['width']}, 高さ: {size['height']}")

アラートとポップアップの処理

アラートの処理

from selenium.webdriver.support.ui import Alert

# アラートが表示されるまで待機
wait.until(EC.alert_is_present())
alert = Alert(driver)

print(f"アラートテキスト: {alert.text}")
alert.accept()  # OKボタンをクリック
# alert.dismiss()  # キャンセルボタンをクリック

確認ダイアログの処理

# 確認ダイアログの処理
alert = Alert(driver)
alert.accept()    # OK
# alert.dismiss()  # キャンセル

プロンプトダイアログの処理

alert = Alert(driver)
alert.send_keys("入力テキスト")
alert.accept()

フレームとiframeの操作

フレームの切り替え

# iframe に切り替え
iframe = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe)

# iframe 内の要素を操作
inner_element = driver.find_element(By.ID, "inner-content")

# メインコンテンツに戻る
driver.switch_to.default_content()

フレーム名での切り替え

# フレーム名で切り替え
driver.switch_to.frame("frame-name")

# フレームインデックスで切り替え
driver.switch_to.frame(0)

ファイルのアップロードとダウンロード

ファイルアップロード

file_input = driver.find_element(By.TYPE, "file")
file_input.send_keys("/path/to/file.txt")

upload_button = driver.find_element(By.ID, "upload-btn")
upload_button.click()

ダウンロード設定

import os

download_dir = os.path.join(os.getcwd(), "downloads")
options = Options()
prefs = {
    "download.default_directory": download_dir,
    "download.prompt_for_download": False,
    "directory_upgrade": True,
    "safebrowsing.enabled": True
}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)

スクリーンショットとページソース

スクリーンショットの撮影

# ページ全体のスクリーンショット
driver.save_screenshot("screenshot.png")

# 特定要素のスクリーンショット
element = driver.find_element(By.ID, "content")
element.screenshot("element.png")

ページソースの取得

# 現在のページソースを取得
page_source = driver.page_source
print(f"ページソース長: {len(page_source)}")

# ファイルに保存
with open("page_source.html", "w", encoding="utf-8") as f:
    f.write(page_source)

実践的なスクレイピング例

1. 動的検索結果の取得

def scrape_search_results(query):
    driver = webdriver.Chrome()
    driver.get("https://example-search.com")
    
    # 検索実行
    search_box = driver.find_element(By.NAME, "q")
    search_box.send_keys(query)
    search_box.submit()
    
    # 結果の待機と取得
    wait = WebDriverWait(driver, 10)
    results = wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "result")))
    
    data = []
    for result in results[:10]:
        title = result.find_element(By.TAG_NAME, "h3").text
        link = result.find_element(By.TAG_NAME, "a").get_attribute("href")
        data.append({"title": title, "link": link})
    
    driver.quit()
    return data

2. 無限スクロールページの処理

def scrape_infinite_scroll(url):
    driver = webdriver.Chrome()
    driver.get(url)
    
    last_height = driver.execute_script("return document.body.scrollHeight")
    
    while True:
        # 最下部までスクロール
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        
        # 新しいコンテンツの読み込み待機
        time.sleep(2)
        
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height
    
    # データ取得
    items = driver.find_elements(By.CLASS_NAME, "item")
    data = [item.text for item in items]
    
    driver.quit()
    return data

3. ログインが必要なサイトのスクレイピング

def scrape_with_login(username, password):
    driver = webdriver.Chrome()
    driver.get("https://example.com/login")
    
    # ログイン処理
    driver.find_element(By.NAME, "username").send_keys(username)
    driver.find_element(By.NAME, "password").send_keys(password)
    driver.find_element(By.TYPE, "submit").click()
    
    # ログイン完了を待機
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_element_located((By.ID, "user-dashboard")))
    
    # プロテクトされたページにアクセス
    driver.get("https://example.com/protected-page")
    
    # データ取得
    protected_data = driver.find_element(By.ID, "protected-content").text
    
    driver.quit()
    return protected_data

4. フォーム送信の自動化

def automate_form_submission(form_data):
    driver = webdriver.Chrome()
    driver.get("https://example.com/form")
    
    # フォーム入力
    for field_name, value in form_data.items():
        element = driver.find_element(By.NAME, field_name)
        if element.tag_name == "select":
            Select(element).select_by_visible_text(value)
        else:
            element.clear()
            element.send_keys(value)
    
    # 送信
    driver.find_element(By.TYPE, "submit").click()
    
    # 結果の確認
    wait = WebDriverWait(driver, 10)
    success_message = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "success")))
    result = success_message.text
    
    driver.quit()
    return result

パフォーマンス最適化

ヘッドレスモードの活用

def create_headless_driver():
    options = Options()
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument('--disable-images')  # 画像読み込み無効
    options.add_argument('--disable-javascript')  # JS無効(必要に応じて)
    
    return webdriver.Chrome(options=options)

ページロード戦略

options = Options()
options.page_load_strategy = 'eager'  # DOM完了で続行(デフォルト: 'normal')
driver = webdriver.Chrome(options=options)

並行処理

from concurrent.futures import ThreadPoolExecutor
import threading

class ThreadSafeDriver:
    _local = threading.local()
    
    @classmethod
    def get_driver(cls):
        if not hasattr(cls._local, 'driver'):
            cls._local.driver = webdriver.Chrome()
        return cls._local.driver

def scrape_url(url):
    driver = ThreadSafeDriver.get_driver()
    driver.get(url)
    return driver.title

# 並行実行
urls = ["https://example1.com", "https://example2.com", "https://example3.com"]
with ThreadPoolExecutor(max_workers=3) as executor:
    results = list(executor.map(scrape_url, urls))

エラーハンドリング

基本的な例外処理

from selenium.common.exceptions import NoSuchElementException, TimeoutException

def safe_find_element(driver, by, value):
    try:
        element = driver.find_element(by, value)
        return element
    except NoSuchElementException:
        print(f"要素が見つかりません: {value}")
        return None

def safe_click(driver, by, value):
    try:
        element = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((by, value))
        )
        element.click()
        return True
    except TimeoutException:
        print(f"要素がクリックできません: {value}")
        return False

リトライ機能付きスクレイピング

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"試行 {attempt + 1} 失敗: {e}")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def scrape_with_retry(url):
    driver = webdriver.Chrome()
    driver.get(url)
    title = driver.title
    driver.quit()
    return title

テスト自動化

単体テストとの連携

import unittest

class WebTestCase(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(10)
    
    def tearDown(self):
        self.driver.quit()
    
    def test_login_functionality(self):
        self.driver.get("https://example.com/login")
        
        username_field = self.driver.find_element(By.NAME, "username")
        password_field = self.driver.find_element(By.NAME, "password")
        login_button = self.driver.find_element(By.TYPE, "submit")
        
        username_field.send_keys("testuser")
        password_field.send_keys("password123")
        login_button.click()
        
        # ログイン成功の確認
        welcome_message = self.driver.find_element(By.ID, "welcome")
        self.assertIn("ようこそ", welcome_message.text)

if __name__ == "__main__":
    unittest.main()

Page Object Model

class LoginPage:
    def __init__(self, driver):
        self.driver = driver
        self.username_field = (By.NAME, "username")
        self.password_field = (By.NAME, "password")
        self.login_button = (By.TYPE, "submit")
    
    def login(self, username, password):
        self.driver.find_element(*self.username_field).send_keys(username)
        self.driver.find_element(*self.password_field).send_keys(password)
        self.driver.find_element(*self.login_button).click()

class DashboardPage:
    def __init__(self, driver):
        self.driver = driver
        self.welcome_message = (By.ID, "welcome")
    
    def get_welcome_text(self):
        return self.driver.find_element(*self.welcome_message).text

# 使用例
driver = webdriver.Chrome()
driver.get("https://example.com/login")

login_page = LoginPage(driver)
login_page.login("testuser", "password123")

dashboard_page = DashboardPage(driver)
welcome_text = dashboard_page.get_welcome_text()

デバッグとトラブルシューティング

デバッグ用の便利な関数

def debug_element_info(driver, by, value):
    try:
        element = driver.find_element(by, value)
        print(f"要素が見つかりました: {element.tag_name}")
        print(f"テキスト: {element.text}")
        print(f"表示状態: {element.is_displayed()}")
        print(f"有効状態: {element.is_enabled()}")
        print(f"位置: {element.location}")
        print(f"サイズ: {element.size}")
    except NoSuchElementException:
        print(f"要素が見つかりません: {value}")

def debug_page_info(driver):
    print(f"現在のURL: {driver.current_url}")
    print(f"ページタイトル: {driver.title}")
    print(f"ウィンドウサイズ: {driver.get_window_size()}")
    print(f"ページソース長: {len(driver.page_source)}")

ログ設定

import logging
from selenium.webdriver.remote.remote_connection import LOGGER

# Seleniumのログレベルを設定
LOGGER.setLevel(logging.WARNING)

# カスタムログ設定
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('selenium.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

セキュリティとベストプラクティス

検出回避テクニック

def create_stealth_driver():
    options = Options()
    options.add_argument('--disable-blink-features=AutomationControlled')
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    
    driver = webdriver.Chrome(options=options)
    driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
    
    return driver

人間らしい操作の実装

import random
from selenium.webdriver.common.action_chains import ActionChains

def human_like_typing(element, text, min_delay=0.05, max_delay=0.2):
    for char in text:
        element.send_keys(char)
        time.sleep(random.uniform(min_delay, max_delay))

def human_like_click(driver, element):
    actions = ActionChains(driver)
    actions.move_to_element(element)
    actions.pause(random.uniform(0.5, 1.5))
    actions.click()
    actions.perform()

プロキシの使用

def create_proxy_driver(proxy_host, proxy_port):
    options = Options()
    options.add_argument(f'--proxy-server=http://{proxy_host}:{proxy_port}')
    return webdriver.Chrome(options=options)

まとめ

Seleniumは、動的なWebサイトの操作やテスト自動化において非常に強力なツールです。JavaScript重要のSPAサイトやログインが必要なサイトでも、実際のブラウザを操作することで確実にデータを取得できます。

主なポイント:

  • 動的コンテンツ対応: JavaScriptで生成されるコンテンツも取得可能
  • 豊富な操作: クリック、入力、スクロール、ファイル操作など
  • 待機機能: 明示的・暗黙的待機で安定した動作
  • ブラウザオプション: ヘッドレスモード、プロキシ設定など
  • エラーハンドリング: 堅牢なスクリーピングのための例外処理
  • パフォーマンス最適化: 並行処理、設定調整による高速化

Seleniumを使用する際は、対象サイトの利用規約を遵守し、サーバーに過度な負荷をかけないよう注意することが重要です。また、APIが提供されている場合は、SeleniumよりもAPIの使用を優先することをお勧めします。

参考リンク

適切な知識とマナーを持って、効果的なWeb自動化を実践しましょう。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座