0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スクレイピング要素取得がunstableになる原因と対策|AI実務ノート 編集部

Last updated at Posted at 2026-01-03

1. 結論(この記事で得られること)

スクレイピングの要素取得が不安定になる原因は、セレクタの書き方ではなく、タイミング・DOM構造・実行環境の差異にあります。

この記事を読むと以下が手に入ります:

  • 要素が取れたり取れなかったりする原因を5分で切り分ける方法
  • Playwright / Selenium / Puppeteer における待機戦略の実装パターン
  • AI(Claude / GPT)を使った最速デバッグフロー
  • 本番環境で動き続けるための監視設計とリトライロジック

私自身、3年前に EC サイトの価格監視システムで「ローカルでは動くのに本番で週2回落ちる」という地獄を経験しました。原因は「time.sleep(3)」という雑な待機処理。それ以来、unstable なスクレイピングは「待機設計の失敗」だと考えるようになりました。

2. 前提(環境・読者層)

想定読者

  • Python / JavaScript でスクレイピングを書いている
  • 「たまに動かない」レベルの不安定さに困っている
  • Selenium / Playwright / Puppeteer などのブラウザ自動化ツールを使用

検証環境

  • Python 3.10 + Playwright 1.40
  • Node.js 18 + Puppeteer 21
  • 対象:SPA(React / Vue)および従来型のサーバーサイドレンダリングサイト

3. Before:よくあるつまずきポイント

3-1. 固定時間sleep に頼る設計

# ❌ 悪い例
import time
from selenium import webdriver
 
driver = webdriver.Chrome()
driver.get("https://example.com")
time.sleep(3)  # 「だいたい3秒で読み込む」前提
element = driver.find_element(By.CSS_SELECTOR, ".price")

何が問題か?

  • ネットワーク速度や サーバー負荷で遅延が変動
  • 3秒で足りないときもあれば、無駄に待つときもある
  • CI/CD 環境やコンテナでは特に不安定

私も昔これで「本番だけたまに落ちる」を経験しました。ローカルは光回線、本番は AWS の Lambda@Edge 経由で遅延が倍違ったんですね。

3-2. 存在チェックなしでいきなりアクセス

// ❌ 悪い例
const puppeteer = require('puppeteer');
 
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
 
  // 要素が存在する前提で取得
  const text = await page.$eval('.dynamic-content', el => el.textContent);
  console.log(text);
})();

何が起きるか?

  • SPA では React の hydration 完了前に要素がない
  • 「$eval」 が 「null」 で落ちる
  • エラーログに 「Cannot read property 'textContent' of null」

3-3. セレクタの脆弱性を無視

# ❌ 脆弱なセレクタ例
driver.find_element(By.CSS_SELECTOR, "div > div > div:nth-child(3) > span")

問題点

  • サイトの HTML 構造変更で即死
  • A/B テストで DOM 順序が変わると壊れる
  • 広告挿入で nth-child がズレる

4. After:基本的な解決パターン

4-1. 明示的な待機(Explicit Wait)を使う

# ✅ 良い例:Selenium + WebDriverWait
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
 
driver = webdriver.Chrome()
driver.get("https://example.com")
 
try:
    # 最大10秒待機、要素が出現したら即次へ
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, ".price"))
    )
    print(element.text)
except TimeoutException:
    print("要素が見つかりませんでした")

ポイント

  • 「presence_of_element_located」:DOM に存在すればOK
  • 「visibility_of_element_located」:visible(display:none でない)が必要なら
  • タイムアウトは「最大待ち時間」で、早く見つかれば即座に次へ進む

4-2. Playwright の auto-waiting を活かす

# ✅ Playwright は自動で待機してくれる
from playwright.sync_api import sync_playwright
 
with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com")
 
    # デフォルトで要素が actionable になるまで待機
    price = page.locator(".price").text_content()
    print(price)
 
    browser.close()

Playwright の強み

  • 「locator()」 は自動で要素の出現 + 可視化 + 安定化を待つ
  • リトライロジックが組み込み済み
  • タイムアウトは 「timeout=10000」(ミリ秒)でカスタマイズ可能

4-3. 堅牢なセレクタ戦略

# ✅ 優先順位の高い順
# 1. data属性(一番安定)
page.locator('[data-testid="product-price"]')
 
# 2. 意味のあるクラス名
page.locator('.product-price')
 
# 3. テキスト内容(多言語対応注意)
page.locator('text="Price:"')
 
# 4. XPath(最終手段)
page.locator('xpath=//div[@class="price-container"]/span')

実務での判断基準

  • 自社サイト → data-testid を開発段階で仕込む
  • 他社サイト → class 名 + 構造の組み合わせ、定期監視必須
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?