はじめに
「Pythonでスクレイピングできた!」
その前に。法的・倫理的に問題ないか確認した?
スクレイピングは便利だけど、やり方を間違えると訴訟リスクがある。この記事では、実際にコードを動かしながら「正しいスクレイピング」を解説するよ。
1. robots.txt の確認(必須)
robots.txtはサイト管理者が「クロールしないで」という意思表示です。
import urllib.robotparser
from urllib.parse import urlparse
def check_robots_txt(url: str, user_agent: str = "*") -> dict:
"""robots.txtをチェックしてアクセス可否を判定"""
parsed = urlparse(url)
robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
rp = urllib.robotparser.RobotFileParser()
rp.set_url(robots_url)
rp.read()
return {
"can_fetch": rp.can_fetch(user_agent, url),
"crawl_delay": rp.crawl_delay(user_agent),
}
実行結果:
✗ https://www.google.com/search?q=test
→ robots.txt: BLOCKED(検索結果ページはダメ)
✓ https://www.google.com/
→ robots.txt: OK(トップページはOK)
✓ https://github.com/
→ robots.txt: OK
Googleの検索結果をスクレイピングするのは明確にNGです。
2. 適切なリクエスト間隔
サーバーに負荷をかけすぎると業務妨害になる可能性があります。
import time
class RateLimiter:
"""リクエスト間隔を制御"""
def __init__(self, requests_per_second: float = 1.0):
self.min_interval = 1.0 / requests_per_second
self.last_request = 0.0
def wait(self):
elapsed = time.time() - self.last_request
if elapsed < self.min_interval:
time.sleep(self.min_interval - elapsed)
self.last_request = time.time()
# 使用例:2秒に1回
limiter = RateLimiter(requests_per_second=0.5)
for i in range(3):
limiter.wait()
print(f"リクエスト {i+1}")
実行結果:
リクエスト 1: 14:14:23.329
(待機中: 2.00秒)
リクエスト 2: 14:14:25.329
(待機中: 2.00秒)
リクエスト 3: 14:14:27.330
目安:
- 一般サイト: 1〜2秒間隔
- 大規模サイト:
Crawl-Delayに従う - 小規模サイト: 5秒以上
3. User-Agent の設定
# ❌ ダメな例
headers = {"User-Agent": "python-requests"} # ブロックされやすい
headers = {"User-Agent": "Mozilla/5.0..."} # 偽装は違法の可能性
# ✓ 良い例
headers = {
"User-Agent": "MyBot/1.0 (+https://example.com/bot; contact@example.com)"
}
含めるべき情報:
- ボット名とバージョン
- 詳細情報のURL
- 連絡先メールアドレス
これにより、問題があればサイト管理者から連絡が来ます。
4. キャッシュで無駄なリクエストを減らす
import hashlib
import time
class SimpleCache:
def __init__(self, max_age_seconds: int = 3600):
self.cache: dict[str, tuple[str, float]] = {}
self.max_age = max_age_seconds
def get(self, url: str) -> str | None:
key = hashlib.md5(url.encode()).hexdigest()
if key in self.cache:
content, timestamp = self.cache[key]
if time.time() - timestamp < self.max_age:
return content # キャッシュヒット
return None
def set(self, url: str, content: str):
key = hashlib.md5(url.encode()).hexdigest()
self.cache[key] = (content, time.time())
実行結果:
[CACHE MISS] https://example.com/page1 - 実際にリクエスト
[CACHE HIT] https://example.com/page1 - キャッシュから取得
5. スクレイピング前チェックリスト
必ず確認すること:
| チェック項目 | 説明 |
|---|---|
| ✓ robots.txt |
urllib.robotparserで確認 |
| ✓ 利用規約 | 「スクレイピング禁止」の記載がないか |
| ✓ APIの有無 | APIがあればそちらを使う |
| ✓ リクエスト間隔 | サーバーに負荷をかけすぎない |
| ✓ User-Agent | 偽装しない、連絡先を含める |
| ✓ 利用目的 | 商用利用は特に注意 |
| ✓ 個人情報 | 収集しない、または法令遵守 |
| ✓ 著作権 | 引用の範囲内か |
6. 法的リスク
訴訟事例
-
岡崎市立中央図書館事件(2010年)
- 図書館サイトへの自動アクセスで逮捕
- 後に不起訴、しかし教訓に
-
hiQ Labs v. LinkedIn(米国)
- 公開データのスクレイピングは違法ではないと判決
- ただし利用規約違反の可能性は残る
法律
- 不正アクセス禁止法: 認証を突破してはダメ
- 著作権法: データの無断転載はダメ
- 個人情報保護法: 個人情報の収集は厳格に
- 業務妨害罪: サーバーダウンさせたらアウト
7. 倫理的スクレイパーの実装
class EthicalScraper:
"""倫理的なスクレイパー"""
def __init__(
self,
user_agent: str,
requests_per_second: float = 1.0,
respect_robots: bool = True
):
self.user_agent = user_agent
self.rate_limiter = RateLimiter(requests_per_second)
self.respect_robots = respect_robots
self.cache = SimpleCache()
self._robot_parsers = {}
def can_fetch(self, url: str) -> bool:
"""robots.txtをチェック"""
if not self.respect_robots:
return True
parsed = urlparse(url)
base_url = f"{parsed.scheme}://{parsed.netloc}"
if base_url not in self._robot_parsers:
rp = urllib.robotparser.RobotFileParser()
rp.set_url(f"{base_url}/robots.txt")
rp.read()
self._robot_parsers[base_url] = rp
return self._robot_parsers[base_url].can_fetch(self.user_agent, url)
def fetch(self, url: str) -> str | None:
# 1. robots.txt チェック
if not self.can_fetch(url):
print(f"[BLOCKED] {url}")
return None
# 2. キャッシュチェック
cached = self.cache.get(url)
if cached:
return cached
# 3. レート制限
self.rate_limiter.wait()
# 4. 実際のリクエスト
import requests
response = requests.get(url, headers={"User-Agent": self.user_agent})
content = response.text
# 5. キャッシュに保存
self.cache.set(url, content)
return content
# 使用例
scraper = EthicalScraper(
user_agent="MyBot/1.0 (+https://example.com/bot; contact@example.com)",
requests_per_second=0.5 # 2秒に1回
)
content = scraper.fetch("https://example.com/page1")
8. APIを使おう
スクレイピングが不要なケース:
| サービス | 公式API |
|---|---|
| Twitter/X | Twitter API |
| GitHub | GitHub API |
| YouTube | YouTube Data API |
| Amazon | Product Advertising API |
| 地図情報 | Google Maps API |
APIがあれば:
- 安定したデータ取得
- 利用規約に沿った利用
- レート制限も明確
まとめ
スクレイピングは「技術的にできる」と「やっていい」は別物です。
- robots.txt を必ず確認
- 利用規約を読む
- APIがあれば使う
- 適切な間隔でリクエスト
- User-Agentに連絡先を入れる
- 個人情報・著作物は慎重に
「相手のサーバーにお邪魔している」という意識を持ちましょう。
# 最低限これだけは
if not check_robots_txt(url)["can_fetch"]:
print("このURLはスクレイピング禁止です")
exit()