股票量化分析入門——抓取數據

目標

我的目標是抓取所有的 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)
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容