7
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?

はじめに

「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. 法的リスク

訴訟事例

  1. 岡崎市立中央図書館事件(2010年)

    • 図書館サイトへの自動アクセスで逮捕
    • 後に不起訴、しかし教訓に
  2. 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があれば:

  • 安定したデータ取得
  • 利用規約に沿った利用
  • レート制限も明確

まとめ

スクレイピングは「技術的にできる」と「やっていい」は別物です。

  1. robots.txt を必ず確認
  2. 利用規約を読む
  3. APIがあれば使う
  4. 適切な間隔でリクエスト
  5. User-Agentに連絡先を入れる
  6. 個人情報・著作物は慎重に

「相手のサーバーにお邪魔している」という意識を持ちましょう。

# 最低限これだけは
if not check_robots_txt(url)["can_fetch"]:
    print("このURLはスクレイピング禁止です")
    exit()
7
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
7
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?