個人的にPythonの中でもぶっちぎりNo1レベルでヘビーユースな外部モジュールrequestsについての便利さをひたすらまとめていきます。
requests 概要
簡単に言うとPythonで行うHTTPリクエストを簡単に手軽にしてくれる神ライブラリです。
言語を跨いだHTTPインターフェイスのディファクトスタンダードといえばCURLコマンドが非常に有名ですが、ことPythonにおいてはCURLの出番はありません。
requestsが優秀すぎるためです。(CURLも一応あります。)
requests 使い方
パッケージ管理モジュールで一発でインストールできます。
$ pip install requests
pip以外でやってみた事はないんですが、多分condaとかeasy install とかでも普通に入ると思います。
スクリプト内で利用する際はimportする必要があります。
>>>import requests
requestsの基本メソッド
requestsの基本メソッドはHTTPリクエストのメソッドと同じ名称なので非常に覚えやすいです。
以下で一挙に紹介します。
requests.get
HTTPで一番利用頻度の高いGETメソッドは、requests.get として要求します。
>>> url = 'http://httpbin.org/get' >>> requests.get(url) <Response [200]>
第一引数はアクセス先のURI、第二引数以降はキーワード引数で各種パラメータを受け付けます。
これらの挙動は各種メソッドに共通します。
params引数
getメソッドに限らず、クエリストリングをキーワード引数「params」として渡すことが出来ます。
pythonの辞書型で引きた渡すとrequestsが自動的にURLエンコードしてくれます。
>>> query = {'cuntory': 'jp'} >>> res = requests.get(url, params=query) >>> res.url 'https://www.amazon.co.jp/?cuntory=jp'
requests.post
HTTPリクエストの中でgetの次に使用頻度が高いPOSTもそのまま、requests.postとして要求します。
>>> url = 'http://httpbin.org/post' >>> requests.post(url)
data引数
基本的にpostメソッドが必要になる展開では、URL単体でリクエストすることは稀で、実用上はリクエストボディを送信するパターンが大半かと思われますが、リクエストボディはキーワード引数「data」として引き渡します。
>>> body = {'email': '[email protected]', 'password': 'passpass'} >>> requests.post(url, data=body)
requests.put
このあたりから利用頻度はぐっと減りますが、restfulなweb apiなどでは使う可能性のあるメソッドです。
POSTがリソースの作成を担当するというのが定義上ではされていますが、PUTはリソースの作成に加えて、「置き換え」を担当するメソッドです。
>>> url = 'http://httpbin.org/put' >>> body = {'put': 'test'} >>> requests.put(url, data=json.dumps(body))
requests.delete
DELETEは見たまんまリソースの削除に使われるメソッドです。
>>> url = 'http://httpbin.org/delete' >>> requests.delete(url)
このほかHTTPのHEAD、PATCHやOPTIONSに対応するメソッドもそのままの名前のメソッドして実装されています。
requests 各種メソッドに渡せるキーワード引数
上でも少し解説してますが、requestsではあらゆるHTTPリクエストを簡単に表現できるように各種メソッドにキーワード引数で必要な情報を渡すことが出来ます。
基本的に key / value の構造になっているデータはpythonの辞書型で引き渡すと、requestsが内部でよしなに変換してくれるというのが非常に便利な部分です。
引数:headers
この引数に辞書を渡すことによって、HTTPのリクエストヘッダを書き換えることが可能です。
大抵のリクエストヘッダ情報はrequestsがいい感じに書き換えてくれるので、特別操作する必要がある項目自体は意外と少ないのですが、よくある書き換えポイントとしてはオーソライゼンーションヘッダや、ユーザーエージェントの書き換えですね。
>>> url = 'http://httpbin.org/get' >>> headers = {'authorization': 'sadgjkherwsdfgsdaf7243rhj'} >>> requests.get(url, headers=headers)
ちなみに、ディフォルトの状態だとユーザーエージェントは「python-requests/2.○○.0」となっており、Botアクセスであることがサーバー側にまるわかりですので、対策をしてるサイトにアクセスするとUA偽装なしの場合は簡単に弾かれます。
引数:params
上の項で少し解説しましたが、params引数に辞書を渡すと、URLのクエリストリングとして付加したURLにアクセスします。
>>> payload = {'query': 'string'} >>> requests.get(url, params=payload)
引数:data
こちらも上の項で少し解説しましたが、リクエストボディとして投げる文字列をdata引数として引き渡すことが出来ます。
辞書として渡す
>>> url = 'http://httpbin.org/post' >>> body = {'email': '[email protected]', 'password': 'passpass'} >>> res = requests.post(url, data=body)
data引数に辞書型を引き渡した場合、モジュール内で自動的にURLエンドーディングされたクエリストリング型の文字列に変換されリクエストボディとして送信されます。
上記の例では実際に送信されるリクエストボディは以下の様な文字列です。
'email=test%40lets-hack.tech&password=passpass'
data引数に辞書を渡した場合は、リクエストヘッダにContent-Typeが application/x-www-form-urlencoded として自動的に付加されます。
>>> res.json() {'args': {}, 'data': '', 'files': {}, 'form': {'email': '[email protected]', 'password': 'passpass'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '45', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.23.0', 'X-Amzn-Trace-Id': 'Root=1-5f18aa7b-a8bcb470b1b849b87e35f4f8'},
文字列として渡す
data引数に辞書を渡さずに文字列型を渡すとその文字列がそのまま渡されます。
リクエストボディをjsonやxml、その他の文字列型で投げたい場合はそのまま標準のstr型テキストとしてdata引数に渡せばOKです。
>>> import json >>> body = {'email': '[email protected]', 'password': 'passpass'} >>> res = requests.post(url, data=json.dumps(body))
ただし、辞書型で引き渡した情報と違い文字列型で引き渡した場合はContent-Typeが自動付与されませんので、必要な場合は自分で、リクエストヘッダを書き換える必要があります。
>>> res.json() {'args': {}, 'data': '{"email": "[email protected]", "password": "passpass"}', 'files': {}, 'form': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '56', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.23.0', 'X-Amzn-Trace-Id': 'Root=1-5f18ad06-97420500e1e812380b1ed580'}, 'json': {'email': '[email protected]', 'password': 'passpass'}, 'origin': '119.24.206.6', 'url': 'http://httpbin.org/post'}
引数:json
リクエストボディをjsonで表現したい場合は、上で挙げたdata引数よりも、こちらのjson引数として取り扱う方が便利です。
引き渡し方は辞書型で、requestsが自動的にjson型の文字列に変換した文字列をリクエストボディとして送信してくれます。
>>> url = 'http://httpbin.org/post' >>> body = {'email': '[email protected]', 'password': 'passpass'} >>> res=requests.post(url, json=body)
この場合、data引数にjson文字列を引き渡した場合と異なり、自動的にContent-Typeがapplication/jsonとして書き換えらえ送信されます。
>>> res.json() {'args': {}, 'data': '{"email": "[email protected]", "password": "passpass"}', 'files': {}, 'form': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '56', 'Content-Type': 'application/json', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.23.0', 'X-Amzn-Trace-Id': 'Root=1-5f18af01-d5d71f60be72ed70e9567eb0'}, 'json': {'email': '[email protected]', 'password': 'passpass'}, 'origin': '119.24.206.6', 'url': 'http://httpbin.org/post'}
引数:files
バイナリータイプのデータを送信することが出来ます。
マルチパートエンコードというタイプの送信手段になります。
>>> url = 'http://httpbin.org/post' >>> files = {'file': open('report.xls', 'rb')} >>> res = requests.post(url, files=files)
受け入れ先のURLがファイル名やmimetype等を受け付ける場合、またはそれらが必須の要素である場合がありますが、辞書のvalue側をタプルで括って、送信するファイルの複数の属性を受け渡すことが可能です。
>>> url = 'http://httpbin.org/post' >>> img_bin = open('image.jpg', 'rb') >>> file_name = 'image.jpg' >>> mimetype = 'image/jpeg' >>> files = {'file': (file_name, img_bin, mimetype)} >>> res = requests.post(url, files=files)
引数:timeout
リクエストのタイムアウトを設定できます。
requests内のタイムアウトは内部的に二種類存在していて、これらtimeoutの引数で受け渡すことが出来る数値の範疇に、データのダウンロードなど実際の通信本体に要した時間は含まれません。
実際通信の本体部分が開始される前段階をそれぞれ段階ごとに分けてタイムアウト設定することが可能です。
connect timeout
通信相手のサーバーとの通信を確立するまでの時間のタイムアウトコントロールです。
相手方のサーバーがダウンしているなど、「そもそも通信を開始できない状況」であればこちらのconnect timeoutが発生します。
read timeout
通信が確立された後、相手方のサーバーが応答の最後のバイトを送出してからのタイムアウトです。
通信が終了しておらず、相手方サーバーからの応答がtimeout引数で設定した時間以上空白となった場合、こちらのread timeoutが発生します。
こちらはダウンしているとかではなく、過負荷など処理に時間がかかって応答できない状態である場合に発生するものです。
timeoutの引き渡し方
上記二種類のタイムアウトをそれぞれ整数型(int)、または浮動小数型(float)で引き渡すことが出来ます。
>>> res = requests.get(url, timeout=3)
connect timeout、read timeoutをそれぞれ別々の数値で指定したい場合はタプルで引き渡すことが出来ます。
>>> res = requests.get(url, timeout=(3, 10))
タプル内の第一要素がconnect timeout、第二要素がread timeoutとして取り扱われます。
requestsはディフォルトではタイムアウトしない設定になっているため、GUIを使った通信を行うアプリケーションなどを製作している場合、通信相手方からのレスポンスを待ち続けて、GUIがフリーズするなんてこともよく起きますので、可能な限りtimeoutを設定してエラーハンドリングしておくのがベターです。
引数:stream
レスポンス本体をイテレータの形式で取得することが可能になります。
これにより応答が完全に完了する前からデータの読み取りを開始することが可能になります。
指定方法はTrueのみ。(ディフォルトがFalseなので意味なし)
>>> requests.get(url, stream=True)
引数:allow_redirects
通常requestsのレスポンスは最終的な結果が返ってきますので、リダイレクトのあるURLにアクセスした場合は最終着地した200番台のレスポンスオブジェクトが本体として返されます。
レスポンスオブジェクトのhistory属性としてリダイレクトの履歴をたどることは可能ですが、そもそもリダイレクトを許容しない設定にすることも可能です。
指定方法はFalseのみ。(ディフォルトがTrueなので意味なし)
>>> requests.get(url, allow_redirects=False)
これにより300番台のレスポンスを直接取得することが出来ます。
requests Responseオブジェクトの操作
属性:url
実際にアクセスした先のurl(クエリストリングを含む)を返却するアトリビュートです。
>>> payload = {'key1': 'value1', 'key2': 'value2'} >>> res = requests.get(url, params=payload) >>> res.url 'http://httpbin.org/get?key1=value1&key2=value2'
属性:text
HTTPリクエストのレスポンス本文をテキストの形式で返します。
>>> res = requests.get(url) >>> res.text '{n "args": {n "key1": "value1", n "key2": "value2"n }, n "headers": {n "Accept": "*/*", n "Accept-Encoding": "gzip, deflate", n "Host": "httpbin.org", n "User-Agent": "python-requests/2.23.0", n "X-Amzn-Trace-Id": "Root=1-5f18c39d-115dd5fc1c094d74555c1ea0"n }, n "origin": "119.24.206.6", n "url": "http://httpbin.org/get?key1=value1&key2=value2"n}n'
属性:content
HTTPリクエストのレスポンス本文をbytes(バイナリ)の形式で返します。
>>> res = requests.get(url) >>> res.content b'{n "args": {n "key1": "value1", n "key2": "value2"n }, n "headers": {n "Accept": "*/*", n "Accept-Encoding": "gzip, deflate", n "Host": "httpbin.org", n "User-Agent": "python-requests/2.23.0", n "X-Amzn-Trace-Id": "Root=1-5f18c39d-115dd5fc1c094d74555c1ea0"n }, n "origin": "119.24.206.6", n "url": "http://httpbin.org/get?key1=value1&key2=value2"n}n'
属性:headers
レスポンスヘッダを辞書型で返却するアトリビュート。
リクエスト時のヘッダではないので注意。
>>> res.headers {'Content-Type': 'text/html', 'Content-Length': '2340', 'Connection': 'keep-alive', 'Server': 'Server', 'Date': 'Thu, 23 Jul 2020 00:33:24 GMT', 'x-amz-rid': '8GSVQM12M33H1T36N690', 'Vary': 'Content-Type,Accept-Encoding,X-Amzn-CDN-Cache,X-Amzn-AX-Treatment,User-Agent', 'Content-Encoding': 'gzip', 'X-Cache': 'Error from cloudfront', 'Via': '1.1 4c88cf886add957cd777a3b7eec7de7c.cloudfront.net (CloudFront)', 'X-Amz-Cf-Pop': 'NRT20-C1', 'X-Amz-Cf-Id': 'fkZc7L_3MA8MY1San4X3P23BCrtss7kYf9-K6r-YOltcYzLu71QQGA=='}
属性:encoding
requestsがレスポンスを解析したencoding形式を返却するアトリビュート。
>>> res.encoding 'ISO-8859-1'
属性:history
アクセスしたページにリダイレクトが設定されていた場合、その遷移の履歴がリストとしてhistory属性に格納されます。
>>> res.history []
属性:status_code
レスポンスのステータスコードがint型で返却されます。
>>> res.status_code 200
属性:request
実際に送信したリクエストの各種パラメータがPreparedRequestオブジェクトとして返却されます。
>>> res.request.__dict__ {'method': 'GET', 'url': 'http://httpbin.org/get?key1=value1&key2=value2', 'headers': {'User-Agent': 'python-requests/2.23.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}, '_cookies': <RequestsCookieJar[]>, 'body': None, 'hooks': {'response': []}, '_body_position': None}
PreparedRequestの__dict__アトリビュートにアクセスすると辞書型で実際に要求したリクエストを確認することが出来ます。
デバッグ時などに便利です。
属性:elapsed
要求を完了するまでにかかった時間をビルトインのdatetime型の派生datetime.timedelta型として返却します。
>>> res.elapsed datetime.timedelta(microseconds=581942)
属性:reason
リクエストの最終的なレスポンスの状態を英文の文字列として返却します。
200系の返却なら’OK’、400系、500系ならなぜダメだったのかのエラーメッセージの様なものが返ってきます。
# 問題ない場合 >>> res.reason 'OK' # なんか問題がある場合 >>> res.reason 'METHOD NOT ALLOWED'
属性:cookies
そのURLにアクセスすることによって付与されたCookieをRequestsCookieJarオブジェクトとして返却します。
>>> res.cookies <RequestsCookieJar[Cookie(version=0, name='skin', value='noskin', port=None, port_specified=False, domain='.amazon.co.jp', domain_specified=True, domain_initial_dot=True, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False)]>
属性:raw
requestsの正体はurllib3のラッパーモジュールの様なものなのですが、rawアトリビュートのurallib3で受け取った生のレスポンスオブジェクトにアクセスすることが出来ます。
>>> res.raw <urllib3.response.HTTPResponse at 0x2747851fa88>
実はほぼ使ったことないですし、使い処もイマイチ思い浮かびません。
メソッド:json()
HTTPリクエストのレスポンスボディがJSON形式のテキストデータであった場合、pythonのビルトインの辞書型(dict)に変換して返却します。
requestsが内部的にjson.loads(res.text)をしてくれているイメージですね。
>>> res.json() {'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.23.0', 'X-Amzn-Trace-Id': 'Root=1-5f18d971-9c80c6af346900f90ed87a18'}, 'origin': '119.24.206.6', 'url': 'http://httpbin.org/get'}
Web APIを利用する場合は、最近ではJSON形式での返却はディファクトスタンダードになりつつありますので、非常によく使うメソッドです。
レスポンスボディがJSON形式のテキストデータでない場合(例えばHTML形式など)、ビルトインのjson.loads()が失敗した時と同じJSONDecodeErrorが発生します。
メソッド:raise_for_status()
レスポンスのステータスコードが200系の場合は、特に何も起きず、200以外のエラーステータスコードだった場合は例外を発生させます。
res.raise_for_status() Traceback (most recent call last): File "C:UsersOwnerAppDataLocalProgramsPythonPython37libsite-packagesIPythoncoreinteractiveshell.py", line 3331, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-122-cd6be6b74546>", line 1, in <module> res.raise_for_status() File "C:UsersOwnerAppDataLocalProgramsPythonPython37libsite-packagesrequestsmodels.py", line 941, in raise_for_status raise HTTPError(http_error_msg, response=self) requests.exceptions.HTTPError: 503 Server Error: Service Unavailable for url: https://www.amazon.co.jp/
メソッド:iter_content()
レスポンスボディにイテレータアクセスするためのメソッド。
上で解説しているstream引数にTrueを与えて要求したリエクエストのレスポンスオブジェクトに対して有効。
>>> file_name = 'image.jpg' >>> res = requests.get(url, stream=True) >>> with open(file_name, 'wb') as file: >>> for chunk in res.iter_content(chunk_size=1024): >>> file.write(chunk)
コメント