Cursor/Claude CodeでPython + Seleniumスクレイピングを効率的に開発する方法
はじめに
JavaScriptで動的にコンテンツが生成されるWebサイトをスクレイピングする場合、通常のrequestsとBeautifulSoupでは対応できません。Seleniumを使う必要がありますが、Seleniumのコードは複雑で、適切な待機処理やエラーハンドリングを実装するのは時間がかかります。
CursorやClaude 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. 段階的な開発
- 基本的なページアクセスと要素取得
- 待機処理の追加
- エラーハンドリングの実装
- ページネーション対応
- ログ出力と通知
2. プロンプトの改善
最初のプロンプトで完璧なコードが生成されなくても、結果を見てプロンプトを改善します。
# 最初のプロンプト
Seleniumでスクレイピングコードを書いて
# 改善後
Python + Seleniumで動的Webサイトのスクレイピングコードを作成してください。
- 対象URL: https://example.com
- 取得データ: 商品名、価格、在庫
- 待機処理: 要素が表示されるまで最大10秒
- エラーハンドリング: リトライ3回、スクリーンショット保存
- 出力: CSV形式
3. コードレビューのポイント
- 待機処理: 適切な待機が設定されているか
- エラーハンドリング: 想定外のエラーに対応できているか
- パフォーマンス: 不要な待機がないか
- リソース管理: ドライバーが適切に終了されているか
注意点と制限事項
Seleniumスクレイピングの注意点
- robots.txtの確認: スクレイピングが許可されているか
- 利用規約の遵守: サイトの利用規約を確認
- 適切な待機時間: サーバーへの負荷を考慮
- リソース管理: ブラウザのメモリ使用量に注意
エージェントの限界
- 最新のSelenium API: 最新機能を理解していない場合がある
- サイト固有の動作: 特殊な動作は理解できない
- パフォーマンス: 最適化が必要な場合がある
まとめ
CursorやClaude Codeなどのコーディングエージェントを活用することで、Seleniumスクレイピングコードの開発を大幅に効率化できます。
効果的な活用方法:
- 具体的で構造化されたプロンプトを使用
- 段階的に開発を進める
- 生成されたコードを必ずレビュー・改善
- 待機処理とエラーハンドリングを重視
改善前:
- Seleniumコードの作成に数時間かかる
- 待機処理の実装が複雑
- エラーハンドリングが不十分
改善後:
- エージェントの支援で開発時間を短縮
- 適切な待機処理が自動生成
- 堅牢なエラーハンドリングが実装
コーディングエージェントは、Seleniumスクレイピング開発の「強力なパートナー」として活用することで、より効率的で保守性の高いコードを実現できます。
関連リンク: