はじめに
このチュートリアルでは、Python の requests ライブラリで様々な HTTP ステータスコードを扱う方法を説明します。HTTP ステータスコードは、Web リクエストが成功したか失敗したかを理解し、様々なシナリオに適切に対応するために不可欠です。この実験(Lab)の終わりには、Web サービスと連携する Python アプリケーションで、堅牢なエラーハンドリングを実装する方法を習得できます。
まず、HTTP ステータスコードの理解から始め、基本的なエラーハンドリングと高度なエラーハンドリング技術を段階的に習得していきます。この知識は、様々なサーバーからのレスポンスを適切に処理する、信頼性の高い Python Web アプリケーションを開発するために不可欠です。
HTTP ステータスコードの理解とセットアップ
HTTP ステータスコードとは?
HTTP ステータスコードは、クライアントのリクエストに対する応答としてサーバーから返される 3 桁の数字です。これらは、特定の HTTP リクエストが正常に完了したかどうかを示します。これらのコードは、次の 5 つのカテゴリに分類されます。
- 1xx (Informational): リクエストは受信され、処理が継続中です
- 2xx (Success): リクエストは正常に受信、理解、および受け入れられました
- 3xx (Redirection): リクエストを完了するために、さらなるアクションが必要です
- 4xx (Client Error): リクエストには誤った構文が含まれているか、実行できません
- 5xx (Server Error): サーバーが、一見有効なリクエストの実行に失敗しました
よく遭遇するステータスコードには、以下のようなものがあります。
| ステータスコード | 名前 | 説明 |
|---|---|---|
| 200 | OK | リクエストは成功しました |
| 201 | Created | リクエストは成功し、リソースが作成されました |
| 400 | Bad Request | サーバーはリクエストを処理できません |
| 401 | Unauthorized | 認証が必要です |
| 404 | Not Found | 要求されたリソースが見つかりませんでした |
| 500 | Internal Server Error | サーバーで予期しない状態が発生しました |
Python 環境のセットアップ
HTTP ステータスコードの処理を開始する前に、Python の requests ライブラリをインストールし、簡単なテストスクリプトを作成しましょう。
- 実験(LabEx)環境でターミナルを開き、以下を実行します。
pip install requests
- プロジェクトファイル用の新しいディレクトリを作成します。
mkdir -p ~/project/http_status_lab
cd ~/project/http_status_lab
- WebIDE を使用して、
http_status_labディレクトリにtest_request.pyという名前の新しいファイルを作成し、次の基本的なコードを記述します。
import requests
def make_request(url):
"""Make a basic GET request to the specified URL."""
response = requests.get(url)
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text[:100]}...") ## Print first 100 characters
return response
## Test with a working URL
response = make_request("https://httpbin.org/status/200")
print(f"Request was successful: {response.ok}")
- テストスクリプトを実行して、動作を確認します。
python test_request.py
次のような出力が表示されるはずです。
Status Code: 200
Response: ...
Request was successful: True
これにより、Python 環境が正常にセットアップされ、基本的な HTTP リクエストを実行できることが確認されました。次のステップでは、様々な HTTP ステータスコードを処理し、より高度なエラーハンドリング技術を実装する方法を学びます。
基本的な HTTP ステータスコードの処理
HTTP ステータスコードが何かを理解し、環境をセットアップしたので、一般的なステータスコードに対する基本的なエラーハンドリングを実装しましょう。
ステータスコードテストツールの作成
まず、様々な HTTP ステータスコードをテストできるツールを作成しましょう。Web サイト httpbin.org は、特定のステータスコードを返すエンドポイントを提供しており、テストに最適です。
http_status_lab ディレクトリに、次のコードを含む status_code_tester.py という名前の新しいファイルを作成します。
import requests
import sys
def test_status_code(status_code):
"""Test a specific HTTP status code using httpbin.org."""
url = f"https://httpbin.org/status/{status_code}"
try:
response = requests.get(url)
print(f"Status Code: {response.status_code}")
print(f"Response OK: {response.ok}")
return response
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
if __name__ == "__main__":
## Get status code from command line or use default
status_code = sys.argv[1] if len(sys.argv) > 1 else "200"
test_status_code(status_code)
このスクリプトは、特定のステータスコードで httpbin.org にリクエストを送信し、その結果を出力します。コマンドライン引数を使用して、様々なステータスコードをテストできます。
様々なステータスコードのテスト
スクリプトを様々なステータスコードでテストし、requests ライブラリがどのように処理するかを確認しましょう。
- 成功したリクエスト (200 OK) をテストします。
python status_code_tester.py 200
期待される出力:
Status Code: 200
Response OK: True
- クライアントエラー (404 Not Found) をテストします。
python status_code_tester.py 404
期待される出力:
Status Code: 404
Response OK: False
- サーバーエラー (500 Internal Server Error) をテストします。
python status_code_tester.py 500
期待される出力:
Status Code: 500
Response OK: False
基本的なステータスコードカテゴリの処理
次に、様々なカテゴリのステータスコードを適切に処理する、より包括的なスクリプトを作成しましょう。http_status_lab ディレクトリに、basic_handler.py という名前の新しいファイルを作成します。
import requests
def handle_response(url):
"""Handle different HTTP status codes with basic error handling."""
try:
response = requests.get(url)
## Check status code category
if 200 <= response.status_code < 300:
print(f"Success! Status code: {response.status_code}")
return response
elif 300 <= response.status_code < 400:
print(f"Redirection! Status code: {response.status_code}")
## For redirection, you might want to follow the redirect
return response
elif 400 <= response.status_code < 500:
print(f"Client error! Status code: {response.status_code}")
## Handle client errors
return response
elif 500 <= response.status_code < 600:
print(f"Server error! Status code: {response.status_code}")
## Handle server errors
return response
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
## Test with different status codes
print("Testing 200 OK:")
handle_response("https://httpbin.org/status/200")
print("\nTesting 404 Not Found:")
handle_response("https://httpbin.org/status/404")
print("\nTesting 500 Internal Server Error:")
handle_response("https://httpbin.org/status/500")
スクリプトを実行して、様々なステータスコードがどのように処理されるかを確認します。
python basic_handler.py
期待される出力:
Testing 200 OK:
Success! Status code: 200
Testing 404 Not Found:
Client error! Status code: 404
Testing 500 Internal Server Error:
Server error! Status code: 500
この基本的なハンドラーは、ステータスコードをカテゴリにグループ化し、カテゴリに基づいて異なるアクションを実行します。これは、Python Web アプリケーションで一般的なパターンであり、様々な種類のレスポンスを適切に処理できます。
高度なエラーハンドリングの実装
HTTP ステータスコードの基本的な処理を理解したので、Python アプリケーション向けに、より高度なエラーハンドリング技術を実装しましょう。
raise_for_status() の使用
requests ライブラリには、4xx および 5xx ステータスコードに対して例外を発生させる便利なメソッド raise_for_status() があります。これは、コード内でエラーを処理するためのシンプルかつ効果的な方法です。
http_status_lab ディレクトリに、raise_for_status_example.py という名前の新しいファイルを作成します。
import requests
def fetch_data(url):
"""Fetch data from a URL with error handling using raise_for_status()."""
try:
response = requests.get(url)
## This will raise an HTTPError if the HTTP request returned an unsuccessful status code
response.raise_for_status()
## If we get here, the request was successful
print(f"Success! Status code: {response.status_code}")
return response.json() if 'application/json' in response.headers.get('Content-Type', '') else response.text
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
return None
except requests.exceptions.ConnectionError as e:
print(f"Connection Error: {e}")
return None
except requests.exceptions.Timeout as e:
print(f"Timeout Error: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"Request Exception: {e}")
return None
## Test with different URLs
print("Testing successful request:")
data = fetch_data("https://httpbin.org/json")
print(f"Received data type: {type(data)}")
print("\nTesting 404 error:")
data = fetch_data("https://httpbin.org/status/404")
print(f"Received data: {data}")
print("\nTesting 500 error:")
data = fetch_data("https://httpbin.org/status/500")
print(f"Received data: {data}")
スクリプトを実行して、raise_for_status() がどのようにエラーを処理するかを確認します。
python raise_for_status_example.py
期待される出力は、ライブラリが 2xx 以外のステータスコードに対して自動的に例外を発生させ、コードがそれをキャッチして適切に処理できることを示します。
カスタム例外ハンドラーの作成
より複雑なアプリケーションでは、より具体的なエラーハンドリングを提供するために、カスタム例外ハンドラーを作成することがあります。より高度な例を作成しましょう。
http_status_lab ディレクトリに、custom_exception_handler.py という名前の新しいファイルを作成します。
import requests
import logging
## Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
## Custom exceptions
class APIError(Exception):
"""Base class for API request exceptions."""
pass
class ClientError(APIError):
"""Exception raised for client errors (4xx)."""
pass
class ServerError(APIError):
"""Exception raised for server errors (5xx)."""
pass
class NotFoundError(ClientError):
"""Exception raised for 404 Not Found errors."""
pass
class UnauthorizedError(ClientError):
"""Exception raised for 401 Unauthorized errors."""
pass
def api_request(url, method="get", **kwargs):
"""Make an API request with comprehensive error handling."""
try:
## Make the request using the specified HTTP method
response = getattr(requests, method.lower())(url, **kwargs)
## Log the response status
logger.info(f"Request to {url} returned status code {response.status_code}")
## Handle different status codes
if 200 <= response.status_code < 300:
return response
elif response.status_code == 401:
raise UnauthorizedError(f"Authentication required: {response.text}")
elif response.status_code == 404:
raise NotFoundError(f"Resource not found: {url}")
elif 400 <= response.status_code < 500:
raise ClientError(f"Client error {response.status_code}: {response.text}")
elif 500 <= response.status_code < 600:
raise ServerError(f"Server error {response.status_code}: {response.text}")
else:
raise APIError(f"Unexpected status code {response.status_code}: {response.text}")
except requests.exceptions.RequestException as e:
logger.error(f"Request failed: {e}")
raise APIError(f"Request failed: {e}")
## Test the custom exception handler
def test_api_request(url):
"""Test the api_request function with error handling."""
try:
response = api_request(url)
print(f"Success! Status code: {response.status_code}")
print(f"Response: {response.text[:100]}...") ## Print first 100 characters
return response
except UnauthorizedError as e:
print(f"Authentication Error: {e}")
except NotFoundError as e:
print(f"Not Found Error: {e}")
except ClientError as e:
print(f"Client Error: {e}")
except ServerError as e:
print(f"Server Error: {e}")
except APIError as e:
print(f"API Error: {e}")
return None
## Test with different status codes
print("Testing 200 OK:")
test_api_request("https://httpbin.org/status/200")
print("\nTesting 404 Not Found:")
test_api_request("https://httpbin.org/status/404")
print("\nTesting 401 Unauthorized:")
test_api_request("https://httpbin.org/status/401")
print("\nTesting 500 Internal Server Error:")
test_api_request("https://httpbin.org/status/500")
スクリプトを実行して、カスタム例外ハンドリングがどのように機能するかを確認します。
python custom_exception_handler.py
この高度な例は、いくつかの重要な概念を示しています。
- 基本的な
Exceptionクラスを拡張するカスタム例外クラス - 様々な種類のエラーに対する階層的な例外
- デバッグと監視のための詳細なロギング
- 様々なステータスコードに対する具体的な処理
このアプローチにより、様々なエラータイプをより構造化され、保守性の高い方法で処理できるようになり、大規模なアプリケーションには不可欠です。
完全な Web API クライアントの構築
この最終ステップでは、学習したすべてのことを組み合わせて、堅牢なエラーハンドリングを備えた完全な Web API クライアントを構築します。テストに使用できる無料のオンライン REST API である JSONPlaceholder API のクライアントを作成します。
簡単な API クライアントの作成
http_status_lab ディレクトリに、api_client.py という名前のファイルを作成しましょう。
import requests
import json
import logging
## Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class APIClient:
"""A simple API client with robust error handling."""
def __init__(self, base_url):
"""Initialize the API client with a base URL."""
self.base_url = base_url
self.session = requests.Session()
def request(self, endpoint, method="get", params=None, data=None, headers=None):
"""Make a request to the API with error handling."""
url = f"{self.base_url}{endpoint}"
## Default headers for JSON APIs
if headers is None:
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
## Convert data to JSON if it's a dictionary
json_data = None
if data and isinstance(data, dict):
json_data = data
data = None
try:
## Log the request
logger.info(f"Making {method.upper()} request to {url}")
## Make the request
response = self.session.request(
method=method,
url=url,
params=params,
data=data,
json=json_data,
headers=headers
)
## Log the response status
logger.info(f"Received response with status code {response.status_code}")
## Check for HTTP errors
response.raise_for_status()
## Parse JSON response if applicable
try:
return response.json()
except json.JSONDecodeError:
return response.text
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
error_message = f"HTTP Error: {status_code}"
try:
## Try to get more details from the response
error_data = e.response.json()
error_message = f"{error_message} - {error_data.get('message', str(error_data))}"
except (json.JSONDecodeError, AttributeError):
error_message = f"{error_message} - {e.response.text if hasattr(e, 'response') else str(e)}"
logger.error(error_message)
## Re-raise with more context
if 400 <= status_code < 500:
logger.error(f"Client error: {error_message}")
raise Exception(f"Client error (HTTP {status_code}): {error_message}")
elif 500 <= status_code < 600:
logger.error(f"Server error: {error_message}")
raise Exception(f"Server error (HTTP {status_code}): {error_message}")
else:
raise
except requests.exceptions.ConnectionError as e:
logger.error(f"Connection Error: {e}")
raise Exception(f"Connection Error: Could not connect to {url}")
except requests.exceptions.Timeout as e:
logger.error(f"Timeout Error: {e}")
raise Exception(f"Timeout Error: Request to {url} timed out")
except requests.exceptions.RequestException as e:
logger.error(f"Request Exception: {e}")
raise Exception(f"Request Error: {str(e)}")
def get(self, endpoint, params=None, headers=None):
"""Make a GET request to the API."""
return self.request(endpoint, method="get", params=params, headers=headers)
def post(self, endpoint, data=None, headers=None):
"""Make a POST request to the API."""
return self.request(endpoint, method="post", data=data, headers=headers)
def put(self, endpoint, data=None, headers=None):
"""Make a PUT request to the API."""
return self.request(endpoint, method="put", data=data, headers=headers)
def delete(self, endpoint, headers=None):
"""Make a DELETE request to the API."""
return self.request(endpoint, method="delete", headers=headers)
## Test the API client with JSONPlaceholder
def test_api_client():
"""Test the API client with JSONPlaceholder."""
client = APIClient("https://jsonplaceholder.typicode.com")
try:
## Get a list of posts
print("\nGetting posts:")
posts = client.get("/posts")
print(f"Retrieved {len(posts)} posts")
print(f"First post: {posts[0]}")
## Get a single post
print("\nGetting a single post:")
post = client.get("/posts/1")
print(f"Retrieved post: {post}")
## Get a non-existent post (404)
print("\nTrying to get a non-existent post:")
try:
client.get("/posts/999999")
except Exception as e:
print(f"Expected error: {e}")
## Create a new post
print("\nCreating a new post:")
new_post = client.post("/posts", {
"title": "My New Post",
"body": "This is the content of my new post.",
"userId": 1
})
print(f"Created post: {new_post}")
## Update a post
print("\nUpdating a post:")
updated_post = client.put("/posts/1", {
"id": 1,
"title": "Updated Title",
"body": "Updated content.",
"userId": 1
})
print(f"Updated post: {updated_post}")
## Delete a post
print("\nDeleting a post:")
delete_response = client.delete("/posts/1")
print(f"Delete response: {delete_response}")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
test_api_client()
スクリプトを実行して、完全な API クライアントが様々なシナリオをどのように処理するかを確認します。
python api_client.py
エラーハンドリングのテスト
API クライアントのエラーハンドリング機能をテストするための別のファイルを作成しましょう。http_status_lab ディレクトリに、test_error_handling.py という名前のファイルを作成します。
from api_client import APIClient
import time
def test_different_errors():
"""Test different error scenarios with our API client."""
## Test with a valid API
print("\n1. Testing with a valid API:")
valid_client = APIClient("https://jsonplaceholder.typicode.com")
try:
data = valid_client.get("/posts/1")
print(f"Success! Retrieved data: {data}")
except Exception as e:
print(f"Unexpected error: {e}")
## Test with a 404 error
print("\n2. Testing with a non-existent endpoint (404):")
try:
valid_client.get("/non_existent_endpoint")
except Exception as e:
print(f"Expected error: {e}")
## Test with an invalid host
print("\n3. Testing with an invalid host (Connection Error):")
invalid_client = APIClient("https://this-does-not-exist-123456789.com")
try:
invalid_client.get("/anything")
except Exception as e:
print(f"Expected error: {e}")
## Test with a timeout
print("\n4. Testing with a timeout:")
timeout_client = APIClient("https://httpbin.org")
try:
## httpbin.org/delay/5 will delay the response for 5 seconds
## but we'll set the timeout to 2 seconds
timeout_client.session.request = lambda **kwargs: timeout_client.session.request_original(
**{**kwargs, 'timeout': 2}
)
timeout_client.session.request_original = timeout_client.session.request
timeout_client.get("/delay/5")
except Exception as e:
print(f"Expected error: {e}")
if __name__ == "__main__":
test_different_errors()
上記のスクリプトは、実行時にメソッドを変更しようとしているため、タイムアウト処理が原因でエラーが発生する可能性があります。これらの問題を回避するために、これを簡素化しましょう。
from api_client import APIClient
def test_different_errors():
"""Test different error scenarios with our API client."""
## Test with a valid API
print("\n1. Testing with a valid API:")
valid_client = APIClient("https://jsonplaceholder.typicode.com")
try:
data = valid_client.get("/posts/1")
print(f"Success! Retrieved data: {data}")
except Exception as e:
print(f"Unexpected error: {e}")
## Test with a 404 error
print("\n2. Testing with a non-existent endpoint (404):")
try:
valid_client.get("/non_existent_endpoint")
except Exception as e:
print(f"Expected error: {e}")
## Test with a server error
print("\n3. Testing with a server error (500):")
error_client = APIClient("https://httpbin.org")
try:
error_client.get("/status/500")
except Exception as e:
print(f"Expected error: {e}")
if __name__ == "__main__":
test_different_errors()
エラーテストスクリプトを実行します。
python test_error_handling.py
このスクリプトは、API クライアントが様々なエラーシナリオをどのように処理するかを示し、実際のアプリケーションのための堅牢な基盤を提供します。
主なポイント
この完全な API クライアントを構築することで、いくつかの重要な技術を学びました。
- 再利用可能な API クライアントクラスの作成
- 包括的なエラーハンドリングの実装
- デバッグのためのリクエストとレスポンスのロギング
- 様々なレスポンス形式の解析
- 様々な HTTP メソッド (GET、POST、PUT、DELETE) の処理
これらのスキルは、Web API と対話する堅牢な Python アプリケーションを構築するために不可欠であり、コードが様々なエラーシナリオを適切に処理し、ユーザーに意味のあるエラーメッセージを提供できるようにします。
まとめ
この実験では、Python のリクエストにおける HTTP ステータスコードの処理方法を学び、基本的なものから高度なテクニックへと進みました。これで、以下のスキルを習得しました。
- HTTP ステータスコードのさまざまなカテゴリとその意味を理解する
- ステータスコードチェックによる基本的なエラーハンドリングを実装する
- 簡単なエラーハンドリングに
raise_for_status()を使用する - より具体的なエラーハンドリングのためにカスタム例外クラスを作成する
- 包括的なエラーハンドリングを備えた完全な API クライアントを構築する
これらのスキルは、Web サービスと対話する堅牢な Python アプリケーションを開発するために不可欠です。HTTP ステータスコードとエラーを適切に処理することで、より信頼性の高いアプリケーションを作成し、より良いユーザーエクスペリエンスを提供し、保守を容易にすることができます。
Python アプリケーションで HTTP リクエストを扱う際には、常にエラーハンドリングを考慮してください。これは、プロフェッショナルなコードとアマチュア的な実装を区別する、Web 開発の重要な側面です。



