PYTHON多線程行情抓取工具實現(xiàn)

思路

借助python當(dāng)中threading模塊與Queue模塊組合可以方便的實現(xiàn)基于生產(chǎn)者-消費者模型的多線程模型。Jimmy大神的tushare一直是廣大python數(shù)據(jù)分析以及業(yè)余量化愛好者喜愛的免費、開源的python財經(jīng)數(shù)據(jù)接口包。

平時一直有在用阿里云服務(wù)器通過tushare的接口自動落地相關(guān)財經(jīng)數(shù)據(jù),但日復(fù)權(quán)行情數(shù)據(jù)以往在串行下載的過程當(dāng)中,速度比較慢,有時遇到網(wǎng)絡(luò)原因還需要重下。每只股票的行情下載過程中都需要完成下載、落地2個步驟,一個可能需要網(wǎng)絡(luò)開銷、一個需要數(shù)據(jù)庫mysql的存取開銷。2者原本就可以獨立并行執(zhí)行,是個典型的“生產(chǎn)者-消費者”模型。

基于queue與threading模塊的線程使用一般采用以下的套路:


producerQueue=Queue()
consumerQueue=Queue()
lock = threading.Lock()
class producerThead(threading.Thread):
    def __init__(self, producerQueue,consumerQueue):
        self.producerQueue=producerQueue
        self.consumerQueue=consumerQueue



    def run(self):
        while not self.thread_stop:
            try:
                #接收任務(wù),如果連續(xù)20秒沒有新的任務(wù),線程退出,否則會一直執(zhí)行
                item=self.producerQueue.get(block=True, timeout=20)
                #阻塞調(diào)用進(jìn)程直到有數(shù)據(jù)可用。如果timeout是個正整數(shù),
                #阻塞調(diào)用進(jìn)程最多timeout秒,
                #如果一直無數(shù)據(jù)可用,拋出Empty異常(帶超時的阻塞調(diào)用)
            except Queue.Empty:
                print("Nothing to do!thread exit!")
                self.thread_stop=True
                break
            #實現(xiàn)生產(chǎn)者邏輯,生成消費者需要處理的內(nèi)容 consumerQueue.put(someItem)
            #還可以邊處理,邊生成新的生產(chǎn)任務(wù)
            doSomethingAboutProducing()
            self.producerQueue.task_done()
    def stop(self):
        self.thread_stop = True

class consumerThead(threading.Thread):
    def __init__(self,lock, consumerQueue):
        self.consumerQueue=consumerQueue
    def run(self):
        while true:
            try:
                #接收任務(wù),如果連續(xù)20秒沒有新的任務(wù),線程退出,否則會一直執(zhí)行
                item=self.consumerQueue.get(block=True, timeout=20)
                #阻塞調(diào)用進(jìn)程直到有數(shù)據(jù)可用。如果timeout是個正整數(shù),
                #阻塞調(diào)用進(jìn)程最多timeout秒,
                #如果一直無數(shù)據(jù)可用,拋出Empty異常(帶超時的阻塞調(diào)用)
            except Queue.Empty:
                print("Nothing to do!thread exit!")
                self.thread_stop=True
                break
            doSomethingAboutConsuming(lock)# 處理消費者邏輯,必要時使用線程鎖 ,如文件操作等
            self.consumerQueue.task_done()
#定義主線程
def main():
    for i in range(n):#定義n個i消費者線程
        t = ThreadRead(producerQueue, consumerQueue)
        t.setDaemon(True)
        t.start()
    producerTasks=[] #定義初始化生產(chǎn)者任務(wù)隊列
    producerQueue.put(producerTasks)
    for i in range(n):#定義n個生產(chǎn)者錢程
        t = ThreadWrite(consumerQueue, lock)
        t.setDaemon(True)
        t.start()    
    stock_queue.join()
    data_queue.join()

相關(guān)接口

1,股票列表信息接口

  • 作用
    獲取滬深上市公司基本情況。屬性包括:
code,代碼
name,名稱
industry,所屬行業(yè)
area,地區(qū)
pe,市盈率
outstanding,流通股本(億)
totals,總股本(億)
totalAssets,總資產(chǎn)(萬)
liquidAssets,流動資產(chǎn)
fixedAssets,固定資產(chǎn)
reserved,公積金
reservedPerShare,每股公積金
esp,每股收益
bvps,每股凈資
pb,市凈率
timeToMarket,上市日期
undp,未分利潤
perundp, 每股未分配
rev,收入同比(%)
profit,利潤同比(%)
gpr,毛利率(%)
npr,凈利潤率(%)
holders,股東人數(shù)
  • 調(diào)用方法
import tushare as ts
ts.get_stock_basics()
  • 返回效果
        name    industry    area       pe   outstanding     totals  totalAssets
code
600606   金豐投資     房產(chǎn)服務(wù)   上海     0.00     51832.01   51832.01    744930.44
002285    世聯(lián)行     房產(chǎn)服務(wù)   深圳    71.04     76352.17   76377.60    411595.28
000861   海印股份     房產(chǎn)服務(wù)   廣東   126.20     83775.50  118413.84    730716.56
000526   銀潤投資     房產(chǎn)服務(wù)   福建  2421.16      9619.50    9619.50     20065.32
000056    深國商     房產(chǎn)服務(wù)   深圳     0.00     14305.55   26508.14    787195.94
600895   張江高科     園區(qū)開發(fā)   上海   171.60    154868.95  154868.95   1771040.38
600736   蘇州高新     園區(qū)開發(fā)   江蘇    48.68    105788.15  105788.15   2125485.75
600663    陸家嘴     園區(qū)開發(fā)   上海    47.63    135808.41  186768.41   4562074.50
600658    電子城     園區(qū)開發(fā)   北京    19.39     58009.73   58009.73    431300.19
600648    外高橋     園區(qū)開發(fā)   上海    65.36     81022.34  113534.90   2508100.75
600639   浦東金橋     園區(qū)開發(fā)   上海    57.28     65664.88   92882.50   1241577.00
600604   市北高新     園區(qū)開發(fā)   上海   692.87     33352.42   56644.92    329289.50

2,日復(fù)權(quán)行情接口

  • 作用
    提供股票上市以來所有歷史數(shù)據(jù),默認(rèn)為前復(fù)權(quán),讀取后存到本地,作為后續(xù)分析的基礎(chǔ)
  • 調(diào)用方法
ts.get_h_data('002337', start='2015-01-01', end='2015-03-16') #兩個日期之間的前復(fù)權(quán)數(shù)據(jù)

parameter:
code:string,股票代碼 e.g. 600848
start:string,開始日期 format:YYYY-MM-DD 為空時取當(dāng)前日期
end:string,結(jié)束日期 format:YYYY-MM-DD 為空時取去年今日
autype:string,復(fù)權(quán)類型,qfq-前復(fù)權(quán) hfq-后復(fù)權(quán) None-不復(fù)權(quán),默認(rèn)為qfq
index:Boolean,是否是大盤指數(shù),默認(rèn)為False
retry_count : int, 默認(rèn)3,如遇網(wǎng)絡(luò)等問題重復(fù)執(zhí)行的次數(shù)
pause : int, 默認(rèn) 0,重復(fù)請求數(shù)據(jù)過程中暫停的秒數(shù),防止請求間隔時間太短出現(xiàn)的問題

return:
date : 交易日期 (index)
open : 開盤價
high : 最高價
close : 收盤價
low : 最低價
volume : 成交量
amount : 成交金額

  • 返回結(jié)果
            open   high  close    low     volume      amount
date
2015-03-16  13.27  13.45  13.39  13.00   81212976  1073862784
2015-03-13  13.04  13.38  13.37  13.00   40548836   532739744
2015-03-12  13.29  13.95  13.28  12.96   71505720   962979904
2015-03-11  13.35  13.48  13.15  13.00   59110248   780300736
2015-03-10  13.16  13.67  13.59  12.72  105753088  1393819776
2015-03-09  13.77  14.73  14.13  13.70  139091552  1994454656
2015-03-06  12.17  13.39  13.39  12.17   89486704  1167752960
2015-03-05  12.79  12.80  12.17  12.08   26040832   966927360
2015-03-04  13.96  13.96  13.30  12.58   26636174  1060270720
2015-03-03  12.17  13.10  13.10  12.05   19290366   733336768

實現(xiàn)

廢話不多說,直接上代碼,

  • 生產(chǎn)者線程,讀取行情
class ThreadRead(threading.Thread):
    def __init__(self, queue, out_queue):
        '''
        用于根據(jù)股票代碼、需要讀取的日期,讀取增量的日行情數(shù)據(jù),
        :param queue:用于保存需要讀取的股票代碼、起始日期的列表
        :param out_queue:用于保存需要寫入到數(shù)據(jù)庫表的結(jié)果集列表
        :return:
        '''
        threading.Thread.__init__(self)
        self.queue = queue
        self.out_queue = out_queue
    def run(self):
        while true:
            item = self.queue.get()
            time.sleep(0.5)
            try:
                df_h_data = ts.get_h_data(item['code'], start=item['startdate'], retry_count=10, pause=0.01)
                if df_h_data is not None and len(df_h_data)>0:
                    df_h_data['secucode'] = item['code']
                    df_h_data.index.name = 'date'
                    print df_h_data.index,item['code'],item['startdate']
                    df_h_data['tradeday'] = df_h_data.index.strftime('%Y-%m-%d')
                    self.out_queue.put(df_h_data)
            except Exception, e:
                print str(e)
                self.queue.put(item) # 將沒有爬取成功的數(shù)據(jù)放回隊列里面去,以便下次重試。
                time.sleep(10)
                continue

            self.queue.task_done()
  • 消費者線程,本地存儲

class ThreadWrite(threading.Thread):
    def __init__(self, queue, lock, db_engine):
        '''
        :param queue: 某種形式的任務(wù)隊列,此處為tushare為每個股票返回的最新日復(fù)權(quán)行情數(shù)據(jù)
        :param lock:  暫時用連接互斥操作,防止mysql高并發(fā),后續(xù)可嘗試去掉
        :param db_engine:  mysql數(shù)據(jù)庫的連接對象
        :return:no
        '''
        threading.Thread.__init__(self)
        self.queue = queue
        self.lock = lock
        self.db_engine = db_engine

    def run(self):
        while True:
            item = self.queue.get()
            self._save_data(item)
            self.queue.task_done()

    def _save_data(self, item):
            with self.lock:
                try:
                    item.to_sql('cron_dailyquote', self.db_engine, if_exists='append', index=False)
                except Exception, e:  # 如果是新股,則有可能df_h_data是空對象,因此需要跳過此類情況不處理
                    print str(e)

  • 定義主線程
from Queue import Queue
stock_queue = Queue()
data_queue = Queue()
lock = threading.Lock()
def main():
    '''
    用于測試多線程讀取數(shù)據(jù)
    :return:
    '''
    #獲取環(huán)境變量,取得相應(yīng)的環(huán)境配置,上線時不需要再變更代碼
    global stock_queue
    global data_queue
    config=os.getenv('FLASK_CONFIG')
    if config == 'default':
        db_url='mysql+pymysql://root:******@localhost:3306/python?charset=utf8mb4'
    else:
        db_url='mysql+pymysql://root:******@localhost:3306/test?charset=utf8mb4'
    db_engine = create_engine(db_url, echo=True)
    conn = db_engine.connect()
    #TODO 增加ts.get_stock_basics()報錯的處理,如果取不到信息則直接用數(shù)據(jù)庫中的股票代碼信息,來獲取增量信息
    #TODO 增加一個標(biāo)志,如果一個股票代碼的最新日期不是最新日期,則需標(biāo)記該代碼不需要重新獲取數(shù)據(jù),即記錄該股票更新日期到了最新工作日,
    df = ts.get_stock_basics()
    df.to_sql('stock_basics',db_engine,if_exists='replace',dtype={'code': CHAR(6)})
    # 計算距離當(dāng)前日期最大的工作日,以便每日定時更新
    today=time.strftime('%Y-%m-%d',time.localtime(time.time()))
    s1=("select max(t.date) from cron_tradeday t where flag=1 and t.date <='"+ today+"'")
    selectsql=text(s1)
    maxTradeay = conn.execute(selectsql).first()
    # 計算每只股票當(dāng)前加載的最大工作日期,支持重跑
    s = ("select secucode,max(t.tradeday) from cron_dailyquote t group by secucode ")
    selectsql = text(s)
    result = conn.execute(selectsql)  # 執(zhí)行查詢語句
    df_result = pd.DataFrame(result.fetchall())
    df_result.columns=['stockcode','max_tradeday']
    df_result.set_index(df_result['stockcode'],inplace=True)
    # 開始?xì)w檔前復(fù)權(quán)歷史行情至數(shù)據(jù)庫當(dāng)中,以便可以方便地計算后續(xù)選股模型

    for i in range(3):#使用3個線程
        t = ThreadRead(stock_queue, data_queue)
        t.setDaemon(True)
        t.start()
    for code in set(list(df.index)):
        try:
            #如果當(dāng)前股票已經(jīng)是最新的行情數(shù)據(jù),則直接跳過,方便重跑。
            #print maxTradeay[0],df_result.loc[code].values[1]
            if df_result.loc[code].values[1] == maxTradeay[0]:
                continue
            startdate=getLastNdate(df_result.loc[code].values[1],1)
        except Exception, e:
            #如果某只股票沒有相關(guān)的行情,則默認(rèn)開始日期為2015年1月1日
            startdate='2015-01-01'
        item={}
        item['code']=code
        item['startdate']=startdate
        stock_queue.put(item) # 生成生產(chǎn)者任務(wù)隊列
    for i in range(3):
        t = ThreadWrite(data_queue, lock, db_engine)
        t.setDaemon(True)
        t.start()
    stock_queue.join()
    data_queue.join()

  • 執(zhí)行效果
    原本需要2,3個小時才能執(zhí)行完成的每日復(fù)權(quán)行情增量落地,有效縮短至了1小時以內(nèi),這里線程數(shù)并不上越多越好,由于復(fù)權(quán)行情讀的是新浪接口,在高并發(fā)情況下會返回HTTP 503服務(wù)器過載的錯誤,另外高并發(fā)下可能需要使用IP代理池,下載的時段也需要嘗試多個時段進(jìn)行。初次嘗試,如果有更好的方法或者哪里有考慮不周的地方歡迎留言建議或者指正。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內(nèi)容

  • # Python 資源大全中文版 我想很多程序員應(yīng)該記得 GitHub 上有一個 Awesome - XXX 系列...
    aimaile閱讀 26,532評論 6 427
  • GitHub 上有一個 Awesome - XXX 系列的資源整理,資源非常豐富,涉及面非常廣。awesome-p...
    若與閱讀 18,692評論 4 418
  • 魏晉玄學(xué)是繼兩漢經(jīng)學(xué)之后的中國古代哲學(xué)思想的一個發(fā)展階段和形態(tài),它提出并討論了有,無,獨化等本原,本體類問題。魏晉...
    張靜年閱讀 743評論 0 6
  • 喜歡你的雙眸 像月光照亮泉水 喜歡你的溫柔 像荷葉間的氤氳 我會叮囑南風(fēng) 希望下一次的吹過 ...
    冷川遠(yuǎn)樹閱讀 207評論 0 0
  • 一般說道演講,大家都會潛意識認(rèn)為,哇塞,好遙遠(yuǎn)的事情,我沒有機會上臺演講。但是卻是在日常生活中,工作上,我們需要和...
    江子牙的魚塘閱讀 188評論 0 0