Pythonでスクレイピング入門|BeautifulSoup・Seleniumの使い方

kento_morota 18分で読めます

Webスクレイピングは、Webサイトから必要な情報を自動的に収集する技術です。競合分析、価格調査、データ収集など、業務のさまざまな場面で活用されています。Pythonにはスクレイピングを効率的に行うためのライブラリが豊富に揃っており、比較的少ないコードで強力なデータ収集が実現できます。

本記事では、Pythonの代表的なスクレイピングライブラリであるBeautifulSoupとSeleniumの使い方を、実践的なコード例とともに解説します。法的・倫理的な注意点にも触れ、安全にスクレイピングを行うための知識をお伝えします。

Webスクレイピングの基礎知識

スクレイピングの技術的な手法に入る前に、基本的な概念と注意事項を理解しておきましょう。

スクレイピングの仕組み

Webスクレイピングは、以下のステップで行われます。

  1. HTTPリクエスト:対象のWebページにアクセスし、HTMLを取得する
  2. HTML解析:取得したHTMLを解析し、必要なデータの位置を特定する
  3. データ抽出:特定した位置からテキストやリンクなどの情報を取り出す
  4. データ保存:抽出したデータをCSVやデータベースに保存する

法的・倫理的な注意点

スクレイピングを行う前に、以下の点を必ず確認してください。

  • robots.txt:対象サイトの/robots.txtを確認し、クロールが許可されているかを確認する
  • 利用規約:サイトの利用規約でスクレイピングが禁止されていないか確認する
  • アクセス頻度:サーバーに過度な負荷をかけないよう、リクエスト間隔を適切に設定する
  • 個人情報:個人情報に該当するデータの収集は、個人情報保護法に抵触する可能性がある
  • 著作権:収集したコンテンツの利用は著作権法の範囲内で行う
# robots.txtの確認方法
import urllib.robotparser

rp = urllib.robotparser.RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()

# 特定のURLへのアクセスが許可されているか確認
can_fetch = rp.can_fetch("*", "https://example.com/target-page")
print(f"アクセス許可: {can_fetch}")

環境構築とライブラリの選び方

必要なライブラリのインストール

# 基本的なスクレイピング用
pip install requests beautifulsoup4 lxml

# 動的サイト向け(Selenium)
pip install selenium

# データ保存・分析用
pip install pandas

ライブラリの使い分け

ライブラリ特徴適したケース
requests + BeautifulSoup軽量・高速。静的HTMLの解析に最適HTMLが直接返されるサイト
Seleniumブラウザを自動操作。JavaScriptの実行が可能SPAやJSで描画されるサイト
Scrapy大規模クロール向けフレームワーク数千〜数万ページの収集
Playwrightモダンなブラウザ自動化ツールSeleniumの代替として

BeautifulSoupによるスクレイピング

BeautifulSoupは、HTMLやXMLを解析するためのライブラリです。requestsと組み合わせることで、シンプルかつ高速なスクレイピングが可能です。

基本的な使い方

import requests
from bs4 import BeautifulSoup

# Webページの取得
url = "https://example.com"
response = requests.get(url)
response.encoding = response.apparent_encoding  # 文字化け対策

# HTMLの解析
soup = BeautifulSoup(response.text, "lxml")

# ページタイトルの取得
print(soup.title.text)

# 特定のタグをすべて取得
links = soup.find_all("a")
for link in links:
    href = link.get("href")
    text = link.text.strip()
    print(f"{text}: {href}")

要素の検索方法

# タグ名で検索
h2_tags = soup.find_all("h2")

# classで検索
items = soup.find_all("div", class_="item-card")

# idで検索
header = soup.find(id="main-header")

# CSSセレクタで検索
prices = soup.select("div.product > span.price")
nav_links = soup.select("nav ul li a")

# 属性で検索
images = soup.find_all("img", attrs={"data-src": True})

実践:ニュースサイトの記事一覧を取得

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time

def scrape_news(url):
    """ニュースサイトの記事一覧をスクレイピングする"""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()

    soup = BeautifulSoup(response.text, "lxml")

    articles = []
    for article in soup.select("article.news-item"):
        title = article.select_one("h2 a")
        date = article.select_one("time")
        summary = article.select_one("p.summary")

        if title:
            articles.append({
                "タイトル": title.text.strip(),
                "URL": title.get("href", ""),
                "日付": date.text.strip() if date else "",
                "概要": summary.text.strip() if summary else "",
            })

    return articles

# 実行
articles = scrape_news("https://example.com/news")
df = pd.DataFrame(articles)
df.to_csv("news_articles.csv", index=False, encoding="utf-8-sig")
print(f"{len(articles)}件の記事を取得しました。")

複数ページのスクレイピング(ページネーション対応)

import time

def scrape_all_pages(base_url, max_pages=10):
    """複数ページをスクレイピングする"""
    all_articles = []

    for page in range(1, max_pages + 1):
        url = f"{base_url}?page={page}"
        print(f"ページ {page} を取得中...")

        try:
            articles = scrape_news(url)
            if not articles:
                print("記事が見つからないため終了します。")
                break
            all_articles.extend(articles)
        except requests.exceptions.RequestException as e:
            print(f"エラーが発生しました: {e}")
            break

        # サーバーへの負荷を軽減するため待機
        time.sleep(2)

    return all_articles

results = scrape_all_pages("https://example.com/news", max_pages=5)
print(f"合計 {len(results)} 件の記事を取得しました。")

Seleniumによる動的サイトのスクレイピング

JavaScriptで動的に描画されるサイト(SPA: Single Page Application等)では、requestsでHTMLを取得してもデータが含まれていない場合があります。Seleniumを使えば、実際のブラウザを操作してJavaScriptが実行された後のHTMLを取得できます。

Seleniumの基本セットアップ

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# Chrome オプションの設定
options = Options()
options.add_argument("--headless")          # ヘッドレスモード(画面非表示)
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")

# ドライバーの起動
driver = webdriver.Chrome(options=options)

try:
    # ページにアクセス
    driver.get("https://example.com")

    # ページタイトルの取得
    print(driver.title)

    # 要素の取得
    elements = driver.find_elements(By.CSS_SELECTOR, "div.item")
    for elem in elements:
        print(elem.text)
finally:
    driver.quit()

要素の待機と操作

# 要素が表示されるまで待機(最大10秒)
wait = WebDriverWait(driver, 10)
element = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "div.loaded-content"))
)

# クリック操作
button = driver.find_element(By.CSS_SELECTOR, "button.load-more")
button.click()

# テキスト入力
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("Python スクレイピング")
search_box.submit()

# スクロール操作(無限スクロール対応)
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)  # スクロール後のコンテンツ読み込みを待つ

実践:動的サイトからデータを収集

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import pandas as pd
import time

def scrape_dynamic_site(url):
    """JavaScript描画のサイトからデータを収集する"""
    options = Options()
    options.add_argument("--headless")
    driver = webdriver.Chrome(options=options)
    results = []

    try:
        driver.get(url)

        # コンテンツの読み込みを待機
        wait = WebDriverWait(driver, 15)
        wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".product-list")))

        # 「もっと見る」ボタンを繰り返しクリック
        for _ in range(5):
            try:
                load_more = driver.find_element(By.CSS_SELECTOR, "button.load-more")
                load_more.click()
                time.sleep(2)
            except Exception:
                break

        # データの抽出
        products = driver.find_elements(By.CSS_SELECTOR, ".product-card")
        for product in products:
            name = product.find_element(By.CSS_SELECTOR, ".product-name").text
            price = product.find_element(By.CSS_SELECTOR, ".product-price").text
            results.append({"商品名": name, "価格": price})

    finally:
        driver.quit()

    return results

data = scrape_dynamic_site("https://example.com/products")
df = pd.DataFrame(data)
print(df)

エラーハンドリングと安定運用

実運用のスクレイピングでは、エラーハンドリングとリトライ処理が不可欠です。

リトライ処理の実装

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_session():
    """リトライ機能付きのHTTPセッションを作成する"""
    session = requests.Session()
    retries = Retry(
        total=3,              # 最大リトライ回数
        backoff_factor=1,     # リトライ間隔(1秒、2秒、4秒と指数的に増加)
        status_forcelist=[500, 502, 503, 504],
    )
    adapter = HTTPAdapter(max_retries=retries)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    return session

session = create_session()
response = session.get("https://example.com", timeout=10)

エラーハンドリングのパターン

import logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

def safe_scrape(url):
    """安全にスクレイピングを行う関数"""
    try:
        response = session.get(url, timeout=10)
        response.raise_for_status()

        soup = BeautifulSoup(response.text, "lxml")
        # データ抽出処理
        return {"status": "success", "data": soup}

    except requests.exceptions.Timeout:
        logger.warning(f"タイムアウト: {url}")
        return {"status": "timeout", "data": None}

    except requests.exceptions.HTTPError as e:
        logger.error(f"HTTPエラー: {e}")
        return {"status": "http_error", "data": None}

    except requests.exceptions.ConnectionError:
        logger.error(f"接続エラー: {url}")
        return {"status": "connection_error", "data": None}

    except Exception as e:
        logger.error(f"予期しないエラー: {e}")
        return {"status": "error", "data": None}

スクレイピングのベストプラクティス

安定的かつ倫理的にスクレイピングを運用するためのベストプラクティスを紹介します。

アクセスマナーの遵守

  • 適切なUser-Agentを設定:ボットであることを明示し、連絡先を含める
  • リクエスト間隔を設ける:最低でも1〜2秒の間隔を設定する
  • robots.txtを尊重する:アクセスが禁止されているページにはリクエストしない
  • APIが提供されている場合はAPIを使う:スクレイピングよりもAPIの利用を優先する

コードの品質

  • 取得したデータを随時保存し、途中で停止しても再開できるようにする
  • ログを出力し、実行状況を追跡可能にする
  • サイトの構造変更に備え、セレクタは定数として管理する
# セレクタを定数として管理する例
SELECTORS = {
    "article": "article.news-item",
    "title": "h2 a",
    "date": "time.published",
    "content": "div.article-body",
}

# 構造変更時はここを修正するだけで済む
articles = soup.select(SELECTORS["article"])

BeautifulSoup vs Selenium の判断基準

判断基準BeautifulSoupSelenium
実行速度高速低速
JavaScript実行不可可能
リソース消費少ない多い(ブラウザ起動)
ログイン操作Cookie手動設定ブラウザ操作で可能
大量ページ収集向いている負荷が高い

まずはBeautifulSoupで試し、JavaScriptの実行が必要な場合にのみSeleniumを使う、という判断が効率的です。

まとめ

本記事では、PythonでのWebスクレイピングの基本から実践的な手法までを解説しました。

  • スクレイピングはHTTPリクエスト、HTML解析、データ抽出の3ステップで行われる
  • 静的サイトにはrequests + BeautifulSoup、動的サイトにはSeleniumが適している
  • robots.txtや利用規約の確認、適切なアクセス間隔の設定は必須
  • リトライ処理やエラーハンドリングを実装し、安定した運用を目指す
  • APIが提供されている場合は、スクレイピングよりもAPIの利用を優先する

スクレイピングは強力なデータ収集手段ですが、対象サイトへの配慮と法的な確認を怠らないことが大切です。適切なマナーを守りながら活用し、業務のデータ収集を効率化しましょう。

#Python#スクレイピング#BeautifulSoup
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

起業準備に役立つ情報、もっとありますよ。

まずは話だけ聞いてもらう