股票量化分析入门——抓取数据

目标

我的目标是抓取所有的 A 股所有股票按日的历史交易数据。将交易数据存储到本地数据库,一遍后续的数据分析之用。

材料

  1. Python 用与编写相关的功能
  2. pip 用于安装所需的包,如:Pandas、lxml、sqlalchemy、Tushare
  3. Tushare 用于抓取数据
  4. Mysql 用户存储数据

尝试

通过对 Tushare 的试用,掌握的 Tushare 抓取数据的要点。

时间需要分割

get_h_data函数默认抓取最近三年的数据,如果要抓取更早之前的数据,可以指定相应的日期。但是如果时间指定的太长,则容易产生超时失败。所以如果要抓取一只股票从1992年到现在的所有数据,在需要逐年获取数据,并追加到同一张表中。使用如下的代码就能轻松完成年份分割的功能。

def getInteralArray(start,end):
     startDate = time.strptime(start,'%Y-%m-%d')
     startDate_ = datetime.datetime(*startDate[:3])
     endDate = time.strptime(end,'%Y-%m-%d')
     endDate_ = datetime.datetime(*endDate[:3])
     datelYear = endDate_.year - startDate_.year
     internalArray = [ startDate_.year + x for x in range(0,datelYear+1)]
     internalArray_ = []
     for i in range(0,len(internalArray)):
         if i == 0:
             internalArray_.append(start)
         else:
             internalArray_.append("%s-%s-%s"%(internalArray[i],'01','01'))
         if i == len(internalArray)-1:
             internalArray_.append(end)
         else:
             internalArray_.append("%s-%s-%s"%(internalArray[i],12,31))

     return internalArray_

以上代码,如果调用参数为‘2015-05-12’,‘2017-12-23’。则返回值为[‘2015-05-12′,2015-12-31′,’2016-01-01′,’2016-12-31′,’2017-01-01′,’2017-12-23’]。其中,第一第二个时间组成一个分割对,如此类推,三和四 ,五和六个组成一个。可以直接用于get_h_data的调用。

数据存储

Tushare 抓取数据之后,返回一个dataframe对象。利用对象的to_sql方法,即可将数据存入数据库。to_sql方法,接受的参数有:数据库链接、表名、写入方式(如果表已经存在,是直接失败、覆盖 或 追加)、索引名称。

思路

基于以上的尝试,得到了以下的一个思路。

1.获取所有的股票代码及对应的上市时期列表
2.循环获取每支股票的历史数据
3.获取单只股票的历史数据时,从上市日期到现在,把时间按年划分。按年获取股票历史数据。
4.获取的数据,直接存入数据库中。

上述的思路中,股票的代码以及对应的上市日期的列表可以借由get_stock_basics()方法获取。

在返回值中 timeToMarket 字段是代表对应股票的上市时间。发现10多只股票的上市时间是0。这个问题后续在做补充,这部分数据在获取数据是先排除。

绕坑方法

建表错误

在使用to_sql方法存储数据时,偶尔发生使用了索引的名称当做字段名称来创建数据表。这样就导致后续的股票数据无法存储的情况。为此,我是提前把所有的表结构全部传建好,这样就不会发生这个异常了。

请求失败

在使用get_h_data方法获取数据时,经常会发生网络的异常,有:服务不可用、超市、服务找不到等。原因应该是访问频繁被服务端暂时封掉了。根据几次尝试。发生类似状况的时候,等待10分钟,然后再尝试,基本就能解决了。在获取数据的时候应该要写一个重试的逻辑。我写的逻辑是发生异常就等待10分钟再尝试。如果继续异常,则增加5分钟的等待时间,一共重试三次。目前根据日志,还没有等待10分钟之后重试,继续失败的情况。

频繁发生请求失败

每次请求最好间隔6秒钟。如果没有这个间隔,请求失败的概率会大大的上升。经过尝试每次请求间隔6秒钟是比较OK的。

日志

获取的过程一定要记录完整的日志,这样方便在发生异常时,对个别年份的数据进行重新获取。

主要代码

def initData():
    #前12个股票的上市时间是“0”所以从第13个开始获取
    start = 13
    count = 4000
    logger.info("Begin fetch init data stock start from %s fetch %s items"%(start,count))
    sql = "select * from stockBasics order by timeToMarket limit %s,%s"%(start,count)
    logger.info("The SQL get Stocks info is:"+sql)
    #从数据库中选取所有上市的股票信息
    result = engine.execute(sql)
    count = 0
    for row in result:
        timeStr = str(row['timeToMarket'])
        startDateStr =("%s-%s-%s"%(timeStr[0:4],timeStr[4:6],timeStr[6:8]))
        logger.info("fetch the[[[%s]]] stock:%s from %s To %s"%(start+count,row['code'],startDateStr,'2017-12-12'))
        fetchStockData(row['code'],'s'+row['code'],startDateStr,'2017-12-12')
        count += 1
    loger.info(">>>>>>>>>>>>get stock data SUCCESS<<<<<<<<<<<<<<<")

def fetchStockData(code,tableName,startDateStr,endDateStr):
    global _callCount
    internalArray_ = getInteralArray(startDateStr,endDateStr)
    for i in range(0,len(internalArray_),2):
        _callCount = 0
        doFetchStockData(code,tableName,internalArray_[i],internalArray_[i+1],engine)

def doFetchStockData(code,tableName,startDateStr,endDateStr,engine):
    global _callCount
    flag = False
    while _callCount < 4 and flag == False:
        _callCount = _callCount + 1
        #_callCount大于一,说明上次请求失败了,所以本次请求必须等待一定的时长
        if _callCount > 1 :
            sleepScd = 300 + 300 * (_callCount-1)
            logger.info("RETRY AND SLEEP %s seconds"%(str(sleepScd)))
            time.sleep(sleepScd)
        logger.info("Call %s times"%(_callCount))
        try:
            now = datetime.datetime.now()
            logger.info("Fetch Stock data start: %s end: %s"%(startDateStr,endDateStr))
            #每次请求等待6秒
            time.sleep(6)
            df=ts.get_h_data(code,start=startDateStr,end=endDateStr,index=False,pause=3)
            logger.info("SUCCESS fetch Stock data start: %s end: %s"%(startDateStr,endDateStr))
            df.to_sql(tableName,engine,if_exists='append')
            flag = True
            logger.info("SECCESS save data to database!!")
        except Exception as err:
            logger.warning("EXCEPTION happen err ",exc_info=True)
            if _callCount == 4:
                sys.exit(0)
    原文作者:coolwind
    原文地址: https://www.jianshu.com/p/0bcf47ce1239
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞