股票量化分析入门——抓取数据
目标
我的目标是抓取所有的 A 股所有股票按日的历史交易数据。将交易数据存储到本地数据库,一遍后续的数据分析之用。
材料
- Python 用与编写相关的功能
- pip 用于安装所需的包,如:Pandas、lxml、sqlalchemy、Tushare
- Tushare 用于抓取数据
- 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)