價值十萬的代碼之二---手把手教你獲取數(shù)據(jù)篇

文 |?閑歡
來源:Python 技術(shù)「ID: pythonall」

上篇文章 一份代碼幫我賺了10萬中,小醬承諾如果大家點贊數(shù)超過30個,我會繼續(xù)分享如何利用個股研報數(shù)據(jù)來進行分析。小醬是個信守承諾的小伙子,既然答應了大家,就一定會做到的。
我們要利用個股研報數(shù)據(jù),肯定是會結(jié)合個股行情數(shù)據(jù)的,所以首先要獲取股票數(shù)據(jù),本篇我跟大家分享一下我是如何獲取個股行情數(shù)據(jù)的。
選定目標
現(xiàn)在獲取股票行情數(shù)據(jù)的渠道有好多,比較正規(guī)的途徑就是各種量化平臺的 API 接口,主要分兩類:
有條件免費或者可以在平臺上使用數(shù)據(jù)的量化平臺,例如聚寬(https://www.joinquant.com/)、Tushare平臺(http://tushare.org/)。 大型財經(jīng)平臺的量化平臺,例如同花順的 ?MindGO(http://quant.10jqka.com.cn/platform/html/home.html)、東方財富的 Choice 數(shù)據(jù)量化(http://quantapi.eastmoney.com/)。
第一種渠道,如果你在他們平臺上去用 Python 寫交易策略模型進行回測很方便,平臺上是使用的 Jupyter Notebook 來編輯程序,但是如果想獲取行情數(shù)據(jù)到本地,自己自由支配就需要通過他們提供的 API 接口來獲取數(shù)據(jù),而 API 接口通常對數(shù)據(jù)量或者訪問頻次有限制,導致我們很難隨心所欲地獲取數(shù)據(jù)。
第二種渠道,也可以在平臺上進行回測,想要獲取數(shù)據(jù)到本地基本上是需要交費成為會員才可以。
為了圖免費方便,大多數(shù)人選擇第一種方式,在他們平臺上去寫各種策略模型或者程序?qū)崿F(xiàn)自己的邏輯,對模型進行回測。
對于我個人來說,我選擇了第三種方式,不依靠免費平臺的數(shù)據(jù),也不花錢去購買數(shù)據(jù),而是靠個人能力獲取所有數(shù)據(jù)到本地存儲。因為我不喜歡依賴別人的平臺,萬一哪天突然垮掉了或者收費了呢?在有選擇的前提下,我更不愿意花費巨額資金去購買,雖然研究這個一方面也是為了賺錢,但是能省點是點,不是嗎?
大家應該也猜到了我所謂的“個人能力”是啥,無非就是靠技術(shù)手段去獲取,雖然麻煩點,但是很香啊!
我的主要目標網(wǎng)站就是國內(nèi)比較大的媒體網(wǎng)站的財經(jīng)版塊,有 搜狐財經(jīng)(https://q.stock.sohu.com/)、新浪財經(jīng)(http://vip.stock.finance.sina.com.cn/mkt/)、網(wǎng)易財經(jīng)(http://quotes.money.163.com/stock)和東方財富網(wǎng)。從這些財經(jīng)版塊的頁面去找到個股行情數(shù)據(jù),然后將其爬取到本地。
這里面我自己長期固定的目標是網(wǎng)易財經(jīng),因為到目前為止,獲取數(shù)據(jù)比較穩(wěn)定,并且個股的信息比較豐富。下面我就分享一下我獲取個股每日行情數(shù)據(jù)的方法。
分析目標頁面
我們獲取數(shù)據(jù)的第一步是找到目標頁面,既然是獲取股票數(shù)據(jù),我們肯定是要找到網(wǎng)易財經(jīng)的股票頁面:http://quotes.money.163.com/stock。
然后在這個頁面的左側(cè)導航欄中找到“漲跌排行”欄目,點擊選擇“滬深A股”,如下圖所示:

我們就來到了最新的行情數(shù)據(jù)頁面,網(wǎng)址如下:
http://quotes.money.163.com/old/#query=EQA&DataType=HS_RANK&sort=PERCENT&order=desc&count=24&page= 0
如果當前時間是交易時間,那么這個頁面顯示的是實時行情,如果當前時間是非交易時間,那么這個頁面顯示的是最近交易日的收盤行情。我們來看看這個頁面:

如果單純看這個網(wǎng)頁的網(wǎng)址,或許你會想是不是我替換一下 count 和 page 這兩個參數(shù)就可以獲取數(shù)據(jù)了。但是實際上替換 count 管用,可以改變頁面每頁的記錄數(shù),而 page 參數(shù),無論你填什么值,頁面都不會有變化。所以我們先放棄這個點,去看看頁面的請求,看能不能發(fā)現(xiàn)“天機”。
逐條掃描請求,我們會發(fā)現(xiàn)有一個請求是這樣的:

看起來這個返回結(jié)果是我們所需要的行情數(shù)據(jù)。找到它之后,接下來我們再來看看請求參數(shù):
host:請求域名
page:請求頁碼
query:未知
fields:獲取股票數(shù)據(jù)的列
sort:股票數(shù)據(jù)排序方式
order:排序順序
count:每頁顯示數(shù)目
type:請求類型
對于我們來說,我們只需要關(guān)心 page、fields、count 這幾個參數(shù)就行,其他的就按照頁面的來,每次請求帶上一樣的值就好。而對于 fields 這個參數(shù),我覺得好不容易爬一次數(shù)據(jù),肯定數(shù)據(jù)列越全越好,所以全部都要吧,小孩才做選擇呢!我們點擊頁面下面的翻頁頁碼,可以觀察到 page 參數(shù)是變化的,因此我們可以根據(jù) page 的變化來獲取每一頁的數(shù)據(jù),從而獲取到所有股票行情數(shù)據(jù)。
代碼實現(xiàn)
第一步,肯定是發(fā)送請求,獲取返回數(shù)據(jù):
def?get_data(self,?url):
????????response?=?requests.get(url,?headers=self.ua_header,?verify=False)
????????content?=?response.content.decode('unicode_escape')
????????return?content
????????
接著,我會對請求到的數(shù)據(jù)做一些自定義的特殊處理,因為返回的數(shù)據(jù)信息當中可能會包含有“:”、“""”、“{}”之類的符號,從而影響到后續(xù)的數(shù)據(jù) json 解析,所以我必須想辦法先干掉他們:
def?deal_json_invaild(self,?data):
????????data?=?data.replace("\n",?"\\n").replace("\r",?"\\r").replace("\n\r",?"\\n\\r")?\
????????????.replace("\r\n",?"\\r\\n")?\
????????????.replace("\t",?"\\t")
????????data?=?data.replace('":"',?'&&GSRGSR&&')\
????????????.replace('":',?"%%GSRGSR%%")?\
????????????.replace('","',?"$$GSRGSR$$")\
????????????.replace(',"',?"~~GSRGSR~~")?\
????????????.replace('{"',?"@@GSRGSR@@")?\
????????????.replace('"}',?"**GSRGSR**")
????????#?print(data)
????????data?=?data.replace('"',?r'\"')?\
????????????.replace('&&GSRGSR&&',?'":"')\
????????????.replace('%%GSRGSR%%',?'":')\
????????????.replace('$$GSRGSR$$',?'","')\
????????????.replace("~~GSRGSR~~",?',"')\
????????????.replace('@@GSRGSR@@',?'{"')\
????????????.replace('**GSRGSR**',?'"}')
????????#?print(data)
????????return?data
注意,這里面是我平時跑程序時會經(jīng)常遇到的一些特殊字符的總結(jié)(血淋淋的教訓換來的),你以后可能會遇到其他的特殊字符,往這里面添加規(guī)則就行。
再接下來,我們就要進入解析數(shù)據(jù)的環(huán)節(jié)了,解析比較簡單,直接轉(zhuǎn)換成 json 就行:
def?parse_data(self,?data):
????????result_obj?=?json.loads(data)
????????obj?=?{}
????????obj['pagecount']?=?result_obj['pagecount']
????????obj['time']?=?result_obj['time']
????????obj['total']?=?result_obj['total']
????????list_str?=?result_obj['list']
????????stock_list?=?[]
????????if?list_str:
????????????data_list?=?list(list_str)
????????????for?s?in?data_list:
????????????????#?print(s)
????????????????stock?=?{}
????????????????stock['query_code']?=?s['CODE']
????????????????stock['code']?=?s['SYMBOL']
????????????????stock['name']?=?s['SNAME']
????????????????if?'PRICE'?in?s.keys():
????????????????????stock['close_price']?=?self.trans_float(s['PRICE'])
????????????????else:
????????????????????stock['close_price']?=?0.00
????????????????if?'HIGH'?in?s.keys():
????????????????????stock['top_price']?=?self.trans_float(s['HIGH'])
????????????????else:
????????????????????stock['top_price']?=?0.00
????????????????if?'LOW'?in?s.keys():
????????????????????stock['low_price']?=?self.trans_float(s['LOW'])
????????????????else:
????????????????????stock['low_price']?=?0.00
????????????????if?'OPEN'?in?s.keys():
????????????????????stock['open_price']?=?self.trans_float(s['OPEN'])
????????????????else:
????????????????????stock['open_price']?=?0.00
????????????????if?'YESTCLOSE'?in?s.keys():
????????????????????stock['last_price']?=?self.trans_float(s['YESTCLOSE'])
????????????????else:
????????????????????stock['last_price']?=?0.00
????????????????if?'UPDOWN'?in?s.keys():
????????????????????stock['add_point']?=?self.trans_float(s['UPDOWN'])
????????????????else:
????????????????????stock['add_point']?=?0.00
????????????????if?'PERCENT'?in?s.keys():
????????????????????stock['add_percent']?=?self.trans_float(s['PERCENT'])
????????????????else:
????????????????????stock['add_percent']?=?0.00
????????????????if?'HS'?in?s.keys():
????????????????????stock['exchange_rate']?=?self.trans_float(s['HS'])
????????????????else:
????????????????????stock['exchange_rate']?=?0.00
????????????????if?'VOLUME'?in?s.keys():
????????????????????stock['volumn']?=?self.trans_float(s['VOLUME'])
????????????????else:
????????????????????stock['volumn']?=?0.00
????????????????if?'TURNOVER'?in?s.keys():
????????????????????stock['turnover']?=?self.trans_float(s['TURNOVER'])
????????????????else:
????????????????????stock['turnover']?=?0.00
????????????????if?'TCAP'?in?s.keys():
????????????????????stock['market_value']?=?self.trans_float(s['TCAP'])
????????????????else:
????????????????????stock['market_value']?=?0.00
????????????????if?'MCAP'?in?s.keys():
????????????????????stock['flow_market_value']?=?self.trans_float(s['MCAP'])
????????????????else:
????????????????????stock['flow_market_value']?=?0.00
????????????????stock_list.append(stock)
????????obj['stock']?=?stock_list
????????return?obj
至于這里面每一項的含義,大家可以參照頁面的列去一一對應。
解析完數(shù)據(jù)后,我們就要將數(shù)據(jù)持久化,我這里選擇 mysql 存儲數(shù)據(jù),便于后續(xù)分析使用:
def?insert_db(self,?obj_list,?day):
????????try:
????????????if?len(obj_list):
????????????????insert_attrs?=?['day',?'query_code',?'code',?'name',?'close_price',?'top_price',?'low_price',?'open_price',?'last_price',?'add_point',?'add_percent',?'exchange_rate',?'volumn',?'turnover',?'market_value',?'flow_market_value']
????????????????insert_tuple?=?[]
????????????????for?obj?in?obj_list:
????????????????????insert_tuple.append((day,
?????????????????????????????????????????obj['query_code'],
?????????????????????????????????????????obj['code'],
?????????????????????????????????????????obj['name'],
?????????????????????????????????????????obj['close_price'],
?????????????????????????????????????????obj['top_price'],
?????????????????????????????????????????obj['low_price'],
?????????????????????????????????????????obj['open_price'],
?????????????????????????????????????????obj['last_price'],
?????????????????????????????????????????obj['add_point'],
?????????????????????????????????????????obj['add_percent'],
?????????????????????????????????????????obj['exchange_rate'],
?????????????????????????????????????????obj['volumn'],
?????????????????????????????????????????obj['turnover'],
?????????????????????????????????????????obj['market_value'],
?????????????????????????????????????????obj['flow_market_value']))
????????????????values_sql?=?['%s'?for?v?in?insert_attrs]
????????????????attrs_sql?=?'('+','.join(insert_attrs)+')'
????????????????values_sql?=?'?values('+','.join(values_sql)+')'
????????????????sql?=?'insert?into?%s'?%?'stock_info'
????????????????sql?=?sql?+?attrs_sql?+?values_sql
????????????????try:
????????????????????print(sql)
????????????????????for?i?in?range(0,?len(insert_tuple),?20000):
????????????????????????self.cur.executemany(sql,?tuple(insert_tuple[i:i+20000]))
????????????????????????self.conn.commit()
????????????????except?pymysql.Error?as?e:
????????????????????self.conn.rollback()
????????????????????error?=?'insertMany?executemany?failed!?ERROR?(%s):?%s'?%?(e.args[0],?e.args[1])
????????????????????print(error)
????????except?Exception:
????????????#輸出異常信息
????????????traceback.print_exc()
送佛送到西,順便附上建表語句吧:
CREATE?TABLE?`stock_info`?(
??`id`?int(11)?unsigned?NOT?NULL?AUTO_INCREMENT,
??`day`?varchar(64)?NOT?NULL?DEFAULT?''?COMMENT?'日期',
??`query_code`?varchar(20)?DEFAULT?'',
??`code`?varchar(10)?DEFAULT?NULL?COMMENT?'股票代碼',
??`name`?varchar(64)?DEFAULT?NULL?COMMENT?'名稱',
??`close_price`?double?DEFAULT?NULL?COMMENT?'收盤價',
??`top_price`?double?DEFAULT?NULL?COMMENT?'最高價',
??`low_price`?double?DEFAULT?NULL?COMMENT?'最低價',
??`open_price`?double?DEFAULT?NULL?COMMENT?'開盤價',
??`last_price`?double?DEFAULT?NULL?COMMENT?'前收盤價',
??`add_point`?double?DEFAULT?NULL?COMMENT?'漲跌額',
??`add_percent`?double?DEFAULT?NULL?COMMENT?'漲跌幅',
??`exchange_rate`?double?DEFAULT?NULL?COMMENT?'換手率',
??`volumn`?double?DEFAULT?NULL?COMMENT?'成交量',
??`turnover`?double?DEFAULT?NULL?COMMENT?'成交金額',
??`amplitude`?double?DEFAULT?NULL?COMMENT?'振幅',
??`market_value`?double?DEFAULT?NULL?COMMENT?'總市值',
??`flow_market_value`?double?DEFAULT?NULL?COMMENT?'流通市值',
??`flag`?int(11)?NOT?NULL?DEFAULT?'0',
??PRIMARY?KEY?(`id`),
??KEY?`day`?(`day`,`query_code`),
??KEY?`code_name`?(`code`,`name`)
)?ENGINE=InnoDB?AUTO_INCREMENT=3953?DEFAULT?CHARSET=utf8;
運行程序,你就能在數(shù)據(jù)庫中看到行情數(shù)據(jù)了。如果不想每天手動運行的話,可以寫個定時,每天在收盤后自動運行,當然丟服務(wù)器上更好了。這樣每天獲取當天最新的行情數(shù)據(jù),日積月累,你就可以獲取到從今以后的股票行情數(shù)據(jù)了。
總結(jié)
本文以網(wǎng)易財經(jīng)為例,手把手分享怎樣獲取股票行情數(shù)據(jù),希望對大家有幫助。但是大家記住一點,獲取數(shù)據(jù)只是自己分析研究使用,千萬不要違反我們的職業(yè)道德哦。
看到這里,大家可能會想:這只是獲取一天的行情數(shù)據(jù),并沒有歷史數(shù)據(jù),如果我今天要使用歷史數(shù)據(jù)分析,那不是撲街啦?
這個想法是對的,大家不要著急,本文的數(shù)據(jù)只是后續(xù)步驟的前提,先給我點個在看,我會繼續(xù)分享如何獲取所有股票的歷史行情數(shù)據(jù)。
PS:公號內(nèi)回復「Python」即可進入Python 新手學習交流群,一起 100 天計劃!
老規(guī)矩,兄弟們還記得么,右下角的 “在看” 點一下,如果感覺文章內(nèi)容不錯的話,記得分享朋友圈讓更多的人知道!


【代碼獲取方式】
