python异步加协程获取比特币市场信息
目标
选取几个比特币交易量大的几个交易平台,查看对应的API,获取该市场下货币对的ticker和depth信息。我们从网站上选取4个交易平台:bitfinex、okex、binance、gdax。对应的交易对是BTC/USD,BTC/USDT,BTC/USDT,BTC/USD。
一、ccxt库
开始想着直接请求市场的API,然后再解析获取下来的数据,但到github上发现一个比较好得python库,里面封装好了获取比特币市场的相关函数,这样一来就省掉分析API的时间了。因此我只要传入市场以及对应的货币对,利用库里面的函数 fetch_ticker 和 fetch_order_book 就可以获取到市场的ticker和depth信息(具体的使用方法可以查看ccxt手册)。接下来以市场okex为例,利用ccxt库获取okex的ticker和depth信息。
# 引入库
import ccxt # 实例化市场
exchange = ccxt.okex()
# 交易对
symbol = 'BTC/USDT' # 获取ticker信息
ticker = exchange.fetch_ticker(symbol)
# 获取depth信息
depth = exchange.fetch_order_book(symbol) print('ticker:%s, depth:%s' % (ticker, depth))
运行后会得到结果如下图,从此可以看出已经获取到了ticker和depth信息。
二、获取四个市场的信息(for循环)
接下来我们获取四个市场的信息,深度里面有asks和bids,数据量稍微有点儿多,这里depth信息我只去前面五个,对于ticker我也只提取里面的info信息(具体代表什么含义就要参考一下对应市场的API啦)。将其简单的封装后,最开始我想的是for循环。想到啥就开始吧:
# 引入库
import ccxt
import time now = lambda: time.time()
start = now() def getData(exchange, symbol):
data = {} # 用于存储ticker和depth信息
# 获取ticker信息
tickerInfo = exchange.fetch_ticker(symbol)
# 获取depth信息
depth = {}
# 获取深度信息
exchange_depth = exchange.fetch_order_book(symbol)
# 获取asks,bids 最低5个,最高5个信息
asks = exchange_depth.get('asks')[:5]
bids = exchange_depth.get('bids')[:5]
depth['asks'] = asks
depth['bids'] = bids data['ticker'] = tickerInfo
data['depth'] = depth return data def main():
# 实例化市场
exchanges = [ccxt.binance(), ccxt.bitfinex2(), ccxt.okex(), ccxt.gdax()]
# 交易对
symbols = ['BTC/USDT', 'BTC/USD', 'BTC/USDT', 'BTC/USD'] for i in range(len(exchanges)):
exchange = exchanges[i]
symbol = symbols[i]
data = getData(exchange, symbol)
print('exchange: %s data is %s' % (exchange.id, data)) if __name__ == '__main__':
main()
print('Run Time: %s' % (now() - start))
运行后会发现虽然每个市场的信息都获取到了,执行完差不多花掉5.7秒,因为这是同步的,也就是按顺序执行的,要是要想每隔一定时间同时获取四个市场的信息,很显然这种结果不符合我们的要求。
三、异步加协程(coroutine)
前面讲的循环虽然可以输出结果,但耗时长而且达不到想要的效果,接下来采用异步加协程(参考知乎上的一篇文章),要用到异步首先得引入asyncio库,这个库是3.4以后才有的,它提供了一种机制,使得你可以用协程(coroutines)、IO复用(multiplexing I/O)在单线程环境中编写并发模型。这里python文档有个小例子。
import asyncio async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(1.0)
return x + y async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result)) loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()
当事件循环开始运行时,它会在Task中寻找coroutine来执行调度,因为事件循环注册了print_sum(),因此print_sum()被调用,执行result = await compute(x, y)这条语句(等同于result = yield from compute(x, y)),因为compute()自身就是一个coroutine,因此print_sum()这个协程就会暂时被挂起,compute()被加入到事件循环中,程序流执行compute()中的print语句,打印”Compute %s + %s …”,然后执行了await asyncio.sleep(1.0),因为asyncio.sleep()也是一个coroutine,接着compute()就会被挂起,等待计时器读秒,在这1秒的过程中,事件循环会在队列中查询可以被调度的coroutine,而因为此前print_sum()与compute()都被挂起了,因此事件循环会停下来等待协程的调度,当计时器读秒结束后,程序流便会返回到compute()中执行return语句,结果会返回到print_sum()中的result中,最后打印result,事件队列中没有可以调度的任务了,此时loop.close()把事件队列关闭,程序结束。
接下来我们采用异步和协程(ps:ccxt库也有对应的异步),运行后发现时间只用了1.9秒,比之前快了好多倍。
Run Time: 1.9661316871643066
相关代码:
# 引入库
import ccxt.async as ccxt
import asyncio
import time now = lambda: time.time()
start = now() async def getData(exchange, symbol):
data = {} # 用于存储ticker和depth信息
# 获取ticker信息
tickerInfo = await exchange.fetch_ticker(symbol)
# 获取depth信息
depth = {}
# 获取深度信息
exchange_depth = await exchange.fetch_order_book(symbol)
# 获取asks,bids 最低5个,最高5个信息
asks = exchange_depth.get('asks')[:5]
bids = exchange_depth.get('bids')[:5]
depth['asks'] = asks
depth['bids'] = bids data['ticker'] = tickerInfo
data['depth'] = depth return data def main():
# 实例化市场
exchanges = [ccxt.binance(), ccxt.bitfinex2(), ccxt.okex(), ccxt.gdax()]
# 交易对
symbols = ['BTC/USDT', 'BTC/USD', 'BTC/USDT', 'BTC/USD'] tasks = []
for i in range(len(exchanges)):
task = getData(exchanges[i], symbols[i])
tasks.append(asyncio.ensure_future(task)) loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) if __name__ == '__main__':
main()
print('Run Time: %s' % (now() - start))
三、定时爬取并用mongodb保存数据
在前面的基础上,添加一个定时任务,实现每隔一段时间爬取一次数据,并将数据保存到mongodb数据库。只需再前面的代码上稍微改改就可以啦,代码和运行结果如下:
import asyncio
import ccxt.async as ccxt
import time
import pymongo # 获取ticker和depth信息
async def get_exchange_tickerDepth(exchange, symbol): # 其中exchange为实例化后的市场
# print('start get_ticker')
while True:
print('%s is run %s' % (exchange.id, time.ctime())) # 获取ticher信息
tickerInfo = await exchange.fetch_ticker(symbol)
ticker = tickerInfo.get('info') if type(ticker) == type({}):
ticker['timestamp'] = tickerInfo.get('timestamp')
ticker['high'] = tickerInfo.get('high')
ticker['low'] = tickerInfo.get('low')
ticker['last'] = tickerInfo.get('last')
else:
ticker = tickerInfo
# print(ticker) # 获取深度信息
depth = {}
exchange_depth = await exchange.fetch_order_book(symbol)
# 获取asks,bids 最低5个,最高5个信息
asks = exchange_depth.get('asks')[:5]
bids = exchange_depth.get('bids')[:5]
depth['asks'] = asks
depth['bids'] = bids
# print('depth:{}'.format(depth))
data = {
'exchange': exchange.id,
'countries': exchange.countries,
'symbol': symbol,
'ticker': ticker,
'depth': depth
} # 保存数据
save_exchangeDate(exchange.id, data)
print('********* %s is finished, time %s *********' % (exchange.id, time.ctime())) # 等待时间
await asyncio.sleep(2) # 存库
def save_exchangeDate(exchangeName, data):
# 链接MongoDB
connect = pymongo.MongoClient(host='localhost', port=27017)
# 创建数据库
exchangeData = connect['exchangeDataAsyncio']
# 创建表
exchangeInformation = exchangeData[exchangeName]
# print(table_name)
# 数据去重后保存
count = exchangeInformation.count()
if not count > 0:
exchangeInformation.insert_one(data)
else:
for item in exchangeInformation.find().skip(count - 1):
lastdata = item
if lastdata['ticker']['timestamp'] != data['ticker']['timestamp']:
exchangeInformation.insert_one(data) def main():
exchanges = [ccxt.binance(), ccxt.bitfinex2(), ccxt.okex(),
ccxt.gdax()]
symbols = ['BTC/USDT', 'BTC/USD', 'BTC/USDT', 'BTC/USD']
tasks = []
for i in range(len(exchanges)):
task = get_exchange_tickerDepth(exchanges[i], symbols[i])
tasks.append(asyncio.ensure_future(task)) loop = asyncio.get_event_loop() try:
# print(asyncio.Task.all_tasks(loop))
loop.run_forever() except Exception as e:
print(e)
loop.stop()
loop.run_forever()
finally:
loop.close() if __name__ == '__main__':
main()
五、小结
使用协程可以实现高效的并发任务。Python在3.4中引入了协程的概念,可是这个还是以生成器对象为基础,3.5则确定了协程的语法。这里只简单的使用了asyncio。当然实现协程的不仅仅是asyncio,tornado和gevent都实现了类似的功能。这里我有一个问题,就是运行一段时间后,里面的市场可能有请求超时等情况导致协程停止运行,我要怎样才能获取到错误然后重启对应的协程。如果有大神知道的话请指点指点。
六、参考链接
1. Python黑魔法 --- 异步IO( asyncio) 协程 http://python.jobbole.com/87310/
2. Python并发编程之协程/异步IO https://www.ziwenxie.site/2016/12/19/python-asyncio/
3. 从0到1,Python异步编程的演进之路 https://zhuanlan.zhihu.com/p/25228075
4. Tasks and coroutines https://docs.python.org/3/library/asyncio-task.html
python异步加协程获取比特币市场信息的更多相关文章
- python -- 异步IO 协程
python 3.4 >>> import asyncio >>> from datetime import datetime >>> @asyn ...
- Python实现基于协程的异步爬虫
一.课程介绍 1. 课程来源 本课程核心部分来自<500 lines or less>项目,作者是来自 MongoDB 的工程师 A. Jesse Jiryu Davis 与 Python ...
- 第十一章:Python高级编程-协程和异步IO
第十一章:Python高级编程-协程和异步IO Python3高级核心技术97讲 笔记 目录 第十一章:Python高级编程-协程和异步IO 11.1 并发.并行.同步.异步.阻塞.非阻塞 11.2 ...
- [转载]Python 3.5 协程究竟是个啥
http://blog.rainy.im/2016/03/10/how-the-heck-does-async-await-work-in-python-3-5/ [译] Python 3.5 协程究 ...
- [译] Python 3.5 协程究竟是个啥
转自:http://blog.rainy.im/2016/03/10/how-the-heck-does-async-await-work-in-python-3-5/ [译] Python 3.5 ...
- python线程、协程、I/O多路复用
目录: 并发多线程 协程 I/O多路复用(未完成,待续) 一.并发多线程 1.线程简述: 一条流水线的执行过程是一个线程,一条流水线必须属于一个车间,一个车间的运行过程就是一个进程(一个进程内至少一个 ...
- Python IO 多路复用 \协程
IO 多路复用 作用: 检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据) 即(可读/可写) IO请求时 解决并发 : 单线程 def get_data(key): cl ...
- Python基础之协程
阅读目录 一 引子 二 协程介绍 三 Greenlet模块 四 Gevent模块 引子 之前我们学习了线程.进程的概念,了解了在操作系统中 进程是资源分配的最小单位,线程是CPU调度的最小单位. 按道 ...
- 协程及Python中的协程
1 协程 1.1协程的概念 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程.(其实并没有说明白~) 我觉得单说协程,比较抽象,如果对线程有一定了解 ...
随机推荐
- js实现黑客帝国文字下落效果
突然想到这个效果便想实现以下. 当然免不了要百度一下,于是查找到 http://www.cnblogs.com/myvin/p/4775152.html这篇文章, 效果可以查看博文中的给出的效果图.那 ...
- cloudera-manager所有服务提示时钟偏差问题解决办法
今天新部署Cloudera Manager Hadoop(CDH)集群 发现祖国江山一片红,所有服务都报时钟偏差 1.查看各服务器,时钟是否正确 发现并无问题 2.查看CDH主机配置----主机时钟偏 ...
- HTML元素分类【三种类型】
在CSS中,html中的标签元素大体被分为三种不同的类型: 块状元素.内联元素(又叫行内元素)和内联块状元素. 块状元素:display:block内联元素:display:inline 内联块状 ...
- Maven适配多种运行环境的打包方案
项目从开发到部署会历经多个运行环境,如开发环境.测试环境和生产环境,不同环境中项目的配置文件通常也会不同,典型的如数据库连接配置.我们当然不希望每次部署打包前都去修改配置文件以适配环境,利用Maven ...
- Gym 100952F&&2015 HIAST Collegiate Programming Contest F. Contestants Ranking【BFS+STL乱搞(map+vector)+优先队列】
F. Contestants Ranking time limit per test:1 second memory limit per test:24 megabytes input:standar ...
- ThinkPHP 下载、导入、导出功能的设计与实现
下载: 1.引入命名空间: use Org\Net\Http; 2.在入口文件中设置根目录: //定义根目录的绝对地址 define('ROOT',str_replace("\\" ...
- 阻止安卓实体返回键后退的网页js实现
提供两种解决方法吧,都是网上来的,侵权删,毕竟我等只是搞后端的-- 第一种方法: // 阻止安卓实体键后退 // 页面载入时使用pushState插入一条历史记录 history.pushState( ...
- Oracle:FOR循环语句练习
--打印输出从1到10的正整数DECLARE v_i NUMBER(10) := 0;BEGIN LOOP v_i := v_i + 1; DBMS_OUTPUT.put_line(v_i); EXI ...
- SQL作业及调度创建
转自:http://www.cnblogs.com/accumulater/p/6223909.html --定义创建作业 转自http://hi.baidu.com/procedure/blog/i ...
- Eclipse报错An internal error occurred during: "J2EE Component Mapping Update". java.lang.NullPointerException
Eclipse每次打开.java文件时,报错信息如下: An internal error occurred during: "J2EE Component Mapping Update&q ...