PythonでHTTPリクエストを実行する方法 - パート2 - リクエストライブラリ
前回の記事では、Python3の標準ライブラリを使用して基本的なHTTPリクエストを実行する方法を見ました。リクエストがより複雑になる場合や、コードを少なくしたい場合、またはプロジェクトに依存関係を追加することを気にしない場合、外部のrequestsモジュールを使用することが可能です(場合によっては推奨されることもあります)。「人間のためのHTTP」というモットーを採用したこのライブラリが、この記事の焦点になります。
このチュートリアルでは、あなたは学びます:
- Python3と‘requests’ライブラリを使ってHTTPリクエストを実行する方法
- サーバーの応答を管理する方法
- セッションを扱う方法
PythonによるHTTPリクエスト - パートII: requestsライブラリ
ソフトウェアの要件と使用される規約
「requests」ライブラリを使ってリクエストを実行する
このシリーズの第一部では、標準ライブラリを使って基本的なHTTPリクエストを実行しました。リクエストがより複雑になる場合、たとえば、あるリクエストと別のリクエストの間でクッキーを保持する必要がある場合、私たちは外部ライブラリrequestsを使用することができ、これによって私たちの作業が簡素化され、多くの操作が裏で自動的に行われます。このライブラリはデフォルトのpython3インストールには含まれていないため、使用する前にシステムにインストールする必要があります。タスクを達成するためのディストリビューションに依存しない方法は、pythonパッケージマネージャーpipを使用することです:
pip3 install requests --userライブラリをインストールしたので、使い方の例をいくつか見てみましょう。
GETリクエストを実行する
私たちが特定の日付の「今日の画像」を取得するためにNASA APIを使用したリクエストを覚えていますか? requestsライブラリを使用して同じリクエストを構築し送信するには、1行のコードが必要です。
>>> import requests
>>> response = requests.get("https://api.nasa.gov/planetary/apod", params={"api_key": "DEMO_KEY", "date": "2019-04-11"})
私たちはURLとクエリパラメータ(依然として辞書として)を、それぞれget関数の最初の引数と二番目の引数として渡しました。この関数は何を返しますか?それはrequests.models.Responseクラスのインスタンスを返します。このクラスのインスタンスとやり取りするのは非常に簡単です。レスポンスのjsonエンコードされたコンテンツを取得したいですか?簡単です!オブジェクトのjsonメソッドを呼び出すだけです。
>>> response.json()
{'date': '2019-04-11',
'explanation': 'What does a black hole look like? To find out, radio '
'telescopes from around the Earth coordinated observations of '
'black holes with the largest known event horizons on the '
...
'immediate vicinity of the black hole in the center of our '
'Milky Way Galaxy.',
'hdurl': 'https://apod.nasa.gov/apod/image/1904/M87bh_EHT_2629.jpg',
'media_type': 'image',
'service_version': 'v1',
'title': 'First Horizon-Scale Image of a Black Hole',
'url': 'https://apod.nasa.gov/apod/image/1904/M87bh_EHT_960.jpg'}
サーバーのレスポンスを文字列として取得したいですか?私たちがすべきことは、textプロパティにアクセスすることだけです:
response.text同様にして、リクエストのreason、status_code、およびheadersにアクセスできます。それぞれのプロパティにアクセスするだけです:
>>> response.reason
'OK'
>>> response.status_code
200
>>> response.headers
{'Server': 'openresty', 'Date': 'Thu, 18 Apr 2019 10:46:26 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-RateLimit-Limit': '40', 'X-RateLimit-Remaining': '39', 'Via': '1.1 vegur, http/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])', 'Age': '0', 'X-Cache': 'MISS', 'Access-Control-Allow-Origin': '*', 'Strict-Transport-Security': 'max-age=31536000; preload', 'Content-Encoding': 'gzip'}
ファイルをダウンロードしています
ファイルをダウンロードするのも非常に簡単です。まず、get関数のstreamパラメータを使用する必要があります。デフォルトでは、このパラメータはFalseに設定されており、これはレスポンスのボディが一度にダウンロードされることを意味します。大きなファイルをダウンロードしたい場合には、これをTrueに設定する必要があります。こうすることで、レスポンスのヘッダーだけが即座にダウンロードされ、接続がオープンのままとなり、必要に応じてさらに処理を行うことができます。
>>> latest_kernel_tarball = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.7.tar.xz"
>>> with requests.get(latest_kernel_tarball, stream=True) as response:
... with open("latest-kernel.tar.xz", "wb") as tarball:
... for chunk in response.iter_content(16384):
... tarball.write(chunk)
このコードは、標準ライブラリの対応部分に似ています。変更されたのは、レスポンスオブジェクトのiter_contentメソッドを使用することです。前の例では、レスポンスの内容が消費されるまでのみ中断されるwhileループの内部で操作を行っていました。このメソッドを使用することで、レスポンスの内容を繰り返し処理できるため、目的のファイルへの書き込みをよりエレガントな方法で行うことができます。iter_contentメソッドは、オプションの引数chunk_sizeを受け入れます。これは、バイト単位のチャンクサイズを示す整数です(各イテレーションでメモリに読み込むデータの量)。
リクエストにフォームエンコードされたデータまたはJSONを送信する
「requests」ライブラリを使ってフォームエンコードされたデータ(例えばPOSTリクエストで)を送信することは、標準ライブラリだけを使用して同じ操作を行うよりもコードが少なくて済みます:
>>>request_data = {
... "variable1": "value1",
... "variable2": "value2"
...}
>>>response = requests.post("https://httpbin.org/post", data=request_data)
同じデータをJSON形式で渡すには:
response = requests.post("https://httpbin.org/post", json=request_data)関数の json パラメーターを使用することで、json.dumps を使用して文字列をエンコードすることを心配する必要すらありません: それは背後で自動的に行われます。
ファイルをアップロードしています
標準ライブラリを使用してファイルをアップロードすることは非常に面倒な作業ですが、requestsライブラリを使用すると非常に簡単です。例えば、画像をアップロードしたいとします:
>>> response = requests.post(
... "https://httpbin.org/post", files={'file': open('nasa_black_hole.png', 'rb')})
印象的に短いコード!今回は files 引数を使って post リクエストを行いました。この引数は辞書である必要があり、キーはフィールド「name」で、値はファイルオブジェクトで、今回は open 関数によって返されるものです。
他のHTTP動詞はどうですか?それぞれは適切に名付けられた関数:put, delete, head または options と共に使用されます。これらはすべて、以前に見たものと基本的に同じインターフェースで使用できます。
セッションと共に作業する
このrequestsライブラリを使用すると、sessionsを使うことができます:セッションコンテキストからリクエストが送信されると、クッキーは1つのリクエストから別のリクエスト間で保持されます。これは、同じTCP接続が再利用されるため、同じホストに対して複数のリクエストを実行するための推奨される方法です。では、セッションを作成して、それを使ってリクエストを送信する方法を見てみましょう:
>>> session = requests.Session()
>>> response = session.get("https://httpbin.org/cookies/set?lastname=skywalker")
requests.Session クラスのインスタンスを作成しました。そして、これまでの例で行ったようにリクエストを単独で実行するのではなく、同じように使用されるHTTP動詞にちなんだメソッド(この場合はget)を使用しました。リクエストURLは、今回はhttp://httpbin.org/cookies/setで、クエリ文字列に送信するクッキーのパラメータを設定するためのエンドポイントです。私たちが行った呼び出しは、クッキーを設定し、それが現在セッションに保存され、session コンテキストから送信されるすべてのリクエストで使用されます。セッションに関連するすべてのクッキーをリストするために、cookies プロパティにアクセスできます。これはrequests.cookies.RequestsCookieJar' クラスのインスタンスです。
>>> session.cookies
<RequestsCookieJar[Cookie(version=0, name='lastname', value='skywalker', port=None, port_specified=False, domain='httpbin.org', domain_specified=False,
domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False)]>
>>> # Access the cookies keys
...session.cookies.keys()
['lastname']
>>>
>>> # Access the cookies values
...session.cookies.values()
['skywalker']
>>>
>>> # The iterkeys method returns an iterator of names of cookies
...session.cookies.iterkeys()
<generator object RequestsCookieJar.iterkeys at 0x7f414a1a5408>
>>> # The itervalues method does the same but for values
...session.cookies.itervalues()
<generator object RequestsCookieJar.itervalues at 0x7f414a1a5408>
セッションに保存されたクッキーをクリアするには、clear メソッドを使用できます。
>>> session.cookies.clear()
>>> session.cookies
<RequestsCookieJar[]>
リクエストオブジェクトを作成する
これまで、私たちは基本的に「その場で」リクエストを作成して送信する get、post、put のような関数を使用してきました。しかし、Request オブジェクトを構築したいが、すぐに送信したくない場合もあります。ここではその方法を示します:
>>> request = requests.Request("GET", "https://httpbin.org/get")Request コンストラクタの最初の引数は使用したい動詞で、2つ目は宛先のURLです。リクエストを直接送信する際に使用するのと同じパラメータを使用できます:headers、params、data、json、および files。Request を作成したら、送信する前にそれを「準備」する必要があります:
>>> session = requests.Session()
>>> request = requests.Request("GET", "https://httpbin.org/get")
>>> prepared_request = session.prepare_request(request)
>>> response = session.send(prepared_request)
私たちは、session.prepare_requestを呼び出す代わりに、Requestオブジェクト自体のprepareメソッドを使用してRequestを準備することもできますが、この場合、リクエストはセッションの一部であることの利点を失います。
レスポンスのステータスコードが200でない場合は例外を発生させる
リクエストが成功したときにサーバーが返すステータスコードは200です。リソースが見つからない場合やアクセス権がない場合など、エラーが発生した際には他のコードが返されます(この場合はそれぞれ404と403です)。このような場合にコードが例外を発生させるようにしたい場合は、requests.models.Responseオブジェクトのraise_for_statusメソッドを呼び出す必要があります。これを使用するとコードがどのように異なる動作をするか見てみましょう。GETメソッドのみを受け付けるエンドポイントにPOSTリクエストを送信します:
>>> response = requests.post('https://httpbin.org/get')
>>> response.status_code
405
>>> response.reason
'METHOD NOT ALLOWED'
予想通り、間違ったHTTP動詞を使用したため、レスポンスのステータスコードは405で、対応する「理由」はMETHOD NOT ALLOWEDでしたが、例外は発生しませんでした。悪いリクエストが例外を発生させるためには、リクエストを送信した後にraise_for_statusメソッドを呼び出す必要があります:
>>> response = requests.post('https://httpbin.org/get')
>>> response.raise_for_status()
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.7/site-packages/requests/models.py", line 940, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 405 Client Error: METHOD NOT ALLOWED for url: https://httpbin.org/get
raise_for_statusを呼び出したので、今回はリクエストがrequests.exceptions.HTTPError例外を発生させました。
結論
この記事では、PythonでHTTPリクエストを実行するシリーズの第2弾として、外部のrequestsライブラリの使用に焦点を当てました。このライブラリを使用することで、わずか数行のコードでシンプルなリクエストと複雑なリクエストの両方を実行できます。もっと知りたいですか?公式ドキュメントはワンクリックでアクセスできます!