Pythonで簡単!curlコマンドをPythonコードにする方法


Pythonで簡単!curlコマンドをPythonコードにする方法

はじめに:なぜcurlコマンドをPythonで?

あなたはWeb開発者ですか?それともAPIを使ったデータ収集や連携をよく行いますか?もしそうであれば、きっとcurlコマンドを使った経験があるでしょう。

curlは、コマンドラインから様々なプロトコル(HTTP, HTTPS, FTPなど)を使ってデータを送受信するための非常に強力で柔軟なツールです。Web APIのテスト、ファイルのダウンロード、サーバー状態の確認など、その用途は多岐にわたります。コマンド一つで手軽にHTTPリクエストを送信できるため、開発やデバッグの現場で広く利用されています。

しかし、curlコマンドにも限界があります。複数のリクエストを連続して送りたい場合、リクエストの結果を元に次の処理を変えたい場合、取得したデータを整形・分析したい場合など、より複雑な処理を行うには、コマンドを羅列するだけでは難しくなります。シェルスクリプトで記述することも可能ですが、データの操作や条件分岐、エラーハンドリングなどは、よりプログラミング言語が得意とするところです。

ここでPythonの出番です。Pythonは汎用性の高いプログラミング言語であり、特にWeb開発、データ分析、自動化の分野で強力な力を発揮します。Pythonを使えば、curlコマンドで行っていたHTTPリクエストの送信を、プログラムとしてより柔軟に、より複雑なロジックと組み合わせて実現できます。

この記事では、curlコマンドの機能をPythonコードに変換するための主要な方法を、詳細なコード例とともに解説します。具体的には、以下の3つのアプローチを中心に見ていきます。

  1. requestsライブラリを使う方法: PythonでHTTP通信を行うためのデファクトスタンダードとも言える、最も推奨される方法です。直感的で使いやすく、ほとんどのcurlの機能を簡単に実現できます。
  2. subprocessモジュールを使う方法: Pythonスクリプトの中から直接OSのcurlコマンドを実行する方法です。既存の複雑なcurlコマンドをそのまま利用したい場合などに有効です。
  3. urllibライブラリを使う方法: Pythonの標準ライブラリに含まれるモジュールです。外部ライブラリのインストールが不要な場合に選択肢となりますが、通常はrequestsの方が使いやすいです。

この記事を読み終える頃には、あなたはcurlコマンドの各オプションがPythonのどのコードに対応するのかを理解し、手作業で実行していたcurlコマンドをPythonスクリプトとして自動化できるようになるでしょう。さあ、curlの世界をPythonで拡張しましょう!

curlコマンドの基礎をおさらい

Pythonでの実装に入る前に、よく使われるcurlコマンドの基本的な使い方と主要なオプションを簡単におさらいしておきましょう。これにより、それぞれのオプションがPythonでどのように表現されるのかを理解しやすくなります。

基本的なcurlコマンドは、対象のURLを指定するだけです。デフォルトではGETリクエストが送信され、そのレスポンスボディが標準出力に表示されます。

bash
curl https://example.com

よく使うcurlオプションとその役割

  • -X <METHOD>: HTTPメソッドを指定します(例: -X POST, -X PUT)。デフォルトはGETです。
  • -H "<Header-Name: Header-Value>": リクエストヘッダーを追加します。複数指定可能です。
  • -d "<data>": リクエストボディとしてデータを送信します。POSTやPUTリクエストでよく使われます。デフォルトではContent-Type: application/x-www-form-urlencodedとして送信されます。
  • -F "<name>=<value>": フォームデータを送信します。ファイルアップロードには-F "name=@/path/to/file"のように指定します。デフォルトではContent-Type: multipart/form-dataとして送信されます。
  • -u "<username>:<password>": Basic認証のためのユーザー名とパスワードを指定します。
  • -O または -o <output-file>: レスポンスボディをファイルに保存します。-OはURLからファイル名を推測し、-oは指定したファイル名で保存します。
  • -L: リダイレクトが発生した場合に、自動的に追跡します。
  • -k: SSL証明書の検証を無効にします。開発時など、自己署名証明書を使っているHTTPSサイトにアクセスする場合に便利ですが、セキュリティ上のリスクを伴います。
  • --connect-timeout <seconds>: 接続を確立する際の最大待ち時間(秒)を指定します。
  • --max-time <seconds>: リクエスト全体の最大実行時間(秒)を指定します。
  • -A "<User-Agent>": User-Agentヘッダーを指定します。-H "User-Agent: ..."と同じです。
  • --cookie "<name>=<value>" または -b <file>: 送信するクッキーを指定します。-b <file>でファイルからクッキーを読み込むこともできます。
  • --data-urlencode "<name>=<value>": データをURLエンコードして送信します。-dと同様にPOSTなどで使われます。

これらのオプションは、組み合わせて使用することで、非常に複雑なHTTPリクエストもコマンド一つで実行できます。次のセクションからは、これらの機能をPythonでどのように実現するのかを見ていきましょう。

PythonでHTTPリクエストを送る主要な方法

前述したように、PythonでHTTPリクエストを送信する方法はいくつかありますが、主に以下の3つがよく使われます。

  1. requestsライブラリ: 外部ライブラリですが、非常に高機能で使いやすく、PythonでのHTTP通信の標準的な選択肢となっています。ほとんどの場合、このライブラリを使用するのが最も推奨されます。
  2. subprocessモジュール: Pythonの標準ライブラリに含まれており、OSのコマンドを実行できます。これにより、システムにインストールされているcurlコマンド自体をPythonスクリプトから呼び出すことが可能です。
  3. urllibライブラリ: Pythonの標準ライブラリに含まれる、より低レベルなHTTP通信モジュールです。requestsが登場する以前はよく使われていましたが、現在ではrequestsの方がより簡単で直感的に扱えます。

それでは、それぞれの方法について詳しく見ていきましょう。

方法1: requestsライブラリを使う(最も推奨)

PythonでHTTPリクエストを扱うなら、まずrequestsライブラリを検討すべきです。使いやすさ、機能の豊富さ、メンテナンス性において、他の方法よりも優れている点が多いからです。

requestsライブラリはPythonの標準ライブラリではないため、まずインストールが必要です。pipを使って簡単にインストールできます。

bash
pip install requests

インストールが完了したら、早速curlコマンドをrequestsに変換してみましょう。

基本的なGETリクエスト

最も単純なcurlコマンドは、URLを指定するGETリクエストです。

bash
curl https://httpbin.org/get

これをrequestsで実行するには、requests.get()関数を使います。

“`python
import requests

url = ‘https://httpbin.org/get’
response = requests.get(url)

レスポンス情報を表示

print(f”Status Code: {response.status_code}”)
print(f”Headers: {response.headers}”)
print(f”Body: {response.text}”) # または response.json() if the body is JSON
“`

requests.get()Responseオブジェクトを返します。このオブジェクトからステータスコード (response.status_code)、レスポンスヘッダー (response.headers)、レスポンスボディ (response.textまたはresponse.content、JSONの場合はresponse.json()) など、様々な情報にアクセスできます。

クエリパラメータ付きGETリクエスト

curlコマンドでURLにクエリパラメータを含める場合、通常はURL文字列として直接記述します。

bash
curl "https://httpbin.org/get?param1=value1&param2=value2"

requestsでは、クエリパラメータを辞書形式でparams引数に渡すのがより一般的で推奨される方法です。これにより、パラメータの値に特殊文字が含まれていても適切にエンコードされます。

“`python
import requests

url = ‘https://httpbin.org/get’
params = {
‘param1’: ‘value1’,
‘param2’: ‘value2’
}

response = requests.get(url, params=params)

print(f”Status Code: {response.status_code}”)
print(f”Requested URL: {response.url}”) # エンコードされた完全なURL
print(f”Body: {response.json()}”) # httpbin.orgはリクエスト情報をJSONで返す
“`

response.urlを確認すると、params辞書が適切にURLエンコードされてURLに追加されていることがわかります。

ヘッダー付きリクエスト (-H)

curl-Hオプションを使ってリクエストヘッダーを追加する場合、requestsではheaders引数に辞書形式で指定します。

例えば、特定のUser-Agentを設定したり、カスタムヘッダーを追加したりする場合です。

bash
curl -H "User-Agent: MyCustomClient/1.0" -H "X-Custom-Header: SomeValue" https://httpbin.org/headers

これをrequestsで表現すると以下のようになります。

“`python
import requests

url = ‘https://httpbin.org/headers’
headers = {
‘User-Agent’: ‘MyCustomClient/1.0’,
‘X-Custom-Header’: ‘SomeValue’
}

response = requests.get(url, headers=headers)

print(f”Status Code: {response.status_code}”)
print(f”Body: {response.json()}”) # リクエストヘッダーがJSONで返される
“`

httpbin.org/headersにアクセスすると、送信したヘッダーがレスポンスボディのJSONに含まれて返されるため、期待通りにヘッダーが送信されたか確認できます。

POSTリクエストとデータの送信 (-X POST, -d)

curlでPOSTリクエストを送信し、リクエストボディにデータを含める場合は、-X POST-dオプションを組み合わせます。

“`bash

デフォルト(application/x-www-form-urlencoded)でデータを送信

curl -X POST -d “key1=value1&key2=value2” https://httpbin.org/post

JSONデータを送信

curl -X POST -H “Content-Type: application/json” -d ‘{“key1”: “value1”, “key2”: “value2”}’ https://httpbin.org/post
“`

requestsでは、requests.post()関数を使います。データの形式によって、data引数またはjson引数を使い分けます。

フォームデータ(application/x-www-form-urlencoded)の送信:

curl -d "key1=value1&key2=value2"に対応するのは、data引数に辞書形式または文字列でデータを渡す方法です。辞書形式で渡すと、requestsが自動的にURLエンコードしてapplication/x-www-form-urlencoded形式で送信してくれます。

“`python
import requests

url = ‘https://httpbin.org/post’
data = {
‘key1’: ‘value1’,
‘key2’: ‘value2’
}

response = requests.post(url, data=data)

print(f”Status Code: {response.status_code}”)
print(f”Body: {response.json()}”) # 送信したデータが”form”フィールドに含まれて返される
“`

JSONデータの送信:

curl -H "Content-Type: application/json" -d '{"key1": "value1", ...}'のようにJSONを送信する場合、requestsではjson引数にPythonの辞書やリストを渡します。requestsが自動的にJSON文字列に変換し、Content-Type: application/jsonヘッダーを追加してくれます。

“`python
import requests

url = ‘https://httpbin.org/post’
json_data = {
‘key1’: ‘value1’,
‘key2’: ‘value2’
}

response = requests.post(url, json=json_data)

print(f”Status Code: {response.status_code}”)
print(f”Body: {response.json()}”) # 送信したデータが”json”フィールドに含まれて返される
“`

このように、requestsではデータの形式に応じて適切な引数を使うことで、ヘッダーを手動で設定することなく簡単にデータを送信できます。

ファイルのアップロード (-F)

curl-Fオプションは、multipart/form-data形式でデータを送信する際に使用され、特にファイルのアップロードによく使われます。

bash
curl -F "fieldname1=value1" -F "filefield=@/path/to/local/file.txt" https://httpbin.org/post

requestsでは、files引数に辞書形式で指定します。辞書のキーがフォームのフィールド名になり、値にはファイルオブジェクトや(ファイル名, ファイルオブジェクト)のタプルなどを指定します。

“`python
import requests

url = ‘https://httpbin.org/post’
file_path = ‘my_local_file.txt’ # 存在しない場合は適当なファイルを作成してください

ダミーファイルを作成 (実行前にファイルが存在することを確認するか、このコードを追加)

try:
with open(file_path, ‘w’) as f:
f.write(“This is the content of the file.”)
except IOError:
print(f”Error creating dummy file: {file_path}”)
exit()

files = {
‘fieldname1’: (None, ‘value1’), # ファイルではないフィールドはタプルで (ファイル名, データ)
‘filefield’: (file_path, open(file_path, ‘rb’)) # ファイルフィールドは (ファイル名, ファイルオブジェクト)
}

response = requests.post(url, files=files)

ファイルオブジェクトは使用後に閉じる必要がある

for file_info in files.values():
if isinstance(file_info, tuple) and isinstance(file_info[1], file):
file_info[1].close()

print(f”Status Code: {response.status_code}”)
print(f”Body: {response.json()}”) # 送信したデータやファイル情報が返される
“`

ファイルを送信する際は、ファイルオブジェクトを開いてfiles辞書に渡し、リクエスト完了後に必ず閉じる必要があります。with open(...)を使う方が安全ですが、requestsへの引渡し方によっては手動で閉じる必要がある場合もあります。上記例は手動で閉じるパターンを示しています。

認証 (-u)

curl-uオプションはBasic認証に利用されます。

bash
curl -u "myusername:mypassword" https://httpbin.org/basic-auth/myusername/mypassword

requestsでは、auth引数に(ユーザー名, パスワード)のタプルを渡すだけです。

“`python
import requests

url = ‘https://httpbin.org/basic-auth/myusername/mypassword’
auth_tuple = (‘myusername’, ‘mypassword’)

認証情報が正しければ200 OK、そうでなければ401 Unauthorized

response = requests.get(url, auth=auth_tuple)

print(f”Status Code: {response.status_code}”)
print(f”Body: {response.text}”)

認証失敗の例 (間違ったパスワード)

url_fail = ‘https://httpbin.org/basic-auth/myusername/mypassword’
auth_fail = (‘myusername’, ‘wrongpassword’)
response_fail = requests.get(url_fail, auth=auth_fail)
print(f”Status Code (Fail): {response_fail.status_code}”)
“`

他の認証スキーム(例: Bearer Token)の場合は、通常はヘッダーとしてトークンを渡します。

“`python
import requests

url = ‘https://api.example.com/data’
token = ‘your_bearer_token_here’
headers = {
‘Authorization’: f’Bearer {token}’
}

response = requests.get(url, headers=headers)

print(f”Status Code: {response.status_code}”)
print(f”Body: {response.text}”)
“`

ファイルのダウンロード (-O, -o)

curl -O-oは、レスポンスボディをファイルとして保存します。

“`bash

URLからファイル名を推測して保存

curl -O https://www.python.org/static/community_logos/python-logo-master-v3-TM.png

ファイル名を指定して保存

curl -o python_logo.png https://www.python.org/static/community_logos/python-logo-master-v3-TM.png
“`

requestsでこれを行うには、GETリクエストを送信し、レスポンスボディを読み込んでファイルに書き込みます。大きなファイルを扱う場合は、レスポンスボディ全体を一度にメモリに読み込まず、ストリーミングで少しずつ読み込むのが効率的です。

“`python
import requests
import os

url = ‘https://www.python.org/static/community_logos/python-logo-master-v3-TM.png’
output_filename = ‘downloaded_python_logo.png’ # -o に相当

URLからファイル名を推測 (-O に相当する場合)

output_filename = url.split(‘/’)[-1]

try:
response = requests.get(url, stream=True) # stream=True で大きなファイルも効率的に扱える
response.raise_for_status() # ステータスコードが4xxまたは5xxなら例外を発生させる

with open(output_filename, 'wb') as f:
    # レスポンスコンテンツをチャンクごとに書き込む
    for chunk in response.iter_content(chunk_size=8192):
        if chunk: # keep-alive connectionで空のチャンクが送られてくることがあるためチェック
            f.write(chunk)

print(f"Successfully downloaded {output_filename}")

except requests.exceptions.RequestException as e:
print(f”Error during download: {e}”)
“`

stream=Trueを指定し、response.iter_content()を使ってレスポンスボディを読み込むことで、メモリ使用量を抑えながらファイルをダウンロードできます。response.raise_for_status()は、HTTPエラーが発生した場合に検知するための重要なステップです。

リダイレクトの追跡 (-L)

curl -Lは、3xx系のリダイレクトレスポンスがあった場合に、その新しいURLに自動的にリクエストを追跡します。requestsでは、デフォルトでリダイレクトを追跡します(allow_redirects=True)。

“`bash

リダイレクトされるURL (例: httpbin.org/redirect/1 は一度だけリダイレクト)

curl -L https://httpbin.org/redirect/1
“`

requestsでデフォルトの挙動を確認します。

“`python
import requests

url = ‘https://httpbin.org/redirect/1’

response = requests.get(url) # allow_redirectsはデフォルトでTrue

print(f”Status Code: {response.status_code}”) # リダイレクト後の最終レスポンスのステータスコード
print(f”Final URL: {response.url}”) # リダイレクト後の最終URL
print(f”History: {response.history}”) # リダイレクトの履歴 (Responseオブジェクトのリスト)
“`

リダイレクトを追跡したくない場合は、allow_redirects=Falseを指定します。

“`python
import requests

url = ‘https://httpbin.org/redirect/1’

response = requests.get(url, allow_redirects=False)

print(f”Status Code: {response.status_code}”) # リダイレクト元のステータスコード (例: 302)
print(f”Location Header: {response.headers.get(‘Location’)}”) # リダイレクト先のURLはLocationヘッダーにある
print(f”Final URL (No Redirect): {response.url}”) # リダイレクト元のURL
print(f”History: {response.history}”) # リダイレクトを追跡しないので空のリスト
“`

SSL証明書の検証無効化 (-k)

curl -kは、HTTPSサイトにアクセスする際にSSL証明書の検証を行いません。自己署名証明書や無効な証明書を使っているサイトにアクセスする場合に一時的に利用されますが、中間者攻撃のリスクがあるため、本番環境では避けるべきです。

“`bash

無効なSSL証明書を持つサイト (例: https://expired.badssl.com/)

curl -k https://expired.badssl.com/
“`

requestsでこれを行うには、verify=Falseを指定します。ただし、この操作はセキュリティ上のリスクを伴うため、強い警告が表示される場合があります。

“`python
import requests

url = ‘https://expired.badssl.com/’

警告を無視するには以下をインポート

import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

try:
# verify=False はセキュリティリスクを伴うため注意して使用
response = requests.get(url, verify=False)

print(f"Status Code: {response.status_code}")
print(f"Body: {response.text}")

except requests.exceptions.RequestException as e:
print(f”Error: {e}”) # 証明書検証エラーは通常発生しない
“`

セキュリティ上の理由から、verify=Falseの使用は最小限にとどめ、可能であれば適切な証明書を使用するか、証明書を信頼する設定(verify=/path/to/certfile)を行うべきです。

タイムアウト設定 (--connect-timeout, --max-time)

curlでは--connect-timeoutで接続確立のタイムアウト、--max-timeでリクエスト全体のタイムアウトを指定できます。

bash
curl --connect-timeout 5 --max-time 10 https://example.com/slow-resource

requestsでは、timeout引数に秒数を指定します。これは接続タイムアウトと読み取りタイムアウト(サーバーからの最初のバイトを受け取ってからの待ち時間)の両方をカバーする単一の値、または(接続タイムアウト, 読み取りタイムアウト)タプルで指定できます。

“`python
import requests

url = ‘https://httpbin.org/delay/5’ # 5秒遅延するURL

5秒の読み取りタイムアウトを設定

try:
# 接続タイムアウトはデフォルト (数秒)、読み取りタイムアウトは5秒
response = requests.get(url, timeout=5)
print(f”Status Code: {response.status_code}”)
print(f”Body: {response.text}”)

except requests.exceptions.Timeout:
print(“Request timed out after 5 seconds.”)

接続タイムアウト1秒、読み取りタイムアウト10秒を設定

try:
# 接続はすぐに確立されるが、データ受信に10秒以上かかる場合にタイムアウト
response = requests.get(url, timeout=(1, 10)) # (connect timeout, read timeout)
print(f”Status Code: {response.status_code}”)
print(f”Body: {response.text}”)

except requests.exceptions.ConnectTimeout:
print(“Connection attempt timed out.”)
except requests.exceptions.ReadTimeout:
print(“Read attempt timed out.”)
except requests.exceptions.Timeout:
print(“Request timed out (general).”) # ConnectTimeout または ReadTimeout の親クラス
“`

タイムアウトが発生するとrequests.exceptions.Timeout(またはそのサブクラスConnectTimeout, ReadTimeout)例外が発生するため、try...exceptブロックで適切に処理する必要があります。

プロキシ設定

curlでプロキシを使う場合、--proxyまたは環境変数を使用します。

bash
curl --proxy http://my.proxy.com:8080 https://example.com

requestsでは、proxies引数にプロトコルごとのプロキシURLを辞書形式で指定します。

“`python
import requests

url = ‘https://example.com’
proxies = {
‘http’: ‘http://user:[email protected]:8080’,
‘https’: ‘http://user:[email protected]:8080’, # HTTPS通信も同じプロキシを使う例
# ‘https’: ‘https://user:[email protected]:8443’, # HTTPSプロキシを使う場合
}

try:
response = requests.get(url, proxies=proxies)
print(f”Status Code: {response.status_code}”)
print(f”Body: {response.text}”)

except requests.exceptions.RequestException as e:
print(f”Error using proxy: {e}”)
“`

認証が必要なプロキシの場合、ユーザー名とパスワードをプロキシURLに含めることができます(http://user:pass@host:port形式)。

クッキーの送信 (--cookie, -b)

curlでクッキーを送信する場合、--cookieオプションで直接指定するか、-bオプションでファイルから読み込みます。

bash
curl --cookie "sessionid=abcdef123; csrftoken=xyz789" https://httpbin.org/cookies

requestsでは、cookies引数に辞書形式で指定します。

“`python
import requests

url = ‘https://httpbin.org/cookies’
cookies = {
‘sessionid’: ‘abcdef123’,
‘csrftoken’: ‘xyz789’
}

response = requests.get(url, cookies=cookies)

print(f”Status Code: {response.status_code}”)
print(f”Body: {response.json()}”) # 送信したクッキーが”cookies”フィールドに含まれて返される
“`

サーバーから返されたクッキーを取得するには、response.cookies属性を使用します。これはRequestsCookieJarオブジェクトですが、辞書のように扱うことができます。

“`python
import requests

url = ‘https://httpbin.org/cookies/set?cookie1=value1&cookie2=value2’

response = requests.get(url) # httpbin.orgはこのリクエストでクッキーをセットする

print(f”Status Code: {response.status_code}”)
print(f”Set-Cookie headers: {response.headers.get(‘Set-Cookie’)}”)

受信したクッキーを取得

received_cookies = response.cookies
print(f”Received cookies: {received_cookies}”)
print(f”Cookie ‘cookie1’: {received_cookies.get(‘cookie1’)}”)

受信したクッキーを使って次のリクエストを送信

url_check = ‘https://httpbin.org/cookies’
response_check = requests.get(url_check, cookies=received_cookies)
print(f”Status Code (Check): {response_check.status_code}”)
print(f”Body (Check): {response_check.json()}”) # 受信したクッキーが次のリクエストで送信されている
“`

複数のリクエスト間でクッキーや接続設定を維持したい場合は、後述のrequests.Sessionを使うのが効率的です。

その他のcurlオプションとrequestsでの対応

多くのcurlオプションは、requestsの引数として直感的に対応しています。

curlオプション requests引数/方法 説明
-I, --head requests.head() HEADリクエストを送信し、ヘッダー情報のみを取得します。
--data-urlencode <name>=<value> requests.post(data={'name': 'value'}) または requests.post(data=urlencode_string) データをURLエンコードして送信します。辞書でdataに渡すのが簡単です。
-A "<User-Agent>" requests.get(headers={'User-Agent': '...'}) User-Agentヘッダーを指定します。
--referer "<URL>" requests.get(headers={'Referer': '...'}) Refererヘッダーを指定します。
--compressed デフォルトで有効 圧縮されたレスポンス(gzip, deflate, brotliなど)を自動的に解凍します。requestsはデフォルトで対応。
--limit-rate <speed> 直接の引数はないが、ストリーミングダウンロード時に手動で制御は可能 帯域幅制限。requestsに直接この機能はないため、必要なら手動で実装。

セッション管理 (requests.Session)

requests.Sessionオブジェクトを使うと、複数のリクエスト間でTCP接続を再利用したり、クッキーを自動的に維持したりすることができます。これは、同じホストに対して繰り返しリクエストを送る場合や、ログインが必要なサイトを操作する場合に非常に便利です。

“`python
import requests

セッションオブジェクトを作成

with requests.Session() as session:
# セッションを使って最初のURLにアクセス (クッキーがセットされる)
session.get(‘https://httpbin.org/cookies/set?cookie1=value1’)

# 同じセッションを使って別のURLにアクセス (前のリクエストでセットされたクッキーが自動的に送信される)
response = session.get('https://httpbin.org/cookies')

print(f"Status Code: {response.status_code}")
print(f"Body: {response.json()}") # cookie1 が含まれているはず

# セッションオブジェクトに対して設定したヘッダーや認証情報は、そのセッションを使う全てのリクエストに適用される
session.headers.update({'X-Custom-Session-Header': 'SessionValue'})
response_with_header = session.get('https://httpbin.org/headers')
print(f"Body (with session header): {response_with_header.json()}") # X-Custom-Session-Header が含まれているはず

“`

withステートメントを使うことで、セッションが自動的に閉じられ、リソースが解放されます。ログインしてからの複数ページ遷移など、状態を維持したい操作にはSessionが必須と言えます。

エラーハンドリング

HTTP通信においては、様々なエラーが発生し得ます。ネットワークエラー、タイムアウト、サーバー側のエラー(4xx, 5xxステータスコード)などです。requestsでは、これらのエラーを適切に処理するための機能が提供されています。

  • ネットワークレベルのエラー(接続拒否、名前解決失敗など): requests.exceptions.RequestExceptionまたはそのサブクラス(ConnectionError, Timeoutなど)として捕捉できます。
  • HTTPエラー(4xx, 5xxステータスコード): デフォルトでは例外は発生しませんが、Responseオブジェクトのraise_for_status()メソッドを呼び出すことで、ステータスコードがエラーを示す場合にrequests.exceptions.HTTPError例外を発生させることができます。これはファイルダウンロードの例でも使用しました。

“`python
import requests

存在しないURLへのアクセス (ネットワークエラー)

try:
response = requests.get(‘http://nonexistent.local’)
except requests.exceptions.ConnectionError as e:
print(f”Connection Error: {e}”)

サーバー側エラー (404 Not Found)

url_not_found = ‘https://httpbin.org/status/404’
response_404 = requests.get(url_not_found)
print(f”Status Code (404): {response_404.status_code}”)

raise_for_status() を使うと例外が発生

try:
response_404.raise_for_status()
except requests.exceptions.HTTPError as e:
print(f”HTTP Error: {e}”) # ‘404 Client Error: Not Found for url: …’ のようなメッセージが表示される

サーバー側エラー (500 Internal Server Error)

url_server_error = ‘https://httpbin.org/status/500’
response_500 = requests.get(url_server_error)
print(f”Status Code (500): {response_500.status_code}”)

try:
response_500.raise_for_status()
except requests.exceptions.HTTPError as e:
print(f”HTTP Error: {e}”) # ‘500 Server Error: Internal Server Error for url: …’ のようなメッセージが表示される
“`

堅牢なスクリプトを書くためには、これらの例外を適切に捕捉し、エラーの原因に応じた処理(リトライ、ログ出力、ユーザーへの通知など)を実装することが重要です。

requestsのまとめ

requestsライブラリは、curlコマンドでできることのほとんど、そしてそれ以上のことをPythonicな方法で実現できます。GET, POSTなどのメソッド、ヘッダー、クエリパラメータ、データ送信(フォーム、JSON)、ファイルアップロード、認証、リダイレクト、SSL検証、タイムアウト、プロキシ、クッキー、セッション管理、そしてエラーハンドリング。これら全てが、直感的で分かりやすいAPIで提供されています。

curlコマンドで行っていたHTTP通信のタスクをPythonで自動化・拡張したい場合、requestsライブラリが最初の、そして多くの場合最終的な選択肢となるでしょう。

方法2: subprocessモジュールを使う

時には、既存の複雑なcurlコマンドをそのままPythonスクリプト内で実行したい場合があります。例えば、curlの非常に特殊なオプションを使っている、curlが利用するシステムレベルの証明書ストアを利用したい、あるいは単に既存のコマンドをPythonコードに書き直す手間を省きたいといったケースです。このような場合、Pythonの標準ライブラリであるsubprocessモジュールを使って、OS上でcurlコマンドを直接実行することができます。

subprocessモジュールは、新しいプロセスを生成し、そのプロセスの入出力、エラー、戻り値などを制御するための機能を提供します。subprocess.run()関数が、外部コマンドの実行とその完了を待つための最も簡単な方法です。

基本的なcurlコマンドの実行

簡単なcurlコマンドを実行してみましょう。

bash
curl https://httpbin.org/get

これをsubprocessで実行します。コマンドとその引数は、リスト形式で渡すのが最も安全で推奨される方法です。

“`python
import subprocess

コマンドと引数をリストで指定

command = [‘curl’, ‘https://httpbin.org/get’]

try:
# コマンドを実行し、完了を待つ
# capture_output=True: 標準出力と標準エラーを取得
# text=True: 出力をテキストとしてデコード (Python 3.7+)
# check=True: 戻り値が非ゼロの場合にCalledProcessErrorを発生させる
result = subprocess.run(command, capture_output=True, text=True, check=True)

print(f"Status Code: {result.returncode}") # 戻り値 (通常0は成功)
print(f"Standard Output:\n{result.stdout}") # curlの出力 (レスポンスボディなど)
print(f"Standard Error:\n{result.stderr}") # curlのエラー出力 (進捗バーなど)

except FileNotFoundError:
print(f”Error: ‘curl’ command not found.”)
except subprocess.CalledProcessError as e:
print(f”Error executing command: {e}”)
print(f”Command: {e.cmd}”)
print(f”Return Code: {e.returncode}”)
print(f”Standard Output:\n{e.stdout}”)
print(f”Standard Error:\n{e.stderr}”)
except Exception as e:
print(f”An unexpected error occurred: {e}”)
“`

  • capture_output=True: コマンドの標準出力(stdout)と標準エラー(stderr)をキャプチャします。
  • text=True: キャプチャした出力をシステムのデフォルトエンコーディングまたは指定されたエンコーディングでテキストとして扱います。これを指定しない場合、出力はバイト列になります。
  • check=True: コマンドの終了コードがゼロ以外だった場合(つまりエラー終了した場合)に、subprocess.CalledProcessError例外を発生させます。これにより、コマンドの成功・失敗を簡単に判定できます。
  • result.returncode: コマンドの終了コードです。通常、0は成功を示します。
  • result.stdout, result.stderr: キャプチャされた標準出力と標準エラーです。

オプション付きのcurlコマンド実行

curlコマンドにオプションを付ける場合も、それぞれをリストの要素として渡します。

bash
curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice"}' https://httpbin.org/post

このコマンドをsubprocessで実行します。-d-Hなどのオプションも、それぞれの引数も、リストの別々の要素として渡すのがポイントです。単一の文字列として渡すと、スペースや特殊文字の扱いで問題が発生する可能性があります(shell=Trueを使う場合は文字列でも良いですが、セキュリティリスクがあるため非推奨です)。

“`python
import subprocess
import json # JSONデータを扱うためにインポート

送信するJSONデータ

data = {“name”: “Alice”}
json_string = json.dumps(data) # PythonオブジェクトをJSON文字列に変換

command = [
‘curl’,
‘-X’, ‘POST’,
‘-H’, ‘Content-Type: application/json’,
‘-d’, json_string, # ‘-d’オプションの引数としてJSON文字列を渡す
‘https://httpbin.org/post’
]

try:
result = subprocess.run(command, capture_output=True, text=True, check=True)

print(f"Return Code: {result.returncode}")
print(f"Standard Output:\n{result.stdout}")
print(f"Standard Error:\n{result.stderr}")

except FileNotFoundError:
print(f”Error: ‘curl’ command not found.”)
except subprocess.CalledProcessError as e:
print(f”Error executing command: {e}”)
print(f”Standard Output:\n{e.stdout}”)
print(f”Standard Error:\n{e.stderr}”)
except Exception as e:
print(f”An unexpected error occurred: {e}”)
“`

curlコマンドの引数リストを作成する際は、シェル上で実行するコマンドのスペースで区切られた各部分を、そのままリストの要素に変換するイメージです。例えば、-H "Content-Type: application/json"は、リストでは['-H', 'Content-Type: application/json']となります。

シェルインジェクションのリスクとshell=True

subprocess.run()関数にはshell=Trueという引数があります。これをTrueにすると、コマンド文字列がシェルのプロセスを介して実行されます。

“`python

非推奨な例!

command_string = “curl https://httpbin.org/get”

print(subprocess.run(command_string, shell=True, capture_output=True, text=True).stdout)

“`

shell=Trueを使うと、コマンド文字列中に含まれるシェルのメタ文字(例: |, >, &, ;, $()など)が解釈されます。これは、シェルの機能を利用したい場合には便利ですが、ユーザーからの入力など信頼できない文字列をコマンドに含める場合にシェルインジェクションというセキュリティ上の脆弱性を引き起こす可能性があります。

例えば、ユーザー入力をもとにURLを組み立てる場合:

“`python

危険な例

user_url = input(“Enter URL: “) # 例えば https://example.com; rm -rf /
command_string = f”curl {user_url}”

shell=True の場合、入力によっては意図しないコマンドが実行される可能性がある

subprocess.run(command_string, shell=True)

“`

一方、リスト形式でコマンドと引数を渡す場合は、シェルの解釈を介さずにコマンドが直接実行されるため、引数に特殊文字が含まれていても安全です。

したがって、特別な理由がない限り、shell=Trueは使用せず、コマンドと引数はリスト形式で渡すことを強く推奨します。

subprocessの利点と欠点

利点:

  • 既存のcurlコマンドをそのまま利用できる: 複雑なコマンドや特殊な設定をPythonに書き直す手間が省けます。
  • curlの環境設定やシステムリソースを利用できる: curlが使用するプロキシ設定や証明書ストアなどをそのまま利用できます。
  • 外部ライブラリが不要: Python標準ライブラリのみで実現できます。

欠点:

  • 環境依存: curlコマンドが実行されるOSにインストールされている必要があります。また、curlのバージョンや設定によって挙動が変わる可能性があります。
  • セキュリティリスク: shell=Trueを使う場合、シェルインジェクションのリスクがあります。
  • エラーハンドリングの難しさ: curlのエラーメッセージは標準エラーに出力されることが多く、それをパースして詳細なエラー原因を特定するのが難しい場合があります。HTTPステータスコードはレスポンスボディの一部として出力されるか、-wオプションなどで整形しないと取得できません。
  • パフォーマンス: 新しいプロセスを起動するオーバーヘッドがあります。特に短時間に多数のリクエストを送る場合には非効率です。
  • データの扱い: Pythonの変数として取得したデータを直接リクエストボディに渡すのがやや煩雑です(一度ファイルに書き出すか、標準入力にパイプするなど)。レスポンスボディもテキストとして取得後に手動でパースする必要があります(JSON文字列など)。
  • クロスプラットフォーム開発の難しさ: OSによってcurlのパスや挙動が異なる場合があります。

これらの欠点を考慮すると、HTTP通信の機能をPythonスクリプトに統合し、柔軟なデータ処理やエラーハンドリングを行いたい場合は、requestsのようなHTTPライブラリを使用する方が圧倒的に優れています。 subprocessは、あくまで既存のcurlコマンドを「実行するだけ」にとどめたい場合の選択肢と考えましょう。

方法3: urllibライブラリを使う

urllibはPythonの標準ライブラリに含まれるHTTP通信モジュールです。外部ライブラリなしでHTTPリクエストを送信できるという利点がありますが、requestsと比較するとAPIがやや低レベルで使い方が煩雑です。現在では、特別な理由(例えば、依存ライブラリを追加できない制限された環境など)がない限り、requestsを使用するのが一般的です。

urllibは複数のサブモジュールから構成されていますが、HTTPリクエストの送信には主にurllib.requestモジュールを使用します。

基本的なGETリクエスト

“`python
import urllib.request
import urllib.error

url = ‘https://httpbin.org/get’

try:
# URLを開いてレスポンスを取得
with urllib.request.urlopen(url) as response:
print(f”Status Code: {response.getcode()}”)
print(f”Headers:\n{response.getheaders()}”)
# レスポンスボディを読み込む (バイト列で取得される)
# .read() は全てをメモリに読み込むため、大きなファイルには注意
body = response.read()
# 必要に応じてデコード
print(f”Body:\n{body.decode(‘utf-8’)}”)

except urllib.error.URLError as e:
print(f”URL Error: {e.reason}”)
except urllib.error.HTTPError as e:
print(f”HTTP Error: {e.code} – {e.reason}”)
except Exception as e:
print(f”An unexpected error occurred: {e}”)
“`

urllib.request.urlopen()はファイルライクなオブジェクトを返します。withステートメントを使うことで、レスポンスオブジェクトが自動的に閉じられます。ステータスコードはgetcode()、ヘッダーはgetheaders()で取得します。レスポンスボディはread()などのメソッドで読み込みますが、バイト列で返されるため、テキストとして扱う場合は適切にデコードする必要があります。

ヘッダーやデータの送信 (urllib.request.Request)

POSTリクエストを送信したり、ヘッダーを追加したりする場合、urllib.request.Requestオブジェクトを作成して、それをurlopen()に渡す必要があります。

“`python
import urllib.request
import urllib.parse # データのエンコードに使用
import urllib.error
import json

url = ‘https://httpbin.org/post’

送信するフォームデータ (辞書形式)

form_data = {‘key1’: ‘value1’, ‘key2’: ‘value2’}

データを application/x-www-form-urlencoded 形式にエンコード

encoded_data = urllib.parse.urlencode(form_data).encode(‘utf-8’) # バイト列にする必要がある

リクエストオブジェクトを作成

method=’POST’ は Python 3.3 以降で指定可能

req = urllib.request.Request(url, data=encoded_data, method=’POST’)

ヘッダーを追加

req.add_header(‘Content-Type’, ‘application/x-www-form-urlencoded’)
req.add_header(‘User-Agent’, ‘MyUrllibClient/1.0’)

リクエストを送信

try:
with urllib.request.urlopen(req) as response:
print(f”Status Code: {response.getcode()}”)
print(f”Headers:\n{response.getheaders()}”)
body = response.read().decode(‘utf-8’)
print(f”Body:\n{body}”)

except urllib.error.URLError as e:
print(f”URL Error: {e.reason}”)
except urllib.error.HTTPError as e:
print(f”HTTP Error: {e.code} – {e.reason}”)
# エラーレスポンスのボディも読み込める
# print(f”Error Body:\n{e.read().decode(‘utf-8’)}”)
except Exception as e:
print(f”An unexpected error occurred: {e}”)
“`

urllibでデータを送信する場合、データをバイト列にエンコードしてからRequestオブジェクトのdata引数に渡す必要があります。フォームデータの場合はurllib.parse.urlencode().encode()を組み合わせて使います。JSONデータの場合はjson.dumps().encode()を使います。

ヘッダーを追加するにはadd_header()メソッドを使用します。

認証、タイムアウト、リダイレクト、クッキーなどの機能もurllib.requestで実現可能ですが、それぞれに対応するハンドラ(HTTPBasicAuthHandler, ProxyHandler, HTTPCookieProcessorなど)を作成し、build_openerで組み合わせて使う必要があり、requestsに比べて設定が複雑になります。

urllibのまとめ

urllibはPython標準ライブラリなので追加インストールが不要という利点がありますが、requestsに比べてコードが冗長になりやすく、使いやすさの面で劣ります。基本的なGETリクエストであれば簡単ですが、ヘッダーの追加、POSTデータの送信、エラーハンドリング、認証、クッキー管理など、複雑な要件になるほどrequestsのシンプルさが際立ちます。

特別な理由がない限り、PythonでHTTP通信を行う際にはrequestsライブラリを選択することをお勧めします。

curlコマンドからPythonコードへの自動変換ツール

手作業でcurlコマンドをPythonコードに変換するのは、特にオプションが多い場合に手間がかかります。このような場合に役立つのが、自動変換ツールです。

最も有名なのは、Webサイトとして提供されているcurlconverter.comです。このサイトにcurlコマンドを貼り付けると、様々な言語(Python, JavaScript, PHPなど)でのコードに変換してくれます。Pythonの場合は、requestsライブラリを使ったコードが出力されます。

例えば、以下のcurlコマンドを貼り付けてみてください。

bash
curl 'https://httpbin.org/post' \
-X POST \
-H 'Content-Type: application/json' \
-H 'User-Agent: MyCurlClient/1.0' \
-d '{"message": "Hello from curl!"}'

curlconverter.comは、これを以下のようなPython (requests) コードに変換してくれます(出力はサイトのバージョンによって多少異なる場合があります)。

“`python
import requests

headers = {
‘Content-Type’: ‘application/json’,
‘User-Agent’: ‘MyCurlClient/1.0’,
}

json_data = {
‘message’: ‘Hello from curl!’,
}

response = requests.post(‘https://httpbin.org/post’, headers=headers, json=json_data)

Note: json_data will be transformed to passing data=json.dumps(json_data).

Please beware that requests does not know the actual schema of the JSON data,

so content-type header should be explicitly provided when sending json_data.

You can access the response like this:

print(response.status_code)

print(response.json())

“`

このように、curlconverter.comはコード変換の出発点として非常に便利です。ただし、注意点もあります。

  • 変換の限界: 非常に複雑なcurlオプションや、curlのバージョンに依存する機能などは正確に変換できない場合があります。
  • セキュリティ: 貼り付けるcurlコマンドに機密情報(APIキー、パスワードなど)が含まれていないか十分に注意が必要です。
  • コードの品質: 生成されたコードはあくまで基本的な変換結果です。エラーハンドリングやセッション管理など、実用的なスクリプトに必要な要素は手動で追加・修正する必要があります。

自動変換ツールはコード作成の効率化に役立ちますが、生成されたコードを鵜呑みにせず、内容を理解し、必要に応じて修正することが重要です。

Pythonライブラリとして提供されているものもありますが、基本的な使い方や注意点はWebサービスと同様です。

まとめと使い分けの指針

この記事では、curlコマンドの機能をPythonで実現するための3つの主要な方法、requestsライブラリ、subprocessモジュール、そしてurllibライブラリを詳細に見てきました。

それぞれの方法には特徴があり、最適な選択はあなたの要件によって異なります。

  1. requestsライブラリ:

    • 利点: 使いやすいAPI、豊富な機能(認証、セッション、SSL検証、タイムアウトなど)、優れたエラーハンドリング、Pythonicなデータ操作、ほとんどのcurl機能を網羅。
    • 欠点: 標準ライブラリではないためインストールが必要。
    • 推奨されるケース: ほとんどの場合。 特に、API連携、Webスクレイピング、HTTP通信を含む自動化スクリプトなど、PythonプログラムとしてHTTPリクエストを柔軟に制御・処理したい場合。これが第一の選択肢となるべきです。
  2. subprocessモジュール:

    • 利点: 既存のcurlコマンドをそのまま実行できる、外部ライブラリ不要、システムにインストールされたcurlの環境を利用できる。
    • 欠点: 環境依存、セキュリティリスク(shell=True)、エラーハンドリングの難しさ、データ操作が煩雑、パフォーマンスオーバーヘッド。
    • 推奨されるケース: 既存の複雑なcurlコマンドを短時間でPythonスクリプトに組み込みたい場合、requestsでは対応が難しいcurlの非常に特殊なオプションを利用したい場合。基本的には非推奨であり、代替手段がないか十分に検討すべきです。
  3. urllibライブラリ:

    • 利点: 標準ライブラリであり、追加インストール不要。
    • 欠点: APIが低レベルで使い方がやや煩雑、特に複雑な設定やエラーハンドリングの実装がrequestsより手間がかかる。
    • 推奨されるケース: 依存ライブラリを追加できないような、極めて制限されたPython実行環境での最低限のHTTP通信が必要な場合。通常はrequestsを選ぶべきです。

このように、Pythonでcurlコマンドの機能を実装する場合、requestsライブラリを使うのが最も一般的で効率的な方法です。 subprocessurllibは、特定のニッチな要件がある場合に検討する、あるいはrequestsが利用できない場合の代替手段として知っておくと良いでしょう。

終わりに

この記事では、curlコマンドで行っていたHTTP通信の操作をPythonスクリプトに落とし込む方法を詳細に解説しました。特にrequestsライブラリを中心に、様々なcurlオプションに対応するPythonコードを具体的な例と共に紹介しました。

curlコマンドはコマンドラインでの手軽な操作に優れていますが、Pythonと組み合わせることで、HTTP通信を自動化し、取得したデータをプログラムで柔軟に処理・活用する道が開けます。APIの定期的な呼び出し、Webサイトからの情報収集、テスト自動化など、Pythonとrequestsを活用することで実現できることは多岐にわたります。

ぜひ、この記事で紹介したコード例を参考に、あなたのPythonプロジェクトにHTTP通信の機能を組み込んでみてください。そして、もしコマンドラインで複雑なcurlコマンドを組み立てている自分に気づいたら、一度Pythonのrequestsで置き換えられないか検討してみましょう。きっと、より効率的でメンテナンスしやすいコードが書けるはずです。

HTTP通信のデバッグには、Wiresharkのようなネットワークプロトコルアナライザや、Chrome/Firefoxなどのブラウザの開発者ツール(Networkタブ)が非常に役立ちます。これらのツールを使って実際のリクエスト/レスポンスの詳細を確認しながら開発を進めることをお勧めします。

これで、あなたはcurlコマンドとPythonを使ったHTTP通信のエキスパートへの道を一歩踏み出しました。Happy coding!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール