← ブログ一覧に戻る
業務自動化

Cursor/Claude CodeでPython + Seleniumスクレイピングを効率的に開発する方法

#Cursor#Claude Code#Python#Selenium#スクレイピング#動的コンテンツ#コーディングエージェント

はじめに

JavaScriptで動的にコンテンツが生成されるWebサイトをスクレイピングする場合、通常のrequestsBeautifulSoupでは対応できません。Seleniumを使う必要がありますが、Seleniumのコードは複雑で、適切な待機処理やエラーハンドリングを実装するのは時間がかかります。

CursorClaude Codeなどのコーディングエージェントを活用することで、Seleniumスクレイピングコードの開発を大幅に効率化できます。この記事では、実際のプロジェクトで活用した経験を基に、効率的な開発方法を解説します。

Seleniumスクレイピングの課題

1. 待機処理の複雑さ

JavaScriptでレンダリングされるコンテンツは、読み込み完了まで待つ必要があります。適切な待機処理を実装しないと、要素が見つからずエラーになります。

2. エラーハンドリングの必要性

ネットワークエラー、タイムアウト、要素が見つからないなど、様々なエラーケースに対応する必要があります。

3. コードの複雑性

ブラウザの起動、要素の検索、待機処理など、多くの処理を組み合わせる必要があり、コードが複雑になりがちです。

実践例1:基本的なSeleniumスクレイピングコードの生成

プロンプト設計

効果的なプロンプトの例:

Python + Seleniumでスクレイピングコードを作成してください。

要件:
- 対象URL: https://example.com/products
- 取得したい情報: 商品名、価格、在庫数、商品画像URL
- 動的コンテンツ: JavaScriptでレンダリングされる
- 待機処理: 要素が表示されるまで最大10秒待機
- エラーハンドリング: 要素が見つからない場合はスキップして続行
- 出力: CSV形式

使用ライブラリ: selenium, pandas
ブラウザ: Chrome(ヘッドレスモード)

生成されたコード例

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import pandas as pd
import time

def setup_driver(headless: bool = True):
    """Chromeドライバーをセットアップ"""
    options = Options()
    if headless:
        options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    driver = webdriver.Chrome(options=options)
    return driver

def scrape_products(url: str) -> pd.DataFrame:
    """商品情報をスクレイピング"""
    driver = setup_driver()
    products = []
    
    try:
        driver.get(url)
        
        # 商品リストが表示されるまで待機
        wait = WebDriverWait(driver, 10)
        product_list = wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, "product-list"))
        )
        
        # 商品要素を取得
        product_elements = driver.find_elements(By.CLASS_NAME, "product-item")
        
        for product_elem in product_elements:
            try:
                # 商品名
                name_elem = product_elem.find_element(By.CLASS_NAME, "product-name")
                name = name_elem.text
                
                # 価格
                price_elem = product_elem.find_element(By.CLASS_NAME, "price")
                price = price_elem.text
                
                # 在庫数
                stock_elem = product_elem.find_element(By.CLASS_NAME, "stock")
                stock = stock_elem.text
                
                # 商品画像
                img_elem = product_elem.find_element(By.TAG_NAME, "img")
                image_url = img_elem.get_attribute("src")
                
                products.append({
                    "name": name,
                    "price": price,
                    "stock": stock,
                    "image_url": image_url
                })
                
            except NoSuchElementException as e:
                print(f"要素が見つかりませんでした: {e}")
                continue
        
        return pd.DataFrame(products)
        
    except TimeoutException:
        print("タイムアウト: ページの読み込みに時間がかかりすぎました")
        return pd.DataFrame()
    except Exception as e:
        print(f"エラーが発生しました: {e}")
        return pd.DataFrame()
    finally:
        driver.quit()

# 実行例
if __name__ == "__main__":
    df = scrape_products("https://example.com/products")
    df.to_csv("products.csv", index=False, encoding="utf-8-sig")

実践例2:待機処理の改善

Seleniumで最も重要なのは、適切な待機処理です。エージェントに改善を依頼する例です。

改善前のコード

# 問題のあるコード
driver.get(url)
product = driver.find_element(By.CLASS_NAME, "product")
# 要素がまだ読み込まれていない可能性がある

エージェントへの指示

このコードの待機処理を改善してください。

要件:
- 要素が表示されるまで待機(最大10秒)
- 複数の待機方法を組み合わせる(presence, visibility, clickable)
- タイムアウト時は適切なエラーメッセージを出力
- スクリーンショットを保存

改善後のコード

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

class SafeScraper:
    def __init__(self, driver, timeout: int = 10):
        self.driver = driver
        self.wait = WebDriverWait(driver, timeout)
    
    def wait_for_element(self, by: By, value: str, wait_type: str = "presence"):
        """安全に要素を待機・取得"""
        try:
            if wait_type == "presence":
                condition = EC.presence_of_element_located((by, value))
            elif wait_type == "visible":
                condition = EC.visibility_of_element_located((by, value))
            elif wait_type == "clickable":
                condition = EC.element_to_be_clickable((by, value))
            else:
                condition = EC.presence_of_element_located((by, value))
            
            element = self.wait.until(condition)
            return element
        except TimeoutException:
            # タイムアウト時のスクリーンショット
            screenshot_path = f"timeout-{int(time.time())}.png"
            self.driver.save_screenshot(screenshot_path)
            raise TimeoutException(
                f"要素が見つかりませんでした: {by}={value} "
                f"(スクリーンショット: {screenshot_path})"
            )
    
    def safe_find_element(self, by: By, value: str, default=None):
        """安全に要素を取得(見つからない場合はデフォルト値を返す)"""
        try:
            return self.wait_for_element(by, value)
        except TimeoutException:
            if default is not None:
                return default
            raise

# 使用例
scraper = SafeScraper(driver)
try:
    product = scraper.wait_for_element(By.CLASS_NAME, "product", wait_type="visible")
    # 要素が見つかった後の処理
except TimeoutException as e:
    print(f"エラー: {e}")

実践例3:ページネーション対応

複数ページにわたるデータを取得する場合のコード生成例です。

エージェントへの指示

Seleniumでページネーション対応のスクレイピングコードを作成してください。

要件:
- 最初のページから最後のページまで自動で巡回
- 各ページで商品情報を取得
- 次のページボタンが存在するかチェック
- 最大ページ数を設定(無限ループ防止)
- 各ページ間で適切な待機時間を設定

生成されたコード例

def scrape_all_pages(base_url: str, max_pages: int = 10) -> pd.DataFrame:
    """全ページをスクレイピング"""
    driver = setup_driver()
    all_products = []
    
    try:
        page_num = 1
        while page_num <= max_pages:
            url = f"{base_url}?page={page_num}"
            driver.get(url)
            
            wait = WebDriverWait(driver, 10)
            
            # 商品リストが表示されるまで待機
            wait.until(EC.presence_of_element_located((By.CLASS_NAME, "product-list")))
            
            # 商品情報を取得
            products = scrape_products_from_page(driver)
            all_products.extend(products)
            
            # 次のページボタンが存在するかチェック
            try:
                next_button = driver.find_element(By.CSS_SELECTOR, ".pagination .next")
                if not next_button.is_enabled():
                    print(f"最後のページに到達しました(ページ {page_num})")
                    break
            except NoSuchElementException:
                print(f"次のページボタンが見つかりません(ページ {page_num})")
                break
            
            page_num += 1
            time.sleep(2)  # ページ間の待機
        
        return pd.DataFrame(all_products)
        
    finally:
        driver.quit()

def scrape_products_from_page(driver) -> list:
    """1ページ分の商品情報を取得"""
    products = []
    product_elements = driver.find_elements(By.CLASS_NAME, "product-item")
    
    for product_elem in product_elements:
        try:
            name = product_elem.find_element(By.CLASS_NAME, "product-name").text
            price = product_elem.find_element(By.CLASS_NAME, "price").text
            products.append({"name": name, "price": price})
        except NoSuchElementException:
            continue
    
    return products

実践例4:エラーハンドリングの強化

様々なエラーケースに対応した堅牢なコードを生成する例です。

エージェントへの指示

このSeleniumスクレイピングコードのエラーハンドリングを強化してください。

要件:
- ネットワークエラー: リトライ3回まで
- タイムアウト: スクリーンショットを保存してログ出力
- 要素が見つからない: スキップして続行
- ブラウザクラッシュ: 自動再起動
- すべてのエラーをログファイルに記録

改善後のコード例

import logging
from typing import Optional
from selenium.common.exceptions import (
    TimeoutException, 
    NoSuchElementException,
    WebDriverException
)

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('scraping.log'),
        logging.StreamHandler()
    ]
)

class RobustScraper:
    def __init__(self, max_retries: int = 3):
        self.max_retries = max_retries
        self.driver: Optional[webdriver.Chrome] = None
    
    def _setup_driver(self):
        """ドライバーをセットアップ"""
        if self.driver:
            try:
                self.driver.quit()
            except:
                pass
        
        options = Options()
        options.add_argument('--headless')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        self.driver = webdriver.Chrome(options=options)
    
    def scrape_with_retry(self, url: str) -> pd.DataFrame:
        """リトライ付きスクレイピング"""
        for attempt in range(self.max_retries):
            try:
                if not self.driver:
                    self._setup_driver()
                
                return self._scrape_page(url)
                
            except (TimeoutException, WebDriverException) as e:
                logging.warning(f"試行 {attempt + 1}/{self.max_retries} 失敗: {e}")
                
                if attempt < self.max_retries - 1:
                    # スクリーンショットを保存
                    try:
                        screenshot_path = f"error-{attempt + 1}.png"
                        self.driver.save_screenshot(screenshot_path)
                        logging.info(f"スクリーンショット保存: {screenshot_path}")
                    except:
                        pass
                    
                    # ドライバーを再起動
                    self._setup_driver()
                    time.sleep(2 ** attempt)  # 指数バックオフ
                else:
                    logging.error(f"すべての試行が失敗しました: {url}")
                    raise
            except Exception as e:
                logging.error(f"予期しないエラー: {e}")
                raise
        
        return pd.DataFrame()
    
    def _scrape_page(self, url: str) -> pd.DataFrame:
        """実際のスクレイピング処理"""
        self.driver.get(url)
        wait = WebDriverWait(self.driver, 10)
        
        # 要素の待機
        wait.until(EC.presence_of_element_located((By.CLASS_NAME, "product-list")))
        
        products = []
        product_elements = self.driver.find_elements(By.CLASS_NAME, "product-item")
        
        for product_elem in product_elements:
            try:
                product = self._extract_product_info(product_elem)
                if product:
                    products.append(product)
            except NoSuchElementException as e:
                logging.warning(f"商品情報の取得に失敗: {e}")
                continue
        
        return pd.DataFrame(products)
    
    def _extract_product_info(self, element) -> Optional[dict]:
        """商品情報を抽出"""
        try:
            name = element.find_element(By.CLASS_NAME, "product-name").text
            price = element.find_element(By.CLASS_NAME, "price").text
            return {"name": name, "price": price}
        except NoSuchElementException:
            return None
    
    def __del__(self):
        """クリーンアップ"""
        if self.driver:
            try:
                self.driver.quit()
            except:
                pass

コーディングエージェント活用のベストプラクティス

1. 段階的な開発

  1. 基本的なページアクセスと要素取得
  2. 待機処理の追加
  3. エラーハンドリングの実装
  4. ページネーション対応
  5. ログ出力と通知

2. プロンプトの改善

最初のプロンプトで完璧なコードが生成されなくても、結果を見てプロンプトを改善します。

# 最初のプロンプト
Seleniumでスクレイピングコードを書いて

# 改善後
Python + Seleniumで動的Webサイトのスクレイピングコードを作成してください。
- 対象URL: https://example.com
- 取得データ: 商品名、価格、在庫
- 待機処理: 要素が表示されるまで最大10秒
- エラーハンドリング: リトライ3回、スクリーンショット保存
- 出力: CSV形式

3. コードレビューのポイント

  • 待機処理: 適切な待機が設定されているか
  • エラーハンドリング: 想定外のエラーに対応できているか
  • パフォーマンス: 不要な待機がないか
  • リソース管理: ドライバーが適切に終了されているか

注意点と制限事項

Seleniumスクレイピングの注意点

  1. robots.txtの確認: スクレイピングが許可されているか
  2. 利用規約の遵守: サイトの利用規約を確認
  3. 適切な待機時間: サーバーへの負荷を考慮
  4. リソース管理: ブラウザのメモリ使用量に注意

エージェントの限界

  • 最新のSelenium API: 最新機能を理解していない場合がある
  • サイト固有の動作: 特殊な動作は理解できない
  • パフォーマンス: 最適化が必要な場合がある

まとめ

CursorやClaude Codeなどのコーディングエージェントを活用することで、Seleniumスクレイピングコードの開発を大幅に効率化できます。

効果的な活用方法:

  • 具体的で構造化されたプロンプトを使用
  • 段階的に開発を進める
  • 生成されたコードを必ずレビュー・改善
  • 待機処理とエラーハンドリングを重視

改善前:

  • Seleniumコードの作成に数時間かかる
  • 待機処理の実装が複雑
  • エラーハンドリングが不十分

改善後:

  • エージェントの支援で開発時間を短縮
  • 適切な待機処理が自動生成
  • 堅牢なエラーハンドリングが実装

コーディングエージェントは、Seleniumスクレイピング開発の「強力なパートナー」として活用することで、より効率的で保守性の高いコードを実現できます。


関連リンク: