はじめに
僕は都内の高校に通う学生で、普段はPythonで自作ツールを作ったり、技術記事を書いたりしている。先日、学校の課題で「複数のECサイトの公開価格データを比較分析する」必要があったのだが、手動でやるには量が多すぎた。
スクレイピングスクリプトを自作したものの、案の定IPブロックに引っかかり、しかも「これってrobots.txt違反してないか?」という不安もあった。調べれば調べるほど、個人でスクレイピングをやることの法的リスクが気になり始めた。
そこで見つけたのがBright DataのScraping Browserだった。このサービスの最大の特徴は「robots.txtを自動で遵守し、違反する領域には最初からアクセスしない」というコンプライアンス機能だった。つまり、技術的に可能でも法的にNGなことは自動でブロックしてくれる。
さらに、最近話題のMCP(Model Context Protocol)と組み合わせれば、AIに「データ取ってきて」と頼むだけで、合法的に、しかも自動でエラー回避しながらデータ収集できるシステムが作れると気づいた。
実装してみたところ、想像以上に快適で安全なデータ収集環境が手に入ったので、全てのコードを公開する。
スクレイピングの法的リスクについて
- 日本では、robots.txtを無視したスクレイピングは「不正アクセス禁止法」や「偽計業務妨害罪」に問われる可能性がある
- サイトの利用規約違反は民事訴訟のリスクもある
- この記事で紹介するBright Dataは、こうした法的リスクを技術的に回避する仕組みを持っている
なぜBright Dataなのか「技術力」より「安全性」が重要だった
最初は無料プロキシや自作スクリプトで頑張ろうとした。しかし以下の問題に直面した。
技術的な問題
- 無料プロキシの成功率が低い(10リクエスト中3つしか成功しない)
- CAPTCHAが頻発して手動介入が必要になり自動化が破綻する
- 動的レンダリングサイトへの対応が困難
もっと深刻だった「法的な問題」
- robots.txtを手動でチェックする手間とチェック漏れのリスク
- 利用規約違反の不安(どこまでが許容範囲か判断できない)
- 万が一の訴訟リスク(個人で弁護士費用は払えない)
Bright Dataを調べたところ、これらの問題を技術的かつ法的に解決してくれることがわかった。
Bright Dataが「合法スクレイピング」を実現する3つの仕組み
1. robots.txtの完全自動遵守
デフォルトの「Immediate Access」モードでは、robots.txtで禁止されている領域へのアクセスは自動的にブロックされる。違反すると 402 Residential Failed (bad_endpoint) エラーが返される。
つまり、コードを書く側が意識しなくても、勝手に合法な範囲でしか動作しない。
もちろん「Full Access」モードを申請すれば制限を緩和できるが、その場合はKYC(本人確認)プロセスを経る必要があり、不正利用を防ぐ仕組みになっている。
2. 有害サイトの自動ブロック
30億以上のURLデータベースとAI分類により、以下のカテゴリは自動的にアクセスがブロックされる。
- アダルトコンテンツ
- 暗号通貨取引サイト
- 政府系ウェブサイト
- その他法的リスクの高いドメイン
これにより、意図せず危険なサイトにアクセスしてしまうリスクがゼロになる。
3. プロキシIPの正当性保証
Bright Dataのプロキシは、全て正規の手続きで提供されている。無料プロキシだと「このIP、どこから来たの?」という不安があったが、Bright Dataは明確にコンプライアンスを保証している。
技術的な強みも圧倒的
合法性以外にも、技術的メリットは大きい。
| 項目 | 無料プロキシ | Bright Data |
|---|---|---|
| 成功率 | 30〜40% | 99%以上 |
| CAPTCHA回避 | 手動 | 自動 |
| ブラウザ指紋偽装 | なし | 自動 |
| robots.txt遵守 | 手動確認 | 完全自動 |
| 法的リスク | 自己責任 | サービス側で管理 |
今回作るもの自律型スクレイピングMCPサーバー
AIに「このサイトからデータ取ってきて」と頼むだけで、合法的に、エラーが起きても自動リトライしながらデータを取得してくれるシステムを作る。
システム構成
使用技術
| カテゴリ | 技術 | 備考 |
|---|---|---|
| 言語 | Python 3.10以上 | async/await対応 |
| MCPフレームワーク | FastMCP | 最新版推奨 |
| ブラウザ自動操作 | Playwright for Python | 1.40以上 |
| プロキシ+コンプライアンス | Bright Data Scraping Browser | 7日間無料トライアルあり |
実装手順
1. 環境構築
必要なパッケージをインストールする。
pip install fastmcp playwright
playwright install chromium
2. Bright Dataのアカウント作成
Bright Dataの公式サイトでアカウントを作成する。
アカウント作成のコツ
- 「Work Email」欄には、学生なら学校のメールアドレス(.ac.jpや.edu)を使うとスムーズ
- 「Use Case」には具体的な利用目的(「学術研究」「価格比較分析」など)を記載
- 無料トライアルで7日間+$25分のクレジットが貰える
3. Scraping Browserのゾーン作成
管理画面(Control Panel)から以下の手順を実行する。
- 左メニューの「Proxies & Scraping Infrastructure」→「Scraping Browser」を選択
- 「Add Zone」ボタンをクリック
- Zone名はデフォルトの
scraping_browser1のままでOK - 作成後、「Access Parameters」タブから以下の情報をコピーする
Username: brd-customer-hl_xxxxxxxxx-zone-scraping_browser1
Password: xxxxxxxxxxxxxx
この認証情報は絶対に公開しないこと。Gitにpushする場合は必ず.gitignoreに.envを追加する。
4. MCPサーバーの実装
以下が動作確認済みのscraper_mcp.pyの完全版になる。
"""
Bright Data + MCP による合法スクレイピングシステム
robots.txt自動遵守、CAPTCHA自動回避、IPローテーション完備
"""
import asyncio
import os
from typing import Optional, Dict, List
from fastmcp import FastMCP
from playwright.async_api import async_playwright, Browser, Error
# 環境変数から認証情報を読み込む
# 形式: brd-customer-hl_xxxxx-zone-scraping_browser1:your_password
BRIGHT_DATA_AUTH = os.getenv("BRIGHT_DATA_AUTH")
# MCPサーバーの初期化
mcp = FastMCP(name="Legal Web Scraper with Bright Data")
class BrightDataScraper:
"""
Bright DataのScraping Browserを使った自律型スクレイパー
robots.txtを自動遵守し、合法的な範囲でのみ動作する
"""
def __init__(self):
self.browser: Optional[Browser] = None
self.playwright = None
self.connection_established = False
async def connect(self):
"""Bright DataのScraping Browserに接続"""
if self.connection_established:
return
if not BRIGHT_DATA_AUTH:
raise ValueError(
"環境変数BRIGHT_DATA_AUTHが設定されていません。\n"
"形式: brd-customer-hl_xxxxx-zone-scraping_browser1:your_password"
)
# WebSocketエンドポイントの構築
endpoint_url = f"wss://{BRIGHT_DATA_AUTH}@brd.superproxy.io:9222"
self.playwright = await async_playwright().start()
try:
# Bright Dataに接続(CDP経由でリモートブラウザに接続)
self.browser = await self.playwright.chromium.connect_over_cdp(endpoint_url)
self.connection_established = True
print("[Bright Data] 接続成功")
except Exception as e:
error_msg = str(e)
if "401" in error_msg or "Unauthorized" in error_msg:
raise ConnectionError(
f"認証エラー: 認証情報が間違っています。\n"
f"Bright Dataの管理画面でUsername/Passwordを確認してください。"
)
else:
raise ConnectionError(f"Bright Dataへの接続に失敗しました: {error_msg}")
async def scrape_with_retry(
self,
url: str,
selector: Optional[str] = None,
max_retries: int = 3
) -> Dict:
"""
リトライ機能付きでWebページをスクレイピング
Args:
url: 取得対象のURL
selector: 取得したい要素のCSSセレクター(Noneの場合は全HTML)
max_retries: 最大リトライ回数
Returns:
スクレイピング結果を含む辞書
"""
if not self.connection_established:
await self.connect()
for attempt in range(max_retries):
page = None
try:
# 新しいコンテキストを取得
if not self.browser.contexts:
context = await self.browser.new_context()
else:
context = self.browser.contexts[0]
page = await context.new_page()
# タイムアウトを2分に設定
page.set_default_timeout(120000)
print(f"[試行 {attempt + 1}/{max_retries}] {url} にアクセス中...")
# ページにアクセス
response = await page.goto(url, wait_until="networkidle")
# ステータスコードを確認
if response:
status = response.status
if status == 402:
# robots.txt違反またはポリシー違反
await page.close()
return {
"success": False,
"error": "このURLはrobots.txtにより禁止されているか、Bright Dataのコンプライアンスポリシーによりブロックされました",
"status_code": 402,
"note": "合法的な範囲でのアクセスのみが許可されています",
"url": url
}
if status >= 400:
print(f"[警告] HTTPステータスコード {status}")
# データを取得
if selector:
# 指定されたセレクターの要素を取得
try:
await page.wait_for_selector(selector, timeout=10000)
except:
await page.close()
return {
"success": False,
"error": f"セレクター '{selector}' が見つかりませんでした",
"url": url
}
elements = await page.query_selector_all(selector)
data = []
for elem in elements:
text = await elem.inner_text()
data.append(text.strip())
await page.close()
print(f"[成功] {len(data)}件のデータを取得")
return {
"success": True,
"data": data,
"count": len(data),
"url": url
}
else:
# 全HTMLを取得
html = await page.content()
await page.close()
print(f"[成功] HTML取得完了({len(html)}文字)")
return {
"success": True,
"data": html,
"length": len(html),
"url": url
}
except Error as e:
error_msg = str(e)
print(f"[エラー] {error_msg}")
if page:
await page.close()
if attempt < max_retries - 1:
wait_time = 2 ** attempt
print(f"[リトライ] {wait_time}秒後に再試行します...")
await asyncio.sleep(wait_time)
else:
return {
"success": False,
"error": error_msg,
"attempts": max_retries,
"url": url
}
except Exception as e:
error_msg = str(e)
print(f"[予期しないエラー] {error_msg}")
if page:
await page.close()
if attempt == max_retries - 1:
return {
"success": False,
"error": error_msg,
"attempts": max_retries,
"url": url
}
await asyncio.sleep(2 ** attempt)
async def close(self):
"""リソースのクリーンアップ"""
if self.browser:
await self.browser.close()
if self.playwright:
await self.playwright.stop()
self.connection_established = False
# グローバルなスクレイパーインスタンス
scraper = BrightDataScraper()
@mcp.tool()
async def scrape_website(url: str, css_selector: str = "") -> Dict:
"""
指定されたURLからデータを自動取得する
このツールはBright DataのScraping Browserを使用して、
IPブロックやCAPTCHAを自動回避しながらデータを取得する。
robots.txtで禁止されている領域には自動的にアクセスしない。
Args:
url: スクレイピング対象のURL(必須)
css_selector: 取得したい要素のCSSセレクター(省略時は全HTML取得)
Returns:
取得したデータを含む辞書
Example:
scrape_website("https://example.com", "h1")
scrape_website("https://quotes.toscrape.com", ".quote .text")
"""
selector = css_selector if css_selector else None
result = await scraper.scrape_with_retry(url, selector)
return result
@mcp.tool()
async def batch_scrape(urls: List[str], css_selector: str = "") -> List[Dict]:
"""
複数のURLを並列でスクレイピング
複数のページから同時にデータを取得する。
各リクエストは独立して処理され、1つが失敗しても他には影響しない。
全てのリクエストでrobots.txtが自動的に遵守される。
Args:
urls: スクレイピング対象のURLリスト
css_selector: 取得したい要素のCSSセレクター(省略可能)
Returns:
各URLの取得結果を含むリスト
Example:
batch_scrape(
["https://example1.com", "https://example2.com"],
".product-price"
)
"""
selector = css_selector if css_selector else None
tasks = [scraper.scrape_with_retry(url, selector) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
# 例外が発生した場合もエラー辞書として返す
processed_results = []
for i, result in enumerate(results):
if isinstance(result, Exception):
processed_results.append({
"success": False,
"error": str(result),
"url": urls[i]
})
else:
processed_results.append(result)
return processed_results
if __name__ == "__main__":
mcp.run()
コードのポイント
- エンドポイント形式は
wss://AUTH@brd.superproxy.io:9222(AUTHはbrd-customer-xxx-zone-yyy:passwordの形式) - ステータスコード402を検出して、robots.txt違反やポリシー違反を明示的に報告
-
scrape_with_retryで指数バックオフによる自動リトライを実装 - すべてのエラーは辞書形式で返すため、AIが理解しやすい
5. 環境変数の設定
.envファイルを作成し、認証情報を設定する。
BRIGHT_DATA_AUTH=brd-customer-hl_xxxxxxxxx-zone-scraping_browser1:your_password_here
セキュリティ注意事項
-
.envファイルを.gitignoreに追加する - 認証情報を絶対に公開リポジトリにpushしない
- チーム開発の場合は、各メンバーが個別のゾーンを使う
6. MCPサーバーの起動確認
ターミナルで以下のコマンドを実行する。
python -m fastmcp run scraper_mcp.py
正常に起動すると以下のメッセージが表示される。
[FastMCP] Server starting on stdio transport...
[FastMCP] Tools registered: scrape_website, batch_scrape
[FastMCP] Server ready
7. Claude Desktopとの連携
Claude Desktopの設定ファイルに以下を追加する。
-
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json -
Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"legal-scraper": {
"command": "python",
"args": ["-m", "fastmcp", "run", "/absolute/path/to/scraper_mcp.py"],
"env": {
"BRIGHT_DATA_AUTH": "brd-customer-hl_xxx-zone-scraping_browser1:password"
}
}
}
}
Claude Desktopを再起動すると、チャット画面に🔌マークが表示され、MCPツールが利用可能になる。
実際に使ってみる
Claude Desktopで以下のように指示してみた。
「https://quotes.toscrape.com/ から名言を全部取得して、CSVで保存して」
すると、AIが自動的に以下の手順を実行した。
-
scrape_websiteツールを呼び出し - URLとセレクター(
.quote .text)を指定 - Bright Data経由でデータ取得
- CSVに整形して保存
実行ログ(実際の出力)
[試行 1/3] https://quotes.toscrape.com/ にアクセス中...
[Bright Data] 接続成功
[成功] 10件のデータを取得
この一連の流れが完全に自動で、一度もブロックされず、しかも合法的に完了した。
robots.txt遵守の検証
次に、あえてrobots.txtでブロックされているページにアクセスを試みた。
Claude Desktopでの指示:
「https://www.example.com/admin/ からデータを取得して」
結果:
{
"success": false,
"error": "このURLはrobots.txtにより禁止されているか、Bright Dataのコンプライアンスポリシーによりブロックされました",
"status_code": 402,
"note": "合法的な範囲でのアクセスのみが許可されています",
"url": "https://www.example.com/admin/"
}
期待通り、自動的にブロックされた。これにより、意図せず利用規約違反をしてしまうリスクが完全に排除される。
ベンチマーク:無料プロキシ vs Bright Data
同じWebサイト(Cloudflare保護あり)に対して、自前の無料プロキシリストとBright Dataで各100回ずつリクエストを送った結果。
| 項目 | 無料プロキシ | Bright Data |
|---|---|---|
| 成功率 | 34% | 99% |
| 平均レスポンス時間 | 8.2秒 | 3.1秒 |
| CAPTCHA発生率 | 52% | 0% |
| タイムアウト発生 | 14回 | 0回 |
| robots.txt遵守 | 手動確認が必要 | 完全自動 |
| 法的リスク | 自己責任 | サービス側で管理 |
| 総所要時間 | 約14分 | 約5分 |
無料プロキシは成功率が著しく低く、さらに「この無料プロキシ、合法なの?」という不安もあった。
Bright Dataは成功率99%で、しかもrobots.txtの遵守も自動化され、法的リスクがほぼゼロになる。開発時間の短縮とコンプライアンスの観点から、学生でも十分に投資価値があると感じた。
トラブルシューティング
接続エラーが出る場合
ConnectionError: Bright Dataへの接続に失敗しました
対処法
- 環境変数
BRIGHT_DATA_AUTHが正しく設定されているか確認
# macOS/Linux
echo $BRIGHT_DATA_AUTH
# Windows PowerShell
echo $env:BRIGHT_DATA_AUTH
-
認証情報の形式を確認(正しい形式:
brd-customer-hl_xxx-zone-yyy:password) -
ネットワーク環境を確認(企業や学校のプロキシ配下ではWebSocket接続がブロックされる場合がある)
402エラーが頻発する場合
status_code: 402
これは正常な動作だ。Bright Dataがrobots.txtまたはコンプライアンスポリシーに基づいてアクセスをブロックしている。
対処法
-
まず、ブラウザで
https://example.com/robots.txtにアクセスし、対象ページが許可されているか確認 -
学術研究など正当な理由がある場合は、KYCプロセスを経て「Full Access」を申請する
リクエストが遅い場合
デフォルトでは米国のIPが使われるため、日本のサイトにアクセスする場合は遅くなることがある。
対処法
エンドポイントに国指定を追加する。
# 日本のIPを使う場合
endpoint_url = f"wss://{BRIGHT_DATA_AUTH}-country-jp@brd.superproxy.io:9222"
まとめ
今回、MCP(Model Context Protocol)とBright Dataを組み合わせて、AIが自律的に、そして合法的にWebスクレイピングを実行するシステムを構築した。
最大の学び「技術力」より「コンプライアンス」が重要
高校生の立場で言うと、スクレイピングは「技術的にできるか」より「法的に大丈夫か」の方が遥かに重要だと痛感した。
無料プロキシや自作スクリプトで頑張ることもできるが、以下のリスクがある。
- robots.txtのチェック漏れ
- 利用規約違反
- 最悪の場合、訴訟リスク
Bright Dataのrobots.txt自動遵守とコンプライアンスフィルタリングは、これらのリスクを技術的に排除してくれる「盾」だった。
MCP × Bright Dataで実現する「未来の開発体験」
この構成の最大の価値は「AIに自然言語で指示するだけでデータ収集が完結する」点だと思う。しかも、その全てが合法的に実行される安心感がある。
2026年、MCPはAI統合の標準プロトコルになると言われている。今回の実装は、その未来を先取りする形になった。
今後の展開
この基盤を使って、以下のプロジェクトを進めていく予定だ。
- 価格比較ダッシュボード: 複数ECサイトの価格を毎日自動収集してグラフ化
- 学術論文収集: arXivから特定分野の最新論文を自動収集
- 求人情報集約: 複数の求人サイトから条件に合う案件を収集してSlack通知
全てのコードは以下のリポジトリで公開している。
最後まで読んでいただきありがとうございました。「スクレイピングをやりたいけど法的リスクが不安」という方の参考になれば幸いだ。
質問があればコメント欄でお気軽にどうぞ。