Python Webスクレイピングガイド:合法性、安定性、保守性を重視するデータ収集

编程语言

Pythonは公開Webデータの収集に向いています。しかし本番のスクレイピングは、「リクエストしてHTMLを解析する」だけではありません。許可確認、丁寧なアクセス、安定したセレクター、リトライ、データ検証、監視が必要です。

このガイドは、合法性と保守性を重視したデータ収集に絞ります。アクセス制御の回避、ボット対策の突破、CAPTCHA回避、ログイン後や有料ページ、明示的に制限されたデータの収集は扱いません。API、エクスポート、フィード、提携データが提供されている場合は、まず公式手段を使います。

参考ドキュメント:

まず許可を確認する

コードを書く前に、次を確認します。

  • データは公開されていて、ログインなしで閲覧できるか。
  • API、サイトマップ、RSS、エクスポートが提供されているか。
  • robots.txt は対象パスのクロールを許可しているか。
  • 利用規約が自動アクセスや再利用を制限していないか。
  • 個人情報、著作物、規制対象データが含まれる可能性はないか。
  • クローラーを識別し、連絡先を示せるか。

不明な場合は、スクレイピングから始めないでください。許可を得るか、公式データソースを使います。

ツールの選び方

ページの種類 推奨方法 理由
静的HTML httpx または requests + BeautifulSoup/parsel シンプルで速く、軽い
似たページが大量にある Scrapy スケジューリング、リトライ、パイプライン、出力が揃っている
JavaScriptで描画されるページ Playwright クライアント側レンダリングを実行できる
APIで取得できるページ 許可された公開APIを使う HTML解析より安定して効率的
一度だけのデータ整理 Pandas + 保存済みHTML/CSV 繰り返しリクエストを避けられる

安定して、丁寧に、目的を満たせる最も単純な方法を選びます。

小さな静的ページスクレイパー

静的ページでは、明示的なタイムアウト、クローラーを識別するUser-Agent、厳密な解析から始めます。

from __future__ import annotations

import time
from dataclasses import dataclass
from urllib.parse import urljoin

import httpx
from bs4 import BeautifulSoup


BASE_URL = "https://example.com/articles/"


@dataclass
class Article:
    title: str
    url: str
    summary: str


def fetch_html(url: str) -> str:
    headers = {
        "User-Agent": "ExampleResearchBot/1.0 (+https://example.com/contact)"
    }
    with httpx.Client(timeout=15, follow_redirects=True, headers=headers) as client:
        response = client.get(url)
        response.raise_for_status()
        return response.text


def parse_articles(html: str) -> list[Article]:
    soup = BeautifulSoup(html, "html.parser")
    articles: list[Article] = []

    for card in soup.select("article.card"):
        title_el = card.select_one("h2 a")
        summary_el = card.select_one(".summary")
        if not title_el:
            continue

        title = title_el.get_text(" ", strip=True)
        url = urljoin(BASE_URL, title_el.get("href", ""))
        summary = summary_el.get_text(" ", strip=True) if summary_el else ""
        articles.append(Article(title=title, url=url, summary=summary))

    return articles


def main() -> None:
    html = fetch_html(BASE_URL)
    for article in parse_articles(html):
        print(article)
    time.sleep(2)


if __name__ == "__main__":
    main()

セレクターはテストしやすいものにします。セレクターが壊れたら、誤ったデータを黙って保存するのではなく、件数を減らして問題を記録します。

Playwrightで動的ページを扱う

対象データがJavaScriptで生成され、許可されたAPIやフィードがない場合にPlaywrightを使います。

from playwright.sync_api import sync_playwright


def collect_titles(url: str) -> list[str]:
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, wait_until="networkidle", timeout=30_000)

        titles = [
            item.inner_text().strip()
            for item in page.locator("article h2").all()
        ]

        browser.close()
        return titles

Playwrightは通常のHTTPリクエストより重いです。レンダリングが本当に必要なページだけで使います。同じデータがHTMLや許可された公開APIで取れるなら、ブラウザを起動しないほうがよいです。

Scrapyで規模を広げる

ページ数が多く、リトライ、出力、データパイプラインが必要な場合はScrapyが向いています。

import scrapy


class ArticleSpider(scrapy.Spider):
    name = "articles"
    allowed_domains = ["example.com"]
    start_urls = ["https://example.com/articles/"]

    custom_settings = {
        "ROBOTSTXT_OBEY": True,
        "CONCURRENT_REQUESTS_PER_DOMAIN": 2,
        "DOWNLOAD_DELAY": 2,
        "AUTOTHROTTLE_ENABLED": True,
        "AUTOTHROTTLE_TARGET_CONCURRENCY": 1.0,
        "USER_AGENT": "ExampleResearchBot/1.0 (+https://example.com/contact)",
        "FEEDS": {
            "articles.jsonl": {"format": "jsonlines", "encoding": "utf8"}
        },
    }

    def parse(self, response):
        for card in response.css("article.card"):
            title = card.css("h2 a::text").get()
            href = card.css("h2 a::attr(href)").get()
            if title and href:
                yield {
                    "title": title.strip(),
                    "url": response.urljoin(href),
                    "source_url": response.url,
                }

        next_url = response.css("a.next::attr(href)").get()
        if next_url:
            yield response.follow(next_url, callback=self.parse)

重要なのはSpiderだけではありません。ROBOTSTXT_OBEY、ドメインごとの並列数、ダウンロード間隔、AutoThrottleにより、アクセス量を予測しやすくします。

データ品質パイプライン

収集した生データは、そのまま使えることが少ないです。早い段階で検証と正規化を入れます。

from itemadapter import ItemAdapter


class CleanArticlePipeline:
    def process_item(self, item, spider):
        adapter = ItemAdapter(item)

        title = (adapter.get("title") or "").strip()
        url = (adapter.get("url") or "").strip()

        if not title or not url:
            raise ValueError("Missing title or URL")

        adapter["title"] = " ".join(title.split())
        adapter["url"] = url
        return item

本番では、検証に失敗したデータを別に保存し、セレクター変更に気づけるようにします。

丁寧なクロールのルール

丁寧なクローラーはサイト負荷を減らし、運用もしやすくなります。

  • robots.txt と利用規約を尊重する。
  • デフォルトでは低い並列数にする。
  • 遅延とAutoThrottleを設定する。
  • 開発中はレスポンスをキャッシュする。
  • 変わっていないページを繰り返し取得しない。
  • 連続エラーでは激しくリトライせず停止する。
  • 明確なUser-Agentでクローラーを識別する。
  • 連絡先ページまたはメールアドレスを示す。

サイトがクローラーを拒否またはチャレンジする場合、それは境界として扱います。許可なしに制限を回避する実装を進めるべきではありません。

差分クロール

定期実行するジョブでは、毎回すべてを取得しないようにします。

使えるもの:

  • サイトマップやフィード。
  • 最後に見たURLやタイムスタンプ。
  • 対応している場合はETagとLast-Modified。
  • 変更検出用のコンテンツハッシュ。
  • 永続化された既訪問URLストア。
import hashlib


def content_hash(text: str) -> str:
    normalized = " ".join(text.split())
    return hashlib.sha256(normalized.encode("utf-8")).hexdigest()

差分クロールは帯域を節約し、サイト負荷を減らし、データ変更の監査もしやすくします。

監視する指標

本番スクレイパーでは次を記録します。

  • 取得ページ数。
  • 抽出レコード数。
  • 空ページ数。
  • HTTPステータス分布。
  • リトライ回数。
  • 解析失敗数。
  • 重複率。
  • ジョブ実行時間。

抽出件数が急に落ちた場合、サイトのHTMLが変わったか、クローラーが制限に達した可能性があります。

よくある失敗

壊れやすいセレクター

.container > div:nth-child(3) > span のようなセレクターは簡単に壊れます。意味のある要素、安定した属性、ページ内の構造化データを優先します。

文字エンコーディングを無視する

多言語データは必ずテストしてください。エンコーディングの問題は、タイトル、住所、名前を静かに壊します。

収集と業務ロジックを混ぜる

クロール、解析、検証、保存を分けます。そうすれば一部だけ変更できます。

速すぎる実行

高い並列数はブロック、不完全データ、運用リスクにつながります。速いが壊れやすいクローラーより、遅くても安定したクローラーのほうが価値があります。

再現可能な入力がない

代表的なHTMLレスポンスを保存し、パーサーテストに使います。サイトが変わったときに、新旧のマークアップを比較できます。

最終チェックリスト

クローラーを繰り返し実行する前に確認します。

  • 対象データを収集する許可がある。
  • robots.txt と利用規約を確認した。
  • クローラーが自分を識別している。
  • レート制限と遅延を設定した。
  • パーサーテストが代表的なページをカバーしている。
  • データ検証で空レコードや不正形式を検出できる。
  • 差分クロールで不要なリクエストを避けている。
  • ログと指標で失敗が見える。
  • 個人情報や機密データを適用ルールに従って扱っている。

信頼できるデータ収集の中心は工程管理です。許可されたデータだけを集め、丁寧にリクエストし、すべてを検証し、実行後も監視します。

ブラウザローカルツールを無料で試す →

#Python爬虫#反爬虫#Scrapy#Playwright爬虫#2026#编程语言