目標
我的目標是抓取所有的 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)