背景:セレクター保守の悪夢
BeautifulSoupやSeleniumを使ってECサイトから10,000件の商品データを抽出するクローラーを構築したとします。しかし翌朝、パイプライン全体が停止してしまいます。原因はお馴染みの問題です。フロントエンドチームがUIをリビルドし、クラス名がclass="product-price"からclass="_2rK9a"のようなランダムなハッシュ文字列に変更されてしまったのです。
XPathやCSSセレクターを手動で調査・更新する作業は、定期的なクローラー保守時間の30%〜50%を消費します。これこそが、従来のデータ抽出手法における根深い弱点です。
ScrapeGraphAIはこの課題を根本から解決します。固定されたDOMツリーに依存する代わりに、グラフ処理パイプライン(Graph pipeline)と大規模言語モデル(LLM)を活用します。LLMは人間の目のようにWebページのコンテキストを理解するため、URLと抽出したいデータを指定するプロンプトを渡すだけで、ScrapeGraphAIが自動でページを解析し、正確なJSONを返してくれます。
環境構築
ScrapeGraphAIはPython 3.10以上を必要とします。ライブラリの競合を防ぐため、仮想環境(virtualenv)での実行を推奨します:
# 仮想環境の作成と有効化
python3 -m venv venv
source venv/bin/activate
# ScrapeGraphAIと動的JSレンダリング用Playwrightのインストール
pip install scrapegraphai playwright
# ブラウザバイナリのダウンロード
playwright install
このライブラリは、OpenAI、Google Gemini、Groq、Azure OpenAI、またはOllama経由で実行するローカルモデルなど、多数のLLMバックエンドを標準でサポートしています。
実践的なグラフパイプラインの設定
ScrapeGraphAIの中核は、事前定義されたグラフパイプラインです。最も代表的な2つのクラスとして、SmartScraperGraph(単一ページ抽出)とSearchGraph(検索エンジン連携)があります。
1. SmartScraperGraphとGPT-4o-miniによるデータ抽出
以下の例では、テクノロジーニュースサイトから1リクエストあたりわずか約0.002ドルのコストで記事一覧を抽出します:
import json
import os
from scrapegraphai.graphs import SmartScraperGraph
graph_config = {
"llm": {
"api_key": os.getenv("OPENAI_API_KEY"),
"model": "openai/gpt-4o-mini",
"temperature": 0,
},
"headless": True,
"verbose": False,
}
prompt = """
ページ上の記事リストから以下の情報を抽出してください:
- title: 記事のタイトル(string)
- author: 著者名(string、存在しない場合はnull)
- points: アップボート数(integer)
- comments_count: コメント数(integer)
"""
smart_scraper = SmartScraperGraph(
prompt=prompt,
source="https://news.ycombinator.com",
config=graph_config
)
result = smart_scraper.run()
print(json.dumps(result, indent=2, ensure_ascii=False))
2. OllamaによるローカルLLMの実行(完全なプライバシー保護、APIコスト0円)
社内データをスクレイピングする場合や、1日数十万ページ規模で処理する場合は、ローカルのOllamaクラスターに接続します:
from scrapegraphai.graphs import SmartScraperGraph
local_config = {
"llm": {
"model": "ollama/qwen2.5:7b",
"base_url": "http://localhost:11434",
"temperature": 0,
},
"embeddings": {
"model": "ollama/nomic-embed-text",
"base_url": "http://localhost:11434",
},
"headless": True
}
scraper = SmartScraperGraph(
prompt="商品名、通常価格、セール価格を取得してください",
source="https://example-shop.com/flash-sale",
config=local_config
)
data = scraper.run()
3. トークン最適化とコスト削減
ECサイトの生のHTMLは通常2MB〜5MBものサイズがあり、無数のSVGタグやインラインCSS、不要なスクリプトが含まれています。このHTMLをそのままLLMに投入すると、大量のトークンを消費しレイテンシが増加します。
ScrapeGraphAIはLLMに送信する前にHTMLを自動的にパース・圧縮します。さらに、ページのDOM構造が非常に大きい場合は、設定でmax_tokensを制限し、chunk_sizeを調整することをお勧めします。
本番環境へのデプロイとモニタリング
スクレイパーをCeleryワーカーやKubernetes CronJobに組み込む際は、適切な例外処理とトークン消費量のモニタリングが不可欠です。
1. リトライおよびフォールバック機構の設計
ネットワークエラーや429 Rate Limitによってバッチジョブ全体がクラッシュしないよう、実行ロジックをリトライ関数でラップします:
import logging
import time
from scrapegraphai.graphs import SmartScraperGraph
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
def fetch_with_retry(url: str, prompt: str, config: dict, max_retries: int = 3, backoff_factor: int = 2):
for attempt in range(1, max_retries + 1):
try:
logger.info(f"スクレイピング中: {url}(試行 {attempt}/{max_retries})")
scraper = SmartScraperGraph(prompt=prompt, source=url, config=config)
output = scraper.run()
if output:
return output
except Exception as err:
logger.warning(f"試行 {attempt} でエラー発生: {err}")
if attempt == max_retries:
logger.error(f"{max_retries} 回の試行後に完全失敗: {url}")
raise
time.sleep(backoff_factor ** attempt)
return None
2. トークン使用量の監視
get_execution_info()関数を使用すると、入力/出力トークン数や各ノードのレイテンシの詳細を取得できます。このメトリクスをPrometheusやGrafanaに送信して、リアルタイムでAPIコスト予算を管理することをお勧めします:
execution_info = smart_scraper.get_execution_info()
logger.info(f"Total tokens used: {execution_info.get('total_tokens', 0)}")
logger.info(f"Execution time: {execution_info.get('execution_time', 0):.2f}s")
3. 運用における3つの重要ポイント
- Anti-bot対策: Cloudflareなどが有効なサイトをスクレイピングする際は、生のHTTPリクエストを直接送信するのではなく、Playwright設定でローテーションプロキシプールを構成してください。
- 構造化プロンプト: モデルが一貫した出力を返し、バックエンドでのフォーマットエラーを防ぐため、プロンプト内のJSONキー名には常に英語(例:
price、sku、stock_status)を使用してください。 - ハイブリッドアーキテクチャ: 構造変更の少ない静的ページの90%には、軽量なパーサー(ScrapyやSelectolax)を使用して100 req/sの高速処理を実現します。ScrapeGraphAIは、従来のパーサーが空データを返した際のフォールバック層としてのみトリガーするのが最適です。

