今回はPythonを使ったプログラムで、リクエスト時にサーバエラーが発生した場合の対応方法について紹介をします。最近はPythonを使ったWEBサービスが増えているので、この悩みで躓くケースもかなり増えているはずなので。
このサイトではPythonで仮想通貨BOTを作成する方法をご紹介しているので、今回はBOTを作る場合を例にしてご紹介していきます。
本来の処理とは関係がないのですが、エラーチェックや異常系処理というのはシステムにとって、とても大切なのです。BOTのエラーの中でもおそらく一番多いのはリクエスト時のサーバ処理に関するエラーです。
ちなみに、これらのエラーは「例外処理」というものを使って回避します。例外処理については以前も紹介していますので、詳細はこちらをご確認下さい。
今回ご紹介する内容は、仮想通貨BOTに限らず、Pythonでエラー処理を行う際に参考になるはずですのでしっかりと学んでおきたい部分です。
コンテンツ
サーバのエラーについて
では、具体的にサーバ側のエラーについて紹介したいと思います。
エラーはどんな時に発生する?
サーバエラー(もしくはリクエストエラー)とは、APIなどで別のサーバにアクセスできない、とか、正しい応答結果が帰って来ない(大抵はタイムアウトとなる)という場合に発生します。
他のサーバにリクエストをしてエラーとなった際は、ステータスとしてエラーコードの番号が返ってくるのが基本ルールです。サーバエラーの際は「500」番台のコードが返ってくるのが一般的です。なので500エラーなどと呼ばれたりもします。
※例えば、200番台は正常系処理である事が多いので、200番台だったら、後続の処理を継続。それ以外は、エラー処理をする。といった判定をする事が多いです。ちなみに400番台はクライアントエラーです
サーバのエラーにはどの様なケースが考えられるでしょうか。基本的に考えられるのは以下の様なものです。
・CryptowatchAPIへのリクエストが応答待ちのエラーになった
・取引所のAPIへのリクエストがサーバに届かずにエラーになった
※新規注文、注文一覧の取得等々
・CCXTの共通関数(結局は取引所のAPI)のリクエスト結果が取引所のサーバに届かずにエラーになった
・リクエストするURLが間違っている
基本的に、APIなどのサーバにリクエスト送信した時のエラーが原因です。
エラーが発生したらどうする?
さて、実際にサーバエラーが発生した場合、どうすれば良いでしょうか?
サーバエラーが発生した場合の対処としてオーソドックスなのは「もう一度リクエストする」という解決方法です。リクエストを行う関数をループ文で囲んで、正常に処理されるまで何度もリクエストし続けるという処理がよく使われます。
但し、サーバ自体が停止していたりすると無限ループになってしまいますので、ループ回数や時間などを決めて関数にするケースが多いです。
この後で実際にループを使って、エラー発生時の処理を作ってみましょう。
requestsのライブラリで定義されたエラーが発生
Pythonではサーバにリクエストする際は、requestsライブラリを使用するのが一般的だと思います。このサイトで紹介しているプログラムでも基本的にはrequestsライブラリを使用しています。
CryptowatchAPIからデータを取得するコードを元にして話を進めます。
CryptowatchAPIからのデータ取得処理
以下が、CryptowatchAPIからデータを取得してDataFrameに設定しているコードになります。2018/5/1~2018/5/5のデータを取得しています。
■Pythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#-------- ライブラリ・モジュール -----------# import requests from datetime import datetime import pandas as pd #-------- サブ関数 -------------------------# #OHLCデータ取得関数 def get_data(min, before=0, after=0): #パラメータ設定する params = {"periods" : min } if before != 0: params["before"] = before if after != 0: params["after"] = after #リクエスト送信 response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohlc",params) data = response.json() #OHLC情報をDataFrameに設定する Time_Data, Open_price, High_price, Low_price, Close_price = [],[],[],[],[] for ohlc in data["result"][str(min)]: Time_Data.append(datetime.fromtimestamp(ohlc[0])) Open_price.append(ohlc[1]) High_price.append(ohlc[2]) Low_price.append(ohlc[3]) Close_price.append(ohlc[4]) df = pd.DataFrame({'Time':Time_Data, 'open':Open_price, 'high':High_price, 'low':Low_price, 'close':Close_price}) return df #-------- main -------------------------# #変数の定義 min = 86400 #1日(86400秒) after = datetime(2018, 5, 1).strftime('%s') #OHLCの取得開始日 before = datetime(2018, 5, 5).strftime('%s') #OHLCの取得終了日 #OHLCデータの取得 df = get_data(min,before,after) print(df) |
■実行結果
1 2 3 4 5 6 |
Time open high low close 0 2018-05-01 1020050.0 1021490.0 990390.0 1010790.0 1 2018-05-02 1010781.0 1011998.0 971273.0 994790.0 2 2018-05-03 994897.0 1013000.0 986500.0 1009408.0 3 2018-05-04 1009394.0 1068000.0 1003085.0 1061198.0 4 2018-05-05 1060600.0 1063050.0 1036474.0 1054800.0 |
■解説
処理の流れの詳細はこれまでに他の記事でお伝えしているので、そちらを参考にしてほしいですが、処理結果を見ると2018/5/1~2018/5/5のOHLCデータが正しく取得できている事が分かります。
ではリクエスト時にサーバエラーが発生した場合の対応方法を見ていきましょう。
requestsのエラー判定は「raise_for_status関数」!
requestsライブラリにはリクエストのステータスを教えてくれる関数があります。
それが「raise_for_status()」という関数です。
※requestsの仕様の詳細については実際の仕様書を参考にして下さい。
このraise_for_status()をリクエスト送信時のコードの中に埋め込んでみます。
リクエストの結果、エラーにならない場合
■Pythonコード(抜粋)
1 2 3 |
#リクエスト送信 response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohlc",params) print("ステータスは" + str(response.raise_for_status())) |
■実行結果(抜粋)
1 |
ステータスはNone |
■解説
この場合、ステータスは「None」となっています。
つまり、リクエストエラーは発生していないという事になります。
リクエストエラーとなる場合
次はエラーが発生する場合を考えてみましょう。
実際のサーバエラーが発生した場合が一番分かりやすいのですが、簡単にリクエストエラーの状態を作れる404エラーで試してみたいと思います。
404エラーは指定のURLが存在しない場合に発生します。
今回はわざとエラーを発生させるために、以下の様にリクエストURLを存在しないものに変更してみます。
■正しいURL
1 |
https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohlc |
■誤ったURL
1 |
https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohl |
この誤ったURLを指定して先ほどと同じ様にリクエストをしてステータスを確認してみましょう。
■Pythonコード(抜粋)
1 2 3 |
#リクエスト送信 response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohl",params) response.raise_for_status() |
■実行結果(抜粋、printしなくともエラーの結果を表示してくれます)
1 2 |
raise HTTPError(http_error_msg, response=self) requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohl?periods=86400&before=1525478400&after=1525132800 |
実行結果を見てみると、HTTPError(ステータス:404)が返ってきました。
400番台はクライアントエラーですから、ClientErrorと表示されています。
クライアントというのは、リクエストする側ですから、指定方法に問題がある事を意味しています。(サーバエラーではありません。)
例外処理を入れてみよう!
エラーのステータスの出し方が分かった所で、エラー処理を実装してみましょう。
といってもやり方は簡単です。
例外処理の記載方法
例外処理を入れるときは以下の様なコードを追記します。
1 2 3 4 |
try: #リクエストの処理 except 例外名称 as e: #リクエストエラー時の処理 |
CryptowatchAPIへのリクエストの場合だと「try:」の後にCryptowatchAPIへのリクエスト処理を入れて、「except 例外名称 as e:」の後にエラー時の処理を入れればOKです。
requestsの例外処理
requestsの例外名称は以下になります。
以下の名前を元にして、例外エラーハンドリングをすれば良いという事になります。
■requestsの例外名称
1 |
requests.exceptions.RequestException |
実際のコード
では実際に例外エラーの発生を想定したコードを実装してみましょう。
■Pythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#OHLCデータ取得関数 def get_data(min, before=0, after=0): #パラメータ設定する while True: try: params = {"periods" : min } if before != 0: params["before"] = before if after != 0: params["after"] = after #リクエスト送信 response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohlc",params) response.raise_for_status() data = response.json() #OHLC情報をDataFrameに設定する Time_Data, Open_price, High_price, Low_price, Close_price = [],[],[],[],[] for ohlc in data["result"][str(min)]: Time_Data.append(datetime.fromtimestamp(ohlc[0])) Open_price.append(ohlc[1]) High_price.append(ohlc[2]) Low_price.append(ohlc[3]) Close_price.append(ohlc[4]) df = pd.DataFrame({'Time':Time_Data, 'open':Open_price, 'high':High_price, 'low':Low_price, 'close':Close_price}) return df except requests.exceptions.RequestException as e: print("エラー発生") sleep(1) |
■実行結果(エラーが5回発生した場合)
1 2 3 4 5 6 7 8 9 10 11 |
エラー発生 エラー発生 エラー発生 エラー発生 エラー発生 Time open high low close 0 2018-05-01 1020050.0 1021490.0 990390.0 1010790.0 1 2018-05-02 1010781.0 1011998.0 971273.0 994790.0 2 2018-05-03 994897.0 1013000.0 986500.0 1009408.0 3 2018-05-04 1009394.0 1068000.0 1003085.0 1061198.0 4 2018-05-05 1060600.0 1063050.0 1036474.0 1054800.0 |
ちなみに例外処理時は「e」の内容もprintなどで出力する様にすると、エラー内容も出力されます。
まとめ
今回は例外処理時のコードについて記載をしました。
サーバのエラーなどが発生するとパニックになってしまう方もいますが、これで大丈夫だと思います。
エラーが発生した時の処理方法はいくつかありますので、これが正解!というものではありません。BOTを稼働させながら試行錯誤をして、最適なエラーハンドリングを決めていくのがベターです。