はじめに:APIが開くデータの世界

現代のWebサービスの核心はAPI(Application Programming Interface)です。APIを通じて、天気情報、株価、ニュース、ソーシャルメディアデータなど、様々な情報をプログラム的に収集・活用できます。Pythonのrequestsライブラリを使用すると、このようなAPI呼び出しを非常に簡単に処理できます。

今回の編では、REST APIの基本概念から始めて、様々な認証方式、実践的なAPI活用法、そして効率的なデータ収集戦略まで体系的に学習します。

1. API基礎:REST APIの理解

1.1 APIとは何か?

APIは異なるソフトウェア間の通信を可能にするインターフェースです。レストランに例えると、APIはお客様(クライアント)と厨房(サーバー)の間のウェイター役を果たします。お客様がメニューを注文するとウェイターが厨房に伝え、料理が完成するとお客様に届けるようなものです。

# APIリクエストの基本構造
import requests

# 1. リクエストを送信
response = requests.get("https://api.example.com/data")

# 2. レスポンスを受け取る
if response.status_code == 200:
    data = response.json()
    print(data)
else:
    print(f"エラー発生: {response.status_code}")

1.2 REST APIの核心概念

REST(Representational State Transfer)はWeb API設計の標準アーキテクチャです。

# HTTPメソッド別の用途
"""
GET    : データ取得 (Read)
POST   : データ作成 (Create)
PUT    : データ全体更新 (Update)
PATCH  : データ部分更新 (Partial Update)
DELETE : データ削除 (Delete)
"""

import requests

base_url = "https://api.example.com"

# GET - データ取得
response = requests.get(f"{base_url}/users")

# POST - データ作成
new_user = {"name": "田中太郎", "email": "tanaka@example.com"}
response = requests.post(f"{base_url}/users", json=new_user)

# PUT - データ更新
updated_user = {"name": "田中太郎", "email": "newemail@example.com"}
response = requests.put(f"{base_url}/users/1", json=updated_user)

# DELETE - データ削除
response = requests.delete(f"{base_url}/users/1")

1.3 HTTPステータスコードの理解

# 主要HTTPステータスコード
status_codes = {
    # 2xx: 成功
    200: "OK - リクエスト成功",
    201: "Created - 作成成功",
    204: "No Content - 成功(レスポンス本文なし)",

    # 3xx: リダイレクション
    301: "Moved Permanently - 永久移動",
    302: "Found - 一時移動",

    # 4xx: クライアントエラー
    400: "Bad Request - 不正なリクエスト",
    401: "Unauthorized - 認証が必要",
    403: "Forbidden - アクセス拒否",
    404: "Not Found - リソースなし",
    429: "Too Many Requests - リクエスト回数超過",

    # 5xx: サーバーエラー
    500: "Internal Server Error - サーバー内部エラー",
    502: "Bad Gateway - ゲートウェイエラー",
    503: "Service Unavailable - サービス利用不可"
}

def handle_response(response):
    """レスポンスステータスコードに応じた処理"""
    code = response.status_code

    if 200 <= code < 300:
        print(f"成功: {status_codes.get(code, '不明な成功コード')}")
        return response.json() if response.content else None
    elif 400 <= code < 500:
        print(f"クライアントエラー: {status_codes.get(code, '不明なエラー')}")
        return None
    elif 500 <= code < 600:
        print(f"サーバーエラー: {status_codes.get(code, '不明なサーバーエラー')}")
        return None

2. requestsライブラリ完全ガイド

2.1 基本的な使い方

import requests

# インストール: pip install requests

# GET リクエストの基本
response = requests.get("https://api.github.com/users/octocat")
print(response.status_code)  # 200
print(response.headers)       # レスポンスヘッダー
print(response.text)          # テキスト形式のレスポンス
print(response.json())        # JSONパースされた辞書

# URLパラメータの渡し方
params = {
    "q": "python",
    "sort": "stars",
    "order": "desc"
}
response = requests.get(
    "https://api.github.com/search/repositories",
    params=params
)
# 実際のリクエストURL: https://api.github.com/search/repositories?q=python&sort=stars&order=desc

2.2 ヘッダーとタイムアウト設定

import requests

# カスタムヘッダー設定
headers = {
    "User-Agent": "MyApp/1.0",
    "Accept": "application/json",
    "Content-Type": "application/json"
}

# タイムアウト設定(接続タイムアウト、読み取りタイムアウト)
try:
    response = requests.get(
        "https://api.example.com/data",
        headers=headers,
        timeout=(5, 30)  # 接続: 5秒、読み取り: 30秒
    )
except requests.exceptions.Timeout:
    print("リクエストがタイムアウトしました。")
except requests.exceptions.ConnectionError:
    print("接続に失敗しました。")
except requests.exceptions.RequestException as e:
    print(f"リクエスト中にエラーが発生: {e}")

2.3 セッションとCookie管理

import requests

# セッションを使用(Cookie自動管理)
session = requests.Session()

# ログイン
login_data = {"username": "user", "password": "pass"}
session.post("https://example.com/login", data=login_data)

# ログイン後のリクエスト(Cookie自動送信)
response = session.get("https://example.com/dashboard")

# セッションヘッダーの設定(すべてのリクエストに適用)
session.headers.update({
    "Authorization": "Bearer token123",
    "User-Agent": "MyApp/1.0"
})

3. API認証方式

3.1 APIキー認証

import requests

# ヘッダーにAPIキーを含める
headers = {
    "X-API-Key": "your_api_key_here"
}
response = requests.get("https://api.example.com/data", headers=headers)

# URLパラメータにAPIキーを含める
params = {
    "api_key": "your_api_key_here",
    "query": "search_term"
}
response = requests.get("https://api.example.com/search", params=params)

3.2 Bearer Token認証

import requests

# Bearer Token認証
access_token = "your_access_token"
headers = {
    "Authorization": f"Bearer {access_token}"
}

response = requests.get(
    "https://api.example.com/protected/resource",
    headers=headers
)

3.3 OAuth 2.0認証

import requests

class OAuth2Client:
    """OAuth 2.0クライアント"""

    def __init__(self, client_id, client_secret, token_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.token_url = token_url
        self.access_token = None

    def get_access_token(self):
        """アクセストークンを取得"""
        data = {
            "grant_type": "client_credentials",
            "client_id": self.client_id,
            "client_secret": self.client_secret
        }

        response = requests.post(self.token_url, data=data)
        if response.status_code == 200:
            token_data = response.json()
            self.access_token = token_data["access_token"]
            return self.access_token
        return None

    def make_request(self, url, method="GET", **kwargs):
        """認証済みリクエストを送信"""
        if not self.access_token:
            self.get_access_token()

        headers = kwargs.get("headers", {})
        headers["Authorization"] = f"Bearer {self.access_token}"
        kwargs["headers"] = headers

        response = requests.request(method, url, **kwargs)

        # トークン期限切れの場合、再取得して再試行
        if response.status_code == 401:
            self.get_access_token()
            headers["Authorization"] = f"Bearer {self.access_token}"
            response = requests.request(method, url, **kwargs)

        return response

4. 天気API活用例

import requests
from datetime import datetime

class WeatherAPI:
    """OpenWeatherMap API クラス"""

    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = "https://api.openweathermap.org/data/2.5"

    def get_current_weather(self, city):
        """現在の天気を取得"""
        params = {
            "q": city,
            "appid": self.api_key,
            "units": "metric",  # 摂氏
            "lang": "ja"
        }

        response = requests.get(f"{self.base_url}/weather", params=params)

        if response.status_code == 200:
            data = response.json()
            return {
                "city": data["name"],
                "temperature": data["main"]["temp"],
                "feels_like": data["main"]["feels_like"],
                "humidity": data["main"]["humidity"],
                "description": data["weather"][0]["description"],
                "wind_speed": data["wind"]["speed"]
            }
        return None

    def get_forecast(self, city, days=5):
        """天気予報を取得"""
        params = {
            "q": city,
            "appid": self.api_key,
            "units": "metric",
            "lang": "ja",
            "cnt": days * 8  # 3時間ごとのデータ
        }

        response = requests.get(f"{self.base_url}/forecast", params=params)

        if response.status_code == 200:
            data = response.json()
            forecasts = []
            for item in data["list"]:
                forecasts.append({
                    "datetime": item["dt_txt"],
                    "temp": item["main"]["temp"],
                    "description": item["weather"][0]["description"]
                })
            return forecasts
        return None

# 使用例
weather = WeatherAPI("YOUR_API_KEY")
current = weather.get_current_weather("Tokyo")
print(f"東京の現在の天気: {current['temperature']}°C, {current['description']}")

5. OpenAI API連携

import requests

class OpenAIClient:
    """OpenAI API クライアント"""

    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = "https://api.openai.com/v1"
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }

    def chat_completion(self, messages, model="gpt-4", temperature=0.7):
        """チャット完了APIを呼び出す"""
        url = f"{self.base_url}/chat/completions"

        payload = {
            "model": model,
            "messages": messages,
            "temperature": temperature
        }

        response = requests.post(url, headers=self.headers, json=payload)

        if response.status_code == 200:
            data = response.json()
            return data["choices"][0]["message"]["content"]
        else:
            print(f"エラー: {response.status_code}")
            print(response.text)
            return None

    def generate_summary(self, text, max_length=100):
        """テキストを要約"""
        messages = [
            {"role": "system", "content": "あなたは優秀な要約者です。与えられたテキストを簡潔に要約してください。"},
            {"role": "user", "content": f"以下のテキストを{max_length}文字以内で要約してください:\n\n{text}"}
        ]

        return self.chat_completion(messages)

    def translate(self, text, target_language="日本語"):
        """テキストを翻訳"""
        messages = [
            {"role": "system", "content": f"あなたは優秀な翻訳者です。与えられたテキストを{target_language}に翻訳してください。"},
            {"role": "user", "content": text}
        ]

        return self.chat_completion(messages)

# 使用例
openai = OpenAIClient("YOUR_API_KEY")

# チャット
response = openai.chat_completion([
    {"role": "user", "content": "Pythonの特徴を3つ教えてください。"}
])
print(response)

6. 効率的なデータ収集

import requests
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

class DataCollector:
    """効率的なデータ収集クラス"""

    def __init__(self, max_workers=5, rate_limit=1.0):
        self.max_workers = max_workers
        self.rate_limit = rate_limit  # 秒あたりのリクエスト数
        self.last_request_time = 0

    def _rate_limited_request(self, url, **kwargs):
        """レート制限付きリクエスト"""
        elapsed = time.time() - self.last_request_time
        wait_time = (1.0 / self.rate_limit) - elapsed

        if wait_time > 0:
            time.sleep(wait_time)

        self.last_request_time = time.time()
        return requests.get(url, **kwargs)

    def collect_sequential(self, urls):
        """順次データ収集"""
        results = []
        for url in urls:
            try:
                response = self._rate_limited_request(url)
                if response.status_code == 200:
                    results.append(response.json())
            except Exception as e:
                print(f"エラー ({url}): {e}")
        return results

    def collect_parallel(self, urls):
        """並列データ収集"""
        results = []

        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            future_to_url = {
                executor.submit(requests.get, url): url
                for url in urls
            }

            for future in as_completed(future_to_url):
                url = future_to_url[future]
                try:
                    response = future.result()
                    if response.status_code == 200:
                        results.append(response.json())
                except Exception as e:
                    print(f"エラー ({url}): {e}")

        return results

# 使用例
collector = DataCollector(max_workers=5, rate_limit=2.0)
urls = [f"https://api.example.com/item/{i}" for i in range(100)]
data = collector.collect_parallel(urls)

まとめ

今回の編では、PythonでのAPI活用とデータ収集について学びました。

  • REST API:HTTP メソッド(GET, POST, PUT, DELETE)の活用
  • requestsライブラリ:Pythonで最も人気のあるHTTPクライアント
  • 認証方式:APIキー、Bearer Token、OAuth 2.0
  • 実践API:天気API、OpenAI APIなど
  • 効率的な収集:レート制限、並列処理

APIは現代のソフトウェア開発において欠かせない技術です。様々なAPIを活用して自動化スクリプトを強化してください。次回の最終編では、スケジューリングと実践プロジェクトについて学びます。