scraper-builder について
ウェブスクレイパーの設計と実装を支援する Bright Data のスキルです。対象サイトの分析から実行可能なコードの作成まで扱い、ページ構造や操作要件に応じて既成スクレイパーや API を選び、サンプルで検証します。
主な機能
- 01既成スクレイパーの有無を確認
- 02ページ構造とデータ形式を分析
- 03データ抽出 API を選択
- 04実行可能なスクレイパーコードを作成
- 05ページネーションと一括処理に対応
- 06少量のサンプルで結果を検証
活用シーン
- 01対象サイトからのデータ抽出
- 02JavaScript 描画や操作が必要なページの取得
- 03ページ分割された結果の収集
- 04規模拡大前の抽出テスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ brightdata/ skills --skill scraper-builderインストール後、Claude Code で/scraper-builderを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/concurrency-guide.mdpagination-patterns.mdsite-analysis-guide.mdsupported-domains.md詳細
スクレイパービルダー
ユーザー向けに、本番環境で使えるウェブスクレイパーを構築します。サイト X からデータを取得したい、という要望から、ページネーション、動的コンテンツ、ボット対策、データ解析など、実際の環境で生じる課題に対応できる、堅牢なスクレイパーの完成までユーザーを導きます。
重要:出力は必ず検証してください
スクレイパーを構築したら、必ず少量のサンプル(1~3 ページ)で実行し、抽出したデータをユーザーに見せてください。出力が空、形式が不正、またはフィールドが欠けている場合は、セレクターの修正、API の切り替え、解析ロジックの調整を繰り返してください。きれいなデータを出力できないスクレイパーは、完成していません。
事前調査には十分な時間をかけてください。事前に HTML を 2 分間分析すれば、後で何時間もデバッグする事態を防げます。ここでは速度より品質が重要です。
このスキルの仕組み
このスキルでは、Bright Data の 4 つの API を連携させて、最適なスクレイパーを構築します。壊れやすいカスタムのスクレイピングコードを書くのではなく、まず対象サイトを分析し、最も信頼性が高く費用対効果に優れた抽出方法を選びます。判断基準は次のとおりです。
- 構築済みのスクレイパーがあるか? → Web Scraper API を使用します(解析コードは不要です)
- ページが静的、または操作が不要か? → Web Unlocker API を使用します(最も低コストでシンプルです)
- ページでクリック、スクロール、または JavaScript の操作が必要か? → Browser API を使用します(完全な自動化が可能です)
- 検索エンジンの検索結果が必要か? → SERP API を使用します
このスキルでは、疑似コードや概要ではなく、完全に実行できるコードを作成します。
フェーズ 1:対象を把握する
コードを書く前に、ユーザーが何を求めているのか、サイトがどのような構成なのかを把握する必要があります。次の質問をしてください(ユーザーがすでに回答済みの項目は省略します)。
- 対象サイトは? — 対象 URL またはドメイン
- 必要なデータは? — 必要なフィールド(商品名、価格、レビューなど)
- 対象範囲は? — 単一ページ、カテゴリページ、検索結果、サイト全体のセクション?
- ページネーションは必要か? — 複数ページにわたるスクレイピングが必要か?
- データ量は? — おおよそのアイテム数/ページ数(同期か非同期かの選択と同時実行戦略に影響します。references/concurrency-guide.md を参照してください)
- 出力形式は? — JSON、CSV、データベース?(指定がなければ JSON を既定とします)
- 希望する言語は? — Python または Node.js?(指定がなければ Python を既定とします)
質問を必要以上に重ねないでください。ユーザーが「Amazon の商品ページ用のスクレイパーを作って」と言った場合、サイトは Amazon、データは商品詳細、対象範囲は商品ページだとわかります。先に進んでください。
フェーズ 2:構築済みのスクレイパーを確認する
カスタム開発を始める前に、Bright Data に対象ドメイン用のスクレイパーがすでにあるか確認してください。これが最も速く、低コストで、信頼性の高い方法です。
一般的な構築済みスクレイパーの厳選リストは、references/supported-domains.md を確認してください。ただし、厳選リストにすべてが含まれているとは限りません。Bright Data は 100 以上のドメインに対応しており、新しいスクレイパーも定期的に追加しています。厳選リストに対象ドメインがない場合は、ライブの Dataset List API に問い合わせて確認してください。
curl -H "Authorization: Bearer $BRIGHTDATA_API_KEY" \
https://api.brightdata.com/datasets/list
利用可能なすべてのスクレイパーと、その dataset_id および名前が返されます。結果から対象ドメインを検索してください。スクレイパー固有のドキュメントや対応パラメーターを確認するには、ドキュメントの完全なインデックス https://docs.brightdata.com/llms.txt も参照できます。
構築済みのスクレイパーがある場合
Web Scraper API または Python SDK のプラットフォーム固有のスクレイパーを使用してください。解析コードを用意することなく、構造化された JSON を取得できます。
Python SDK を使う方法(推奨):
from brightdata import BrightDataClient
async with BrightDataClient() as client:
result = await client.scrape.amazon.products(url="https://amazon.com/dp/B0CRMZHDG8")
if result.success:
print(result.data) # Structured product data
CLI を使う方法(Bright Data CLI 0.4.0+):
bdata pipelines search "<site or data type>" # find the dataset_id
bdata pipelines info <dataset_id> <method> # check input fields
bdata pipelines run <dataset_id> <method> "<url>"
構築済みのスクレイパーで大量のデータをスクレイピングする場合は、非同期のトリガー/ポーリング/取得パターンを使用してください。
async with BrightDataClient() as client:
# Trigger without waiting
job = await client.scrape.amazon.products_trigger(url=url)
# Poll until ready
await job.wait(timeout=180, poll_interval=10, verbose=True)
# Fetch results
data = await job.fetch()
構築済みのスクレイパーで複数ページのスクレイピングが必要な場合は、フェーズ 5(ページネーション/オーケストレーション)に進んでください。
構築済みのスクレイパーがない場合
フェーズ 3 に進み、サイトを分析してカスタムスクレイパーを構築してください。
フェーズ 3:サイトの調査
信頼性の高いスクレイパーと壊れやすいスクレイパーを分ける重要なステップです。抽出コードを書く前に、サイトの構造を理解する必要があります。
ステップ 3a:ページの HTML を取得する
Web Unlocker を使って生の HTML を取得します。これにより、コンテンツがサーバー側でレンダリングされているか、クライアント側でレンダリングされているかを確認でき、分析対象となる実際の DOM も取得できます。
import requests
import os
API_KEY = os.environ["BRIGHTDATA_API_KEY"]
ZONE = os.environ["BRIGHTDATA_UNLOCKER_ZONE"]
response = requests.post(
"https://api.brightdata.com/request",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"zone": ZONE,
"url": "https://target-site.com/page",
"format": "raw"
}
)
html = response.text
または、スクレイピングスキルのシェルスクリプトを使います:
bash skills/scrape/scripts/scrape.sh "https://target-site.com/page"
ステップ 3b:HTML 構造を分析する
詳しい分析手順については、references/site-analysis-guide.md をお読みください。
取得した HTML を確認し、次の点を判断します。
-
コンテンツは HTML に含まれていますか? 必要なデータが生の HTML に含まれている場合は、Web Unlocker で十分です。HTML が JavaScript フレームワークのマーカー(
<div id="root"></div>、<div id="__next"></div>、ng-app)を含む空の殻のような状態であれば、コンテンツはクライアント側でレンダリングされているため、Browser API が必要です。 -
信頼できるセレクターを特定します。 データフィールドを対象とする CSS セレクターまたはデータ属性を探します。次の順序(信頼性の高いものから低いもの)でセレクターを優先してください。
data-*属性(例:[data-testid="product-price"])— デザイン変更後も残りやすい- 固有のクラスを持つセマンティック HTML(例:
.product-card .price) id属性 — 一意ですが、変更される可能性があります- 構造セレクター(例:
div > span:nth-child(2))— 壊れやすいため、避けてください
-
データのパターンを特定します。 次のどれに該当しますか。
- 一覧ページ — 繰り返し構造の複数アイテム(商品グリッド、検索結果)
- 詳細ページ — 多数のフィールドを持つ単一アイテム(商品ページ、プロフィール)
- ページネーションあり — 次へ/前へコントロールがある複数ページの結果
- 無限スクロール — スクロール時にコンテンツが読み込まれる(Browser API が必要)
- API 連携 — ネットワークタブのパターンを確認してください。一部のサイトは JSON API からデータを取得します
-
非公開 API を確認します。 最新のサイトの多くは、内部 API への XHR/fetch 呼び出しでデータを読み込みます。ページソースやネットワーク通信に構造化された JSON エンドポイントが見つかった場合、Web Unlocker 経由で直接アクセスするほうが、HTML を解析するよりもすっきりした方法であることがよくあります。
ステップ 3c:抽出方法を決める
分析結果に基づいて、次のように判断します。
| 調査結果 | 方法 |
|---|---|
| コンテンツが HTML にあり、操作が不要 | Web Unlocker — HTML を取得し、BeautifulSoup/Cheerio で解析 |
| JSON API 経由で読み込まれるコンテンツ | Web Unlocker — API エンドポイントに直接アクセス |
| JavaScript のレンダリングが必要なコンテンツ | Browser API — レンダリングしてから抽出 |
| クリック/スクロール/操作が必要なコンテンツ | Browser API — 操作を自動化 |
| 無限スクロールによるページネーション | Browser API — スクロールして収集 |
| 標準的な URL ベースのページネーション | Web Unlocker — ページ URL を順に処理 |
| CAPTCHA が頻繁に表示されるサイト | Browser API — CAPTCHA を自動で解決 |
フェーズ 4:抽出処理を構築する
ここで実際の抽出コードを書きます。方法はフェーズ 3 での判断に応じて異なります。
方法 A:Web Unlocker + HTML 解析
静的なサイトや、サーバー側でレンダリングされた HTML を持つサイトに最適です。最も安価で高速な方法です。
import requests
import os
from bs4 import BeautifulSoup
API_KEY = os.environ["BRIGHTDATA_API_KEY"]
ZONE = os.environ["BRIGHTDATA_UNLOCKER_ZONE"]
def fetch_page(url: str) -> str:
"""Fetch a page through Bright Data Web Unlocker."""
response = requests.post(
"https://api.brightdata.com/request",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"zone": ZONE, "url": url, "format": "raw"}
)
response.raise_for_status()
return response.text
def parse_products(html: str) -> list[dict]:
"""Extract product data from HTML. Customize selectors per site."""
soup = BeautifulSoup(html, "html.parser")
products = []
for card in soup.select(".product-card"): # Adjust selector
product = {
"name": card.select_one(".product-title").get_text(strip=True),
"price": card.select_one(".product-price").get_text(strip=True),
"url": card.select_one("a")["href"],
# Add more fields as needed
}
products.append(product)
return products
# Usage
html = fetch_page("https://example.com/products")
products = parse_products(html)
堅牢な解析のための主なパターン:
- 空白を整えるため、常に
.get_text(strip=True)を使う - 属性がない場合の KeyError を避けるため、
["href"]ではなく.get("href", "")を使う - 1 つのアイテムの問題でスクレイピング全体が停止しないよう、個々のフィールドの抽出を try/except で囲む
- 価格の正規化(通貨記号の除去、float への変換)は別のステップで行う
方法 B:Web Unlocker + API からの直接抽出
サイトが JSON API エンドポイントからデータを読み込んでいるとわかった場合は、そのエンドポイントに直接アクセスします。HTML の解析が不要なため、最もすっきりした方法です。
import requests
import json
import os
API_KEY = os.environ["BRIGHTDATA_API_KEY"]
ZONE = os.environ["BRIGHTDATA_UNLOCKER_ZONE"]
def fetch_api(api_url: str) -> dict:
"""Fetch a JSON API endpoint through Web Unlocker."""
response = requests.post(
"https://api.brightdata.com/request",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"zone": ZONE, "url": api_url, "format": "raw"}
)
return json.loads(response.text)
# Example: site with internal API
data = fetch_api("https://example.com/api/products?page=1&limit=50")
products = data["results"] # Already structured!
アプローチ C:Browser API + Playwright
サイトで JavaScript によるレンダリングや操作(クリック、スクロール、フォーム入力)が必要な場合、または強力なボット対策がある場合に使用します。
import asyncio
from playwright.async_api import async_playwright
AUTH = os.environ.get("BROWSER_AUTH", "brd-customer-CUSTOMER_ID-zone-ZONE_NAME:PASSWORD")
async def scrape_with_browser(url: str) -> str:
"""Scrape a page using Bright Data Browser API."""
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
f"wss://{AUTH}@brd.superproxy.io:9222"
)
page = await browser.new_page()
page.set_default_navigation_timeout(120_000) # 2 minutes — required
# Block unnecessary resources to reduce bandwidth costs
await page.route("**/*.{png,jpg,jpeg,gif,svg,css,woff,woff2}",
lambda route: route.abort())
await page.goto(url, wait_until="domcontentloaded")
# Wait for the content you need to appear
await page.wait_for_selector(".product-card", timeout=30_000)
# Extract data using page.evaluate for performance
products = await page.evaluate("""
() => Array.from(document.querySelectorAll('.product-card')).map(card => ({
name: card.querySelector('.product-title')?.textContent?.trim(),
price: card.querySelector('.product-price')?.textContent?.trim(),
url: card.querySelector('a')?.href,
}))
""")
await browser.close()
return products
必ず守る Browser API のルール:
- ナビゲーションのタイムアウトは必ず 120 秒に設定します(
set_default_navigation_timeout(120_000)) - 1 セッションにつき
page.goto()は 1 回のみです。新しい URL に移動する場合は、新しいブラウザー接続を作成します networkidleではなくwait_until="domcontentloaded"を使用します(SPA はnetworkidleに到達しません)- 任意の時間だけ待つのではなく、特定のセレクターを待ちます
- 帯域幅のコストを削減するため、画像、CSS、フォントをブロックします
- 個別のセレクター呼び出しより高速な
page.evaluate()を使用して一括抽出します
アプローチ D:無限スクロール向けの Browser API
スクロールすると追加のコンテンツが読み込まれるサイトに使用します。
async def scrape_infinite_scroll(url: str, max_items: int = 100) -> list:
"""Scrape a page with infinite scroll."""
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
f"wss://{AUTH}@brd.superproxy.io:9222"
)
page = await browser.new_page()
page.set_default_navigation_timeout(120_000)
await page.route("**/*.{png,jpg,jpeg,gif,svg,woff,woff2}",
lambda route: route.abort())
await page.goto(url, wait_until="domcontentloaded")
all_items = []
previous_count = 0
while len(all_items) < max_items:
# Scroll to bottom
await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
await page.wait_for_timeout(2000) # Wait for content to load
# Extract all currently visible items
items = await page.evaluate("""
() => Array.from(document.querySelectorAll('.item-selector')).map(el => ({
// ... extract fields
}))
""")
all_items = items
if len(all_items) == previous_count:
break # No new content loaded — we've reached the end
previous_count = len(all_items)
await browser.close()
return all_items[:max_items]
フェーズ 5:ページネーションに対応する
スクレイピングタスクの多くは複数ページにわたります。適切な方法はページネーションの種類によって異なります。
詳しいページネーション戦略については、references/pagination-patterns.md をご覧ください。
パターン 1:URL ベースのページネーション(最も一般的)
?page=2 や ?offset=20 のような URL パラメーターを使ってページにアクセスします。
import time
def scrape_all_pages(base_url: str, max_pages: int = 50) -> list[dict]:
"""Scrape all pages of a paginated listing."""
all_items = []
for page_num in range(1, max_pages + 1):
url = f"{base_url}?page={page_num}"
html = fetch_page(url)
items = parse_products(html)
if not items:
break # No more results
all_items.extend(items)
print(f"Page {page_num}: {len(items)} items (total: {len(all_items)})")
time.sleep(1) # Be respectful — don't hammer the site
return all_items
パターン 2:次ページリンクによるページネーション
HTML 内で見つかった「次へ」リンクをたどります。
def scrape_with_next_links(start_url: str) -> list[dict]:
"""Follow next-page links to scrape all pages."""
all_items = []
url = start_url
while url:
html = fetch_page(url)
items = parse_products(html)
all_items.extend(items)
# Find next page link
soup = BeautifulSoup(html, "html.parser")
next_link = soup.select_one("a.next-page, a[rel='next'], .pagination .next a")
url = next_link["href"] if next_link else None
# Handle relative URLs
if url and not url.startswith("http"):
from urllib.parse import urljoin
url = urljoin(start_url, url)
time.sleep(1)
return all_items
パターン 3:複数 URL の並行スクレイピング
URL が多数(50 件以上)ある場合は、必ずセマフォを使った並行リクエストを使用してください。逐次ループで 1 件ずつ取得してはいけません。サイトごとの調整、複数サイトの並列処理、再試行戦略を含む並行処理の手順全体については、references/concurrency-guide.md をご覧ください。
import asyncio
import aiohttp
CONCURRENCY = 20 # Start here, tune per site — see concurrency guide
async def scrape_pages_concurrent(urls: list[str]) -> list[dict]:
"""Scrape multiple pages with controlled concurrency."""
sem = asyncio.Semaphore(CONCURRENCY)
async def fetch_one(session, url):
async with sem:
async with session.post(
"https://api.brightdata.com/request",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"zone": ZONE, "url": url, "format": "raw"},
timeout=aiohttp.ClientTimeout(total=60),
) as resp:
return {"url": url, "html": await resp.text()}
async with aiohttp.ClientSession() as session:
tasks = [fetch_one(session, url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
all_items = []
for r in results:
if not isinstance(r, Exception):
all_items.extend(parse_products(r["html"]))
return all_items
# Generate all page URLs
urls = [f"https://example.com/products?page={i}" for i in range(1, 51)]
items = asyncio.run(scrape_pages_concurrent(urls))
パターン 4:カーソル/トークンベースのページネーション(API)
ページ番号の代わりにカーソルトークンを使う API もあります。
def scrape_with_cursor(api_base: str) -> list[dict]:
"""Handle cursor-based API pagination."""
all_items = []
cursor = None
while True:
url = f"{api_base}?limit=100"
if cursor:
url += f"&cursor={cursor}"
data = fetch_api(url)
all_items.extend(data["results"])
cursor = data.get("next_cursor")
if not cursor:
break
return all_items
フェーズ 6:完全なスクレイパーを組み立てる
ここまでの内容をまとめ、すっきりとした実行可能なスクリプトを作成します。作成するすべてのスクレイパーには、次の要素が必要です。
- 設定 — 環境変数、対象 URL、出力設定
- フェッチャー — ページを取得する関数(Web Unlocker または Browser API)
- パーサー — HTML/JSON から構造化データを抽出する関数
- ページネーター — 複数ページに対応するロジック
- 並行処理 — セマフォ制御による並列取得(references/concurrency-guide.md を参照)
- 出力 — 指定された形式で結果を保存
- エラー処理 — 再試行、ログ記録、適切な失敗処理
重要: ユーザーのスクレイピング対象 URL が約 50 件を超える場合、スクレイパーは逐次ループではなく、必ず並行リクエストを使用する必要があります。並行スクレイパーの完全なテンプレートと調整ガイドラインについては、references/concurrency-guide.md をご覧ください。
テンプレート:完全なスクレイパースクリプト
#!/usr/bin/env python3
"""
Scraper for [SITE NAME] - [DESCRIPTION]
Built with Bright Data [API NAME]
Usage:
export BRIGHTDATA_API_KEY="your-api-key"
export BRIGHTDATA_UNLOCKER_ZONE="your-zone-name"
python scraper.py
"""
import json
import os
import sys
import time
import logging
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
# --- Configuration ---
API_KEY = os.environ["BRIGHTDATA_API_KEY"]
ZONE = os.environ["BRIGHTDATA_UNLOCKER_ZONE"]
TARGET_URL = "https://example.com/products"
OUTPUT_FILE = "results.json"
MAX_PAGES = 50
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
log = logging.getLogger(__name__)
# --- Fetcher ---
def fetch_page(url: str, retries: int = 3) -> str:
for attempt in range(retries):
try:
response = requests.post(
"https://api.brightdata.com/request",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"zone": ZONE, "url": url, "format": "raw"},
timeout=60,
)
response.raise_for_status()
return response.text
except requests.RequestException as e:
log.warning(f"Attempt {attempt + 1} failed for {url}: {e}")
if attempt < retries - 1:
time.sleep(2 ** attempt)
raise RuntimeError(f"Failed to fetch {url} after {retries} attempts")
# --- Parser ---
def parse_items(html: str) -> list[dict]:
soup = BeautifulSoup(html, "html.parser")
items = []
for card in soup.select("ITEM_SELECTOR"):
try:
item = {
"name": card.select_one("NAME_SELECTOR").get_text(strip=True),
"price": card.select_one("PRICE_SELECTOR").get_text(strip=True),
"url": card.select_one("a").get("href", ""),
}
items.append(item)
except (AttributeError, TypeError) as e:
log.warning(f"Failed to parse item: {e}")
continue
return items
# --- Paginator ---
def scrape_all(base_url: str) -> list[dict]:
all_items = []
for page in range(1, MAX_PAGES + 1):
url = f"{base_url}?page={page}"
log.info(f"Scraping page {page}...")
html = fetch_page(url)
items = parse_items(html)
if not items:
log.info(f"No items on page {page} — stopping")
break
all_items.extend(items)
log.info(f"Got {len(items)} items (total: {len(all_items)})")
time.sleep(1)
return all_items
# --- Output ---
def save_results(items: list[dict], path: str):
with open(path, "w") as f:
json.dump(items, f, indent=2, ensure_ascii=False)
log.info(f"Saved {len(items)} items to {path}")
# --- Main ---
if __name__ == "__main__":
items = scrape_all(TARGET_URL)
save_results(items, OUTPUT_FILE)
ユーザー向けのスクレイパーを作成する際は、このテンプレートをカスタマイズします。
ITEM_SELECTOR、NAME_SELECTOR、PRICE_SELECTORをフェーズ 3 で取得した実際のセレクターに置き換えます- 適切なフェッチャーを選びます(Web Unlocker または Browser API)
- フェーズ 5 から適切なページネーションパターンを選びます
- ユーザーが必要とするフィールドを追加します
- CSV またはデータベースへの挿入を希望する場合は、出力形式を調整します
判断の参考:API 選択のクイックガイド
| シナリオ | API | コスト | 速度 |
|---|---|---|---|
| サイトに既製のスクレイパーがある(Amazon、LinkedIn など) | Web Scraper API | レコードごと | 高速 |
| 静的 HTML ページで、JavaScript が不要 | Web Unlocker | リクエストごと(成功時のみ) | 高速 |
| サイトが JSON API を公開している | Web Unlocker → API endpoint | リクエストごと(成功時のみ) | 最速 |
| JavaScript でレンダリングされるコンテンツ(React、Vue、Angular) | Browser API | 帯域幅ごと | 中速 |
| 無限スクロール | Browser API | 帯域幅ごと | 低速 |
| フォーム送信/ログインが必要 | Browser API | 帯域幅ごと | 中速 |
| CAPTCHA が多いサイト | Browser API | 帯域幅ごと | 中速 |
| 検索エンジンの検索結果 | SERP API | リクエストごと | 高速 |
よくある回避すべき落とし穴
-
Web Unlockerで十分な場合に、安易にBrowser APIを使わないでください。 Browser APIはコストが高く(帯域幅ベース)、速度も遅くなります。まずは必ずWeb Unlockerを試してください。
-
div:nth-child(3) > spanのような構造的なCSSセレクターは使わないでください。 サイトにバナーが追加されたり、要素の配置が変わったりすると機能しなくなります。データ属性またはセマンティックセレクターを使用してください。 -
ページネーションの上限をハードコードしないでください。 ページに実際のアイテムが返されたかどうかを必ず確認してください。空のページが返されたら、終端に達したことを意味します。
-
事前調査の段階を省略しないでください。 HTMLの分析に2分かければ、壊れやすいセレクターのデバッグに何時間も費やさずに済みます。
-
各アイテムのエラー処理を忘れないでください。 形式が不正な商品カードが1つあっても、スクレイピング全体が停止しないようにしてください。個々のアイテムの解析をtry/exceptで囲んでください。
-
Browser APIで
networkidleを使わないでください。 SPAがネットワークアイドル状態に本当に達することはありません。代わりにdomcontentloadedとwait_for_selectorを使用してください。 -
リストをスクレイピングするとき、ページごとに新しいブラウザーセッションを作成しないでください。 リストページで「次へ」をクリックする場合は、同じセッションを維持できます。新しいセッションを作成するのは、ベースURLが異なる場合だけです。
-
URLが大量にある場合、順番にスクレイピングしないでください。 1,000件以上のURLを、それぞれの間に
time.sleep(1)を挟んで1件ずつ取得するのは、許容できないほど遅くなります。セマフォを使って同時リクエストを行ってください。references/concurrency-guide.mdを参照してください。
例
例1:Eコマースの商品スクレイパー(既成のものがあります)
ユーザーの依頼:「Amazonの商品ページ用スクレイパーを作成してください。ASINのリストが200件あります」
実行内容:
- supported-domains.mdを確認 → Amazonには既成のスクレイパーがあります
- URLが200件 → 非同期のトリガー/ポーリング/取得を使用(同期処理の上限である20件を超えるため)
- URLをまとめて
client.scrape.amazon.products_trigger()を使用 - 準備ができるまでポーリングし、構造化されたJSONをダウンロード
- HTMLの解析は不要です。データはあらかじめ構造化されています
結果:進行状況のログ記録とJSON出力を備えた、非同期の一括スクレイピング用Pythonスクリプトが完成します。
例2:カスタムサイトのスクレイパー(既成のものはありません)
ユーザーの依頼:「jobs.customsite.comの求人情報を、ページネーションを含めてすべてスクレイピングしたいです」
実行内容:
- supported-domains.mdを確認 → 記載なし → Dataset List APIに問い合わせ → 見つからない
- Web UnlockerでページのHTMLを取得し、構造を分析
- コンテンツがHTML内にある(SSR)→ Web Unlocker方式を使用
- セレクターを特定:
.job-card、.job-title、.company-name、.salary ?page=NのURLパラメーターでページネーション- フェッチャー、パーサー、ページネーターを備えた完全なスクレイパーを構築
結果:Web UnlockerとBeautifulSoupを使った、URLベースのページネーション対応Pythonスクリプトが完成します。
例3:JavaScriptを多用するサイト(Browser APIが必要)
ユーザーの依頼:「スクロールに応じてデータを読み込むReact SPAから、商品の価格をスクレイピングしてください」
実行内容:
- Web UnlockerでHTMLを取得 → 本文が空の
div#root→ クライアント側でレンダリングされています - ページソース内に隠れたAPIがないか確認 → APIは見つからない
- Playwrightを使うBrowser APIに切り替え
- リソースのブロックを組み合わせた無限スクロールのパターンを実装
- コンテンツの読み込み後、
page.evaluate()でデータを抽出
結果:Browser APIを使った非同期Playwrightスクリプトが完成します。無限スクロールに対応し、帯域幅を最適化します。
トラブルシューティング
Web Unlockerが空のページ、またはブロックされたページを返す
原因: サイトでJavaScriptのレンダリングが必要か、強力なボット検出が行われています。
解決策: Browser APIに切り替えてください。また、data_format: "markdown"を追加して、コンテンツが存在するものの別の形式で返されている可能性を確認してください。
セレクターがローカルでは機能するのに、本番環境では失敗する
原因: サイトが地域やユーザーエージェントによって異なるHTMLを返しています。
解決策: Web Unlockerのリクエストにcountryパラメーターを追加し、同じ地域を指定してください。ブラウザーのDevToolsではなく、APIから実際に返されたHTMLでセレクターを検証してください。
スクレイパーがページ間で重複するアイテムを返す
原因: ページネーションのロジックが最初に戻っているか、サイトのページネーションに一貫性がありません。 解決策: 取得済みのアイテムIDをセットで追跡してください。重複が見つかったら処理を終了します。ページネーションのURLパターンが正しいことを確認してください。
ブラウザー API セッションがタイムアウトする
原因: ナビゲーションのタイムアウトが短すぎるか、サイトのブロック解除に時間がかかっています。
解決策: 常に set_default_navigation_timeout(120_000) を設定してください。networkidle ではなく wait_until="domcontentloaded" を使用してください。ご利用のゾーンでプレミアムドメインが有効になっている必要があるか確認してください。
API が 401 Unauthorized を返す
原因: 環境変数 BRIGHTDATA_API_KEY が設定されていないか、値が無効です。
解決策: キーが設定されていることを確認してください:echo $BRIGHTDATA_API_KEY。新しいキーは https://brightdata.com/cp/setting/users から取得してください。
リファレンスファイル
- references/supported-domains.md — データセット ID と Python SDK メソッドを含む、事前構築済みスクレーパーの完全な一覧です。カスタムのスクレイピングコードを書く前に、必ず最初に確認してください。
- references/site-analysis-guide.md — サイトの HTML 構造を分析し、信頼性の高いセレクターを選ぶための手順を解説します。
- references/pagination-patterns.md — よくあるパターンごとのコード例を含む、詳細なページネーション戦略です。
- references/concurrency-guide.md — セマフォ制御、サイトごとの調整、複数サイトの並列処理、再試行、進捗状況の追跡を用いて URL を並行してスクレイピングする方法を解説します。ユーザーがスクレイピングする URL を 50 件以上指定している場合は、必ずこれを読んでください。