22
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PythonでWebsocketのクライアント実装(bitbankのリアルタイムデータ配信APIがPubNubからSocket.IOに急変したのでその対応)

Last updated at Posted at 2019-10-27

#背景
暗号資産(仮想通貨)の自動売買取引のボットをbitbank上で動かしていたんだけど、リアルタイムの価格情報やローソクチャートを取得するAPIの技術仕様がPubNubからWebsocket(Socket.IO)に変更された

今月に入ってアナウンスがあってPubNubは非推奨となり、将来的に廃止されるとのことだったが、先日のbitbankのメンテナンスで事前の告知なくPubNubが急遽廃止されて、アタフタ...

おそらく、メンテナンスに絡む技術的な問題か何かで急遽廃止せざるを得なくなったのだろう(知らんけどw)(つか、少なくとも事前に廃止告知と、告知後の並行稼働期間は設けてくれよっ!とは思ったわ)

ということで、ビット子ボットちゃんの処理を、早急にWebsocket対応にしたいわけだけど、Websocket扱ったことがないので四苦八苦中、というか、まだ事前の調査段階

#下調べ
公式のドキュメントは、Websocketを知っている人前提の非常に簡単な内容

Pythonでボットは開発しているので、Pythonでの実装方法を知りたいの、ということで色々検索してみたけど、クライアントとしての実装の情報がなかなか見つからない

ドンピシャなコードサンプルはあったけど...

上記は、 import socketio とあるので、おそらく、以下の python-socketio というライブラリを利用していると思われる

他に、以下のあたりをザザッと見たけど、情報が古かったり、求めている情報ではなさそう(検索の仕方が悪いのかな... 適当な情報が見つからない...)

そんな中、以下の結構クリティカルじゃないかと思われるbitbankのこのAPIに関するissueを見つけてしまった...

技術仕様とか、これを使う仕組みを考えたら「5分でセッションを強制的に切っちゃう」とかあり得ないですよ?という2人のユーザー(エンジニア?)からのツッコミに対して、運営(bitbank)の中の人っ?が「"malicious connections" (悪意ある接続) 対応のため」という理由付けで説明をしていることに対して、そういうのは別の対策をすべきでしょっ?というやりとりになっていて... 今後も継続して使うことに不安も覚えるが... とりあえず、まだ、5分で切れちゃう問題は解消されていないので、その前提で実装をする必要がある

ライブラリ側で再接続の考慮がされている場合もあるけど、それがない場合は、プログラム内で考慮してあげるという感じかな... めんどっ...

運営さんからもライブラリに再接続のオプションがあるから、それを使うか自前で実装して的なフォローが書かれていた

で、利用することになりそうな python-socketio のドキュメントを読むと...

reconnection – True if the client should automatically attempt to reconnect to the server after an interruption, or False to not reconnect. The default is True.

reconnection_attempts – How many reconnection attempts to issue before giving up, or 0 for infinity attempts. The default is 0.

reconnection_delay – How long to wait in seconds before the first reconnection attempt. Each successive attempt doubles this delay.

reconnection_delay_max – The maximum delay between reconnection attempts.

ベースになるクラスのインスタンスを作成する時の再接続のオプションがいくつかあるので対応は問題なくできそう

(>>> 追記 2019-11-02)
どうやらちょっとだけ改善されて、300secの5分ではなく、1時間に変更された模様
でも、問題指摘した人は、せめて24時間にしてよって投稿してる

Constantly getting disconnected from websocket after a few minutes · Issue #4
Upon further testing, I can see now the disconnect interval is exactly 3600 sec (1 hour). Can we not extend this to a 24 hour interval? For example, binance forces a server disconnect only once every 24 hours.

そんなアップデートを見つつ、別のissueを見ていたらローソクチャートがリアルタイムAPIから除外されてた... orz それ、私のビット子ボットちゃんとっては大変困るんですが...

Public API でも取得できるんだけど、日付指定でその日のチャート全部だから最新のものだけを参照しないといけないし... 別の改修が必要になるじゃないノォ...
(<<< 追記 2019-11-02)

実装

コード書きながら、どんな感じの実装になるのか具体化するので、その工程を書き残しておく

コードの前にまずはライブラリのインストールで、公式ドキュメントには、インストールするのに、 pip コマンドの例があるけど、asyncio を利用した非同期用のライブラリと、標準的なライブラリでインストールの時の引数の指定が異なるっぽい

非同期処理は、利用しているフレームワーク responder の方でゴニョゴニョするので良いはずだから(フレームワーク側のシンプルな実装で非同期に簡単にできるから)、とりあえず、標準のライブラリを入れれば良さそう

構築した環境は、pipenv管理なので、コマンドは pip ではなく、pipenv

pipenv install python-socketio

公式ドキュメントのコードをベースに、巷の実装例を参考に内容を精査していく

import socketio

sio = socketio.Client(
    reconnection=True, 
    reconnection_attempts=0, 
    reconnection_delay=1,
    reconnection_delay_max=30
)

SocketIOのクライアント・インスタンス作成時のパラメータは以下のような意味
(だと思うw)

  • 再接続を有効にして
  • attempts=0で無限で再接続を試みる
  • 再接続の遅延間隔は1秒(再接続に失敗するごとに、この間隔は倍化)
  • 再接続の遅延間隔の最大値は、30秒

この後の接続やデータ取得、切断の処理あたりについて色々な実装例が見られるので、少々混乱w

公式ドキュメントと、巷のどなたかの実装例を比較してみると、デコレーターで実装している部分と、メソッドで実装している部分があって、どちらのコードでも良さそうだけど、微妙にそれぞれ差異があって... 何が正しいのか、どれが標準的な実装方法なのかわかりづらい

というか、単に、Pythonの実装方法やデコレーターを深く理解していないからという初歩的?構文理解の話のように話でもあるような気はするが...

全体のボットの実装方法が手続き的に書いているので、デコレーターではなくメソッドを利用した実装方法の方が扱いやすい気がする...

で、改めて、以下のサンプルコードを見ると、アァーなるほどという感じ

import socketio
 
 def on_connect():
 	print('connect')
 	sio.on('message',on_data)
 	sio.emit('join-room','ticker_btc_jpy')
 	sio.emit('join-room','transactions_btc_jpy')
 	sio.emit('join-room','depth_diff_btc_jpy')
 	sio.emit('join-room','depth_whole_btc_jpy')
 
 def on_data(data):
 	print(data)
 
 sio = socketio.Client()
 sio.on('connect', on_connect)
 sio.connect('wss://stream.bitbank.cc', transports = ['websocket'])
 sio.wait()

インスタンスを生成 sio = socketio.Client() した後、クラスの on メソッドで接続時のハンドラーメソッドを設定 sio.on('connect', on_connect) してから connect メソッドで接続

on_connect のハンドラーメソッド内で、実際に接続するときに、データ取得(message を受け取る)時のハンドラーメソッドを定義している

on メソッドで disconnect のハンドラーも設定しておくと良さそう

で、connectdisconnect には print による出力ではなく、ロギングの処理を書いておくのも忘れずに

message のハンドラー設定は、on_connect のハンドラーの中じゃなくても良さそうな気がする...

なので、クライアント・インスタンスを作成してからの処理は以下のようにしたらどうだろうか


sio = socketio.Client()
sio.on('connect', on_connect)
sio.on('message', on_message)
sio.on('disconnect', on_disconnect)
sio.connect('wss://stream.bitbank.cc', transports = ['websocket'])
sio.wait()

あとは、ビット子ボットちゃんのファイル構成とかが関係してくるので、机上のコーディングはこの程度にしておき、ここまでで得た情報で実コーディングを進めてみる

追記(顛末)

前述の内容を踏まえて、実際にコーディングを進めてテスト実行をしてみたけれど、動作しない... エラーは出ない... テストコードを実行すると sio.wait() の部分で少し待たされて... 何も標準出力に出てこない

print文だとダサいのと、python-socketio が logger に標準対応しているので logger を仕込んだコードを書いてみたが... ログレベルを DEBUG にしているけど、何も出力されない...

実際のコードが以下のような感じ

import socketio
import json
import logging

logging.basicConfig(
    level=logging.ERROR,
    filename='./try.log',
    format='%(levelname)s : %(asctime)s : %(message)s'
)
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)

sio = socketio.Client(
    reconnection=True, 
    reconnection_attempts=0, 
    reconnection_delay=1, 
    reconnection_delay_max=30, 
    logger=True
)
logger.info('Created socketio client')

@sio.event
def connect():
    logger.info('connected to server')
    sio.emit('join-room','transactions_btc_jpy')

@sio.event
def message(data):
    logger.info('print message')
    print(json.dumps(data, indent=2))

@sio.event
def disconnect():
    logger.info('disconnected from server')

sio.connect('wss://stream.bitbank.cc', transports=['websocket'])
sio.wait()

エラーなり、ログなりが出力されれば、調べることもできるけど、何も出力されないんじゃぁ...

どうにも手立てが思いつかないので人に頼ろうと思い、Discord の Python JP コミュニティがあるので、そこにでも投稿しようと思って、メッセージもしたためて、送信押す直前で、再度メッセージに掲載した上記実行コードを見直していたら...

気づきました

logging.basicConfig(
    level=logging.ERROR,

ココ、読み込んでいるモジュール側のログレベルが ERROR になっているっwwwwww
ということで、この ERROR のところを...

logging.basicConfig(
    level=logging.DEBUG,

DEBUG にしたところ!ログに以下が出力されました

WARNING : 2019-10-30 21:15:18,357 : websocket-client package not installed, only polling transport is available

依存ライブラリがあるんかーい!
まとめてインストールされないんかーい!
ドキュメントに書かんかーい!

pipenv install ライブラリ名 で依存関係のあるライブラリも一緒にインストールされると思うじゃない? Pipfile.lock 見ると関連するライブラリもインストールされているっぽいけど... なぜ?

一番関連ありそうな、最重要そうな websocket-client がインストールされないんだと...

結果

pipenv install websocket-client

したら全て解決!
これでようやくやりたいことができる

22
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?