【Python】WEBスクレイピング完全ガイド2025年版 – 基礎から難関テクニックまで

PythonによるWEBスクレイピングとは?基礎知識の習得

WEBスクレイピングは、Webサイトから自動的にデータを収集する技術です。Pythonの豊富なライブラリを活用することで、効率的にWebデータを取得・処理できます。本記事では、基礎的な使い方から高度なテクニックまで、実践的なコード例とともに解説します。

WEBスクレイピング環境構築:必要なライブラリ

# 必要なライブラリのインストール
# pip install requests beautifulsoup4 selenium pandas lxml

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

基礎編:requestsとBeautifulSoupで始めるスクレイピング

基本的なHTMLの取得と解析

# Webページの取得
url = "https://example.com"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

# タイトル取得
title = soup.find('title').text
print(f"ページタイトル: {title}")

# 特定の要素を取得
links = soup.find_all('a')
for link in links[:3]:  # 最初の3つのリンク
    print(f"リンク: {link.get('href')}")

CSSセレクターを使った要素取得

# CSSセレクターでの要素取得
def scrape_basic_data(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # 見出しを取得
    headlines = soup.select('h1, h2, h3')
    for i, headline in enumerate(headlines[:5]):
        print(f"{i+1}: {headline.text.strip()}")

scrape_basic_data("https://news.ycombinator.com")

中級編:動的コンテンツとフォーム処理

セッション管理とログイン処理

def login_session_example():
    session = requests.Session()
    
    # ログインデータ
    login_data = {
        'username': 'your_username',
        'password': 'your_password'
    }
    
    # ログイン実行
    session.post('https://example.com/login', data=login_data)
    
    # ログイン後のページにアクセス
    protected_page = session.get('https://example.com/protected')
    return protected_page.text

# 使用例(実際のサイトでは適切な認証情報が必要)
# content = login_session_example()

エラーハンドリングとリトライ機能

def robust_scraper(url, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            return BeautifulSoup(response.text, 'html.parser')
        except requests.RequestException as e:
            print(f"試行 {attempt + 1} 失敗: {e}")
            time.sleep(2)
    raise Exception(f"{max_retries}回の試行後に失敗")

# 使用例
soup = robust_scraper("https://httpbin.org/status/200")

上級編:Seleniumによる高度なスクレイピング

動的JavaScript対応

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def selenium_scraper():
    # ヘッドレスモードでブラウザ起動
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    driver = webdriver.Chrome(options=options)
    
    try:
        driver.get("https://example.com")
        
        # 要素が読み込まれるまで待機
        element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "dynamic-content"))
        )
        
        return element.text
    finally:
        driver.quit()

# 使用例
# content = selenium_scraper()

Ajax対応とページ遷移

def handle_ajax_content(driver):
    # Ajaxでロードされるコンテンツを待機
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    
    # 動的コンテンツの読み込み完了を待機
    WebDriverWait(driver, 10).until(
        lambda d: len(d.find_elements(By.CLASS_NAME, "loaded-item")) > 10
    )
    
    items = driver.find_elements(By.CLASS_NAME, "loaded-item")
    return [item.text for item in items]

難関編:大規模スクレイピングと最適化

並行処理による高速化

import concurrent.futures
import threading

def scrape_single_page(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    return soup.find('title').text if soup.find('title') else "No title"

def parallel_scraping(urls):
    results = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        future_to_url = {executor.submit(scrape_single_page, url): url for url in urls}
        
        for future in concurrent.futures.as_completed(future_to_url):
            url = future_to_url[future]
            try:
                title = future.result()
                results.append({'url': url, 'title': title})
            except Exception as exc:
                print(f"{url} でエラー: {exc}")
    
    return results

# 使用例
urls = ["https://example.com", "https://httpbin.org", "https://google.com"]
results = parallel_scraping(urls)

プロキシローテーションとレート制限

import random

def advanced_scraper_with_rotation():
    proxies_list = [
        {'http': 'http://proxy1:8080', 'https': 'https://proxy1:8080'},
        {'http': 'http://proxy2:8080', 'https': 'https://proxy2:8080'},
    ]
    
    user_agents = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
    ]
    
    def scrape_with_rotation(url):
        headers = {'User-Agent': random.choice(user_agents)}
        proxies = random.choice(proxies_list) if proxies_list else None
        
        response = requests.get(url, headers=headers, proxies=proxies)
        time.sleep(random.uniform(1, 3))  # ランダム待機
        return response.text
    
    return scrape_with_rotation

# 使用例
# scraper = advanced_scraper_with_rotation()
# content = scraper("https://example.com")

エキスパート編:データ永続化と分析

データベース連携とETL処理

import sqlite3

class ScrapingPipeline:
    def __init__(self, db_name="scraping_data.db"):
        self.conn = sqlite3.connect(db_name)
        self.setup_database()
    
    def setup_database(self):
        self.conn.execute('''
            CREATE TABLE IF NOT EXISTS scraped_data (
                id INTEGER PRIMARY KEY,
                url TEXT,
                title TEXT,
                content TEXT,
                scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
    
    def save_data(self, url, title, content):
        self.conn.execute(
            "INSERT INTO scraped_data (url, title, content) VALUES (?, ?, ?)",
            (url, title, content)
        )
        self.conn.commit()

# 使用例
pipeline = ScrapingPipeline()
pipeline.save_data("https://example.com", "Sample Title", "Sample Content")

自動監視とアラート機能

def monitoring_scraper(target_url, check_interval=3600):
    """Webサイトの変更を監視"""
    previous_content = ""
    
    while True:
        try:
            response = requests.get(target_url)
            current_content = response.text
            
            if previous_content and previous_content != current_content:
                print(f"変更検出: {target_url}")
                # アラート処理(メール送信など)
                
            previous_content = current_content
            time.sleep(check_interval)
            
        except Exception as e:
            print(f"監視エラー: {e}")
            time.sleep(300)  # エラー時は5分後に再試行

# バックグラウンドで実行
# monitoring_scraper("https://example.com/news")

スクレイピングにおける法的・倫理的配慮

robots.txtの確認とマナー

import urllib.robotparser

def check_robots_txt(url, user_agent='*'):
    """robots.txtの確認"""
    rp = urllib.robotparser.RobotFileParser()
    rp.set_url(f"{url}/robots.txt")
    rp.read()
    
    return rp.can_fetch(user_agent, url)

# 使用例
can_scrape = check_robots_txt("https://example.com")
if can_scrape:
    print("スクレイピング可能")
else:
    print("robots.txtによりスクレイピング禁止")

レスポンシブルスクレイピング

class ResponsibleScraper:
    def __init__(self, delay=1, max_requests_per_minute=30):
        self.delay = delay
        self.max_requests = max_requests_per_minute
        self.request_times = []
    
    def scrape(self, url):
        # レート制限チェック
        current_time = time.time()
        self.request_times = [t for t in self.request_times if current_time - t < 60]
        
        if len(self.request_times) >= self.max_requests:
            wait_time = 60 - (current_time - self.request_times[0])
            time.sleep(wait_time)
        
        response = requests.get(url)
        self.request_times.append(current_time)
        time.sleep(self.delay)
        
        return response

# 使用例
scraper = ResponsibleScraper(delay=2)
# response = scraper.scrape("https://example.com")

実践的なスクレイピングプロジェクト例

ニュースサイトのヘッドライン収集

def news_headline_scraper():
    news_sites = {
        "Site1": "https://example-news.com",
        "Site2": "https://another-news.com"
    }
    
    all_headlines = []
    
    for site_name, url in news_sites.items():
        try:
            response = requests.get(url)
            soup = BeautifulSoup(response.text, 'html.parser')
            
            headlines = soup.find_all(['h1', 'h2', 'h3'])[:5]
            for headline in headlines:
                all_headlines.append({
                    'site': site_name,
                    'title': headline.text.strip(),
                    'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
                })
        except Exception as e:
            print(f"{site_name} のスクレイピングでエラー: {e}")
    
    return pd.DataFrame(all_headlines)

# 使用例
# df = news_headline_scraper()
# print(df.head())

パフォーマンス最適化とトラブルシューティング

メモリ効率の改善

def memory_efficient_scraper(urls):
    """大量URLのメモリ効率的処理"""
    for url in urls:
        try:
            response = requests.get(url, stream=True)
            
            # ストリーミング処理でメモリ使用量を削減
            content = ""
            for chunk in response.iter_content(chunk_size=1024):
                if chunk:
                    content += chunk.decode('utf-8', errors='ignore')
                    
                    # 必要な部分のみを処理
                    if '</head>' in content:
                        break
            
            # 処理後は即座に解放
            soup = BeautifulSoup(content, 'html.parser')
            title = soup.find('title')
            yield {'url': url, 'title': title.text if title else None}
            
        except Exception as e:
            yield {'url': url, 'error': str(e)}

# 使用例
# for result in memory_efficient_scraper(large_url_list):
#     print(result)

スクレイピング結果の可視化と分析

import matplotlib.pyplot as plt

def analyze_scraping_results(data_frame):
    """スクレイピング結果の分析"""
    # 文字数分布
    data_frame['title_length'] = data_frame['title'].str.len()
    
    plt.figure(figsize=(10, 6))
    plt.hist(data_frame['title_length'], bins=20)
    plt.title('タイトル文字数分布')
    plt.xlabel('文字数')
    plt.ylabel('頻度')
    plt.show()
    
    # 統計情報
    stats = {
        'total_pages': len(data_frame),
        'avg_title_length': data_frame['title_length'].mean(),
        'most_common_words': data_frame['title'].str.split().explode().value_counts().head(5)
    }
    
    return stats

まとめ:効果的なWEBスクレイピングの実践

PythonによるWEBスクレイピングは、適切な技術と倫理的配慮を組み合わせることで、強力なデータ収集ツールとなります。基礎的なrequests + BeautifulSoupから始まり、Seleniumによる動的コンテンツ対応、並行処理による高速化まで、段階的にスキルを向上させることが重要です。

成功のためのポイント

  • 段階的学習: 基礎から応用まで順序立てて習得
  • 法的遵守: robots.txtやサイトの利用規約を必ず確認
  • レスポンシブル: サーバーに負荷をかけない配慮
  • エラー処理: 堅牢なエラーハンドリングの実装

継続的な学習と実践により、効率的で責任あるWEBスクレイピングスキルを身につけましょう。

らくらくPython塾 – 読むだけでマスター

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座