Pythonリクエストの高度な使用方法
31280 ワード
PythonのHTTPライブラリrequests おそらく私のプログラムのすべての言語で私のお気に入りのHTTPユーティリティです.これは、シンプルで直感的で、ユビキタスのPythonコミュニティです.HTTPとのインタフェースのほとんどのプログラムは標準ライブラリからのリクエストかurllib 3を使用します.
簡単なAPIのため、すぐに要求を生産するのは簡単ですが、ライブラリはまた、高度なユースケースの拡張性を提供しています.あなたがAPI重いクライアントまたはウェブスクレーパーを書くならば、あなたはおそらくネットワーク失敗、役に立つデバッグ跡と構文上の砂糖に寛容を必要とします.
以下は、JSON APIを広範囲に使用しているウェブ・スクレーピング・ツールまたはプログラムを書くとき、私が要求において役に立つとわかる特徴の概要です.
Request hooks Setting base URLs Setting default timeouts Retry on failure Debugging HTTP requests Testing and mocking requests Mimicking browser behaviors
サードパーティAPIを使用する場合、返される応答が実際に有効であることを確認します.リクエストは速記ヘルパーを提供する
例えば
リクエストプロセスのある部分について.
我々は、フックを使用することができます
APIでホストされているAPIだけを使用しているとします.org. HTTPプロトコルごとにプロトコルとドメインを繰り返します.
リクエストドキュメントrecommends すべてのプロダクションコードでタイムアウトを設定します.タイムアウトを設定するのを忘れてしまった場合は、あなたのアプリケーションをハングさせるかもしれません.
使用Transport Adapters すべてのHTTPコールのデフォルトタイムアウトを設定できます.これは、開発者がtimeout = 1パラメータを彼の個々の呼び出しに加えるのを忘れても、賢明なタイムアウトがセットされるのを確実にしますが、1回の呼び出しベースでオーバーライドを考慮に入れます.
既定のタイムアウトを持つカスタムトランスポートアダプタの例ですthis Github comment . timeout引き数が与えられていない場合は、デフォルトのタイムアウトを確実にするためにHTTPクライアントとsend ()メソッドを構築するときにコンストラクタをオーバーライドしてデフォルトのタイムアウトを提供します.
ネットワーク接続は損失、混雑し、サーバーが失敗します.本当に堅牢なプログラムを作りたいならば、我々は失敗を説明して、再試行戦略を必要とします.
あなたのHTTPクライアントに再試行戦略を追加簡単です.HttpAdapterを作成し、アダプターに戦略を渡します.
以下のパラメータはリクエストライブラリが使用するデフォルトパラメータを含みます.
1秒連続眠る 2秒- 10秒 値は指数関数的に増加するretry strategies .
この値はデフォルト0で、指数関数のバックオフは設定されず、再試行は直ちに実行されます.これを1に設定してください
あなたのサーバ!
再試行モジュールの完全な文書はhere .
HttpAdapterは同等ですので、再試行とタイムアウトを組み合わせることができます.
時々、要求は失敗します、そして、あなたは理由を理解することができません.要求と応答をログ出力すると、失敗に対する洞察を与えるかもしれません.これを行うには2つの方法があります.
0より大きいログレベルのログレベルを変更すると、レスポンスHTTPヘッダがログ出力されます.これは最も簡単なオプションですが、HTTPリクエストやレスポンス本文を見ることはできません.ロギングに適していないか、バイナリ内容を含む大きなボディーペイロードを返すAPIを扱うなら、それは役に立ちます.
0より大きい値はデバッグログを有効にします.
リクエストと応答のテキスト表現の両方を含むHTTP LifeCycle全体をログに記録する場合は、RequestsCount ToolBarからリクエストフックとダンプUtilsを使用できます.
私は非常に大きな応答を返さない残りのベースのAPIに対処しているいつでもこのオプションを好む.
サードパーティAPIを使用して開発に痛みポイントを導入する-彼らはユニットテストに困難です.エンジニアSentry 開発中にモックスリクエストにライブラリを書くことでこの痛みの一部を緩和しました.
HTTPレスポンスをサーバに送信する代わりにgetsentry/responses HTTPリクエストを傍受し、テスト中に追加した応答を返します.
それは例でより実証されます.
あなたが十分なウェブスクレーパーコードを書いたならば、あなたはブラウザーを使用しているか、プログラム的にサイトにアクセスしているならば、特定のウェブサイトが異なるHTMLを返す方法に気がつきます.時々、これは反掻き対策です、しかし、通常、サーバーはどのような内容が最高の装置(例えばデスクトップまたはモバイル)に合うかについて調べるために、ユーザーエージェントSnffffingに取り組みます.
ブラウザの表示と同じ内容を返したい場合は、ユーザエージェントのヘッダリクエストをオーバーライドできます.
簡単なAPIのため、すぐに要求を生産するのは簡単ですが、ライブラリはまた、高度なユースケースの拡張性を提供しています.あなたがAPI重いクライアントまたはウェブスクレーパーを書くならば、あなたはおそらくネットワーク失敗、役に立つデバッグ跡と構文上の砂糖に寛容を必要とします.
以下は、JSON APIを広範囲に使用しているウェブ・スクレーピング・ツールまたはプログラムを書くとき、私が要求において役に立つとわかる特徴の概要です.
目次
リクエストフック
サードパーティAPIを使用する場合、返される応答が実際に有効であることを確認します.リクエストは速記ヘルパーを提供する
raise_for_status()
これは、応答HTTPステータスコードが4 xxまたは5 xxでないと主張します、すなわち、要求がクライアントかサーバエラーをもたらさなかったということです.例えば
response = requests.get('https://api.github.com/user/repos?page=1')
# Assert that there were no errors
response.raise_for_status()
必要に応じてこれは繰り返し得ることができますraise_for_status()
呼び出しごとに.幸いにもリクエストライブラリは、コールバックを付けることができるフックのインターフェイスを提供していますリクエストプロセスのある部分について.
我々は、フックを使用することができます
raise_for_status()
が各レスポンスオブジェクトに対して呼び出されます.# Create a custom requests object, modifying the global module throws an error
http = requests.Session()
assert_status_hook = lambda response, *args, **kwargs: response.raise_for_status()
http.hooks["response"] = [assert_status_hook]
http.get("https://api.github.com/user/repos?page=1")
> HTTPError: 401 Client Error: Unauthorized for url: https://api.github.com/user/repos?page=1
ベースURLの設定
APIでホストされているAPIだけを使用しているとします.org. HTTPプロトコルごとにプロトコルとドメインを繰り返します.
requests.get('https://api.org/list/')
requests.get('https://api.org/list/3/item')
あなた自身を使用していくつかの入力を保存することができますBaseUrlSession . これにより、HTTPクライアントの基本URLを指定し、リクエストのときにリソースパスを指定できます.from requests_toolbelt import sessions
http = sessions.BaseUrlSession(base_url="https://api.org")
http.get("/list")
http.get("/list/item")
注意requests toolbelt デフォルトのリクエストのインストールには含まれませんので、別途インストールする必要があります.デフォルトのタイムアウトの設定
リクエストドキュメントrecommends すべてのプロダクションコードでタイムアウトを設定します.タイムアウトを設定するのを忘れてしまった場合は、あなたのアプリケーションをハングさせるかもしれません.
requests.get('https://github.com/', timeout=0.001)
しかし、これは繰り返しになり、誰かがタイムアウトを設定し、生産のプログラムを停止していることを認識したときに将来のテーブルの反転を引き起こす可能性があります.使用Transport Adapters すべてのHTTPコールのデフォルトタイムアウトを設定できます.これは、開発者がtimeout = 1パラメータを彼の個々の呼び出しに加えるのを忘れても、賢明なタイムアウトがセットされるのを確実にしますが、1回の呼び出しベースでオーバーライドを考慮に入れます.
既定のタイムアウトを持つカスタムトランスポートアダプタの例ですthis Github comment . timeout引き数が与えられていない場合は、デフォルトのタイムアウトを確実にするためにHTTPクライアントとsend ()メソッドを構築するときにコンストラクタをオーバーライドしてデフォルトのタイムアウトを提供します.
from requests.adapters import HTTPAdapter
DEFAULT_TIMEOUT = 5 # seconds
class TimeoutHTTPAdapter(HTTPAdapter):
def __init__(self, *args, **kwargs):
self.timeout = DEFAULT_TIMEOUT
if "timeout" in kwargs:
self.timeout = kwargs["timeout"]
del kwargs["timeout"]
super().__init__(*args, **kwargs)
def send(self, request, **kwargs):
timeout = kwargs.get("timeout")
if timeout is None:
kwargs["timeout"] = self.timeout
return super().send(request, **kwargs)
以下のように使えます:import requests
http = requests.Session()
# Mount it for both http and https usage
adapter = TimeoutHTTPAdapter(timeout=2.5)
http.mount("https://", adapter)
http.mount("http://", adapter)
# Use the default 2.5s timeout
response = http.get("https://api.twilio.com/")
# Override the timeout as usual for specific requests
response = http.get("https://api.twilio.com/", timeout=10)
失敗を再試行する
ネットワーク接続は損失、混雑し、サーバーが失敗します.本当に堅牢なプログラムを作りたいならば、我々は失敗を説明して、再試行戦略を必要とします.
あなたのHTTPクライアントに再試行戦略を追加簡単です.HttpAdapterを作成し、アダプターに戦略を渡します.
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
retry_strategy = Retry(
total=3,
status_forcelist=[429, 500, 502, 503, 504],
method_whitelist=["HEAD", "GET", "OPTIONS"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
http = requests.Session()
http.mount("https://", adapter)
http.mount("http://", adapter)
response = http.get("https://en.wikipedia.org/w/api.php")
デフォルトの再試行クラスは、saneのデフォルト値を提供しますが、非常に設定可能です.以下のパラメータはリクエストライブラリが使用するデフォルトパラメータを含みます.
total=10
再試行の合計数を試みます.失敗したリクエストまたはリダイレクトの数がこの数を超える場合、クライアントはurllib3.exceptions.MaxRetryError
例外.私は、私が働いているAPIに基づいて、このパラメタを変えます、しかし、通常、私は通常10より低くそれをセットしました.status_forcelist=[413, 429, 503]
HTTPレスポンスコードを再試行します.サーバと逆プロキシがHTTP仕様に常に適合していないので、一般的なサーバエラー(500、502、503、504)で再試行したいと思います.method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"]
再試行するHTTPメソッド.デフォルトでは、POSTを除くすべてのHTTPメソッドが含まれています.POSTは新しい挿入を行うことができます.このパラメータをポストに含めるように変更します.APIのほとんどは、エラーコードを返さず、同じ呼び出しで挿入を実行しないからです.そして、彼らがするならば、あなたは多分バグ報告を出すべきです.backoff_factor=0
これは面白いものです.これは、プロセスが失敗した要求の間にスリープ状態になるかを変更することができます.アルゴリズムは以下の通りです.{backoff factor} * (2 ** ({number of total retries} - 1))
例えば、バックオフ係数が設定されている場合、0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256
. 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560
この値はデフォルト0で、指数関数のバックオフは設定されず、再試行は直ちに実行されます.これを1に設定してください
あなたのサーバ!
再試行モジュールの完全な文書はhere .
タイムアウトとリトライの組み合わせ
HttpAdapterは同等ですので、再試行とタイムアウトを組み合わせることができます.
retries = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504])
http.mount("https://", TimeoutHTTPAdapter(max_retries=retries))
要求のデバッグ
時々、要求は失敗します、そして、あなたは理由を理解することができません.要求と応答をログ出力すると、失敗に対する洞察を与えるかもしれません.これを行うには2つの方法があります.
HTTPヘッダの出力
0より大きいログレベルのログレベルを変更すると、レスポンスHTTPヘッダがログ出力されます.これは最も簡単なオプションですが、HTTPリクエストやレスポンス本文を見ることはできません.ロギングに適していないか、バイナリ内容を含む大きなボディーペイロードを返すAPIを扱うなら、それは役に立ちます.
0より大きい値はデバッグログを有効にします.
import requests
import http
http.client.HTTPConnection.debuglevel = 1
requests.get("https://www.google.com/")
# Output
send: b'GET / HTTP/1.1\r\nHost: www.google.com\r\nUser-Agent: python-requests/2.22.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Fri, 28 Feb 2020 12:13:26 GMT
header: Expires: -1
header: Cache-Control: private, max-age=0
印刷すべて
リクエストと応答のテキスト表現の両方を含むHTTP LifeCycle全体をログに記録する場合は、RequestsCount ToolBarからリクエストフックとダンプUtilsを使用できます.
私は非常に大きな応答を返さない残りのベースのAPIに対処しているいつでもこのオプションを好む.
import requests
from requests_toolbelt.utils import dump
def logging_hook(response, *args, **kwargs):
data = dump.dump_all(response)
print(data.decode('utf-8'))
http = requests.Session()
http.hooks["response"] = [logging_hook]
http.get("https://api.openaq.org/v1/cities", params={"country": "BA"})
# Output
< GET /v1/cities?country=BA HTTP/1.1
< Host: api.openaq.org
> HTTP/1.1 200 OK
> Content-Type: application/json; charset=utf-8
> Transfer-Encoding: chunked
> Connection: keep-alive
>
{
"meta":{
"name":"openaq-api",
"license":"CC BY 4.0",
"website":"https://docs.openaq.org/",
"page":1,
"limit":100,
"found":1
},
"results":[
{
"country":"BA",
"name":"Goražde",
"city":"Goražde",
"count":70797,
"locations":1
}
]
}
参照https://toolbelt.readthedocs.io/en/latest/dumputils.html テストとモッキング要求
サードパーティAPIを使用して開発に痛みポイントを導入する-彼らはユニットテストに困難です.エンジニアSentry 開発中にモックスリクエストにライブラリを書くことでこの痛みの一部を緩和しました.
HTTPレスポンスをサーバに送信する代わりにgetsentry/responses HTTPリクエストを傍受し、テスト中に追加した応答を返します.
それは例でより実証されます.
import unittest
import requests
import responses
class TestAPI(unittest.TestCase):
@responses.activate # intercept HTTP calls within this method
def test_simple(self):
response_data = {
"id": "ch_1GH8so2eZvKYlo2CSMeAfRqt",
"object": "charge",
"customer": {"id": "cu_1GGwoc2eZvKYlo2CL2m31GRn", "object": "customer"},
}
# mock the Stripe API
responses.add(
responses.GET,
"https://api.stripe.com/v1/charges",
json=response_data,
)
response = requests.get("https://api.stripe.com/v1/charges")
self.assertEqual(response.json(), response_data)
mockked応答にマッチしないHTTPリクエストが作成された場合、ConnectionErrorがスローされます.class TestAPI(unittest.TestCase):
@responses.activate
def test_simple(self):
responses.add(responses.GET, "https://api.stripe.com/v1/charges")
response = requests.get("https://invalid-request.com")
出力requests.exceptions.ConnectionError: Connection refused by Responses - the call doesn't match any registered mock.
Request:
- GET https://invalid-request.com/
Available matches:
- GET https://api.stripe.com/v1/charges
ブラウザの動作を模倣する
あなたが十分なウェブスクレーパーコードを書いたならば、あなたはブラウザーを使用しているか、プログラム的にサイトにアクセスしているならば、特定のウェブサイトが異なるHTMLを返す方法に気がつきます.時々、これは反掻き対策です、しかし、通常、サーバーはどのような内容が最高の装置(例えばデスクトップまたはモバイル)に合うかについて調べるために、ユーザーエージェントSnffffingに取り組みます.
ブラウザの表示と同じ内容を返したい場合は、ユーザエージェントのヘッダリクエストをオーバーライドできます.
import requests
http = requests.Session()
http.headers.update({
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"
})
Reference
この問題について(Pythonリクエストの高度な使用方法), 我々は、より多くの情報をここで見つけました https://dev.to/danihodovic/advanced-usage-of-python-requests-timeouts-retries-hooks-kokテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol