寫在前面
忙于學(xué)習(xí),我已經(jīng)好久沒有寫過博客了。最近,由于越來越意識(shí)到了理財(cái)?shù)闹匾裕谑俏疫x擇了從最容易入門且風(fēng)險(xiǎn)較低的基金入手,看的同時(shí)也能夠?qū)W習(xí)到一些金融知識(shí)。不過,不買就沒有看的欲望,所以我也還是選擇了幾支基金入手了,當(dāng)然,最終還是以學(xué)習(xí)為主,收益只能作為一個(gè)檢驗(yàn)學(xué)習(xí)效果的手段,而不是我當(dāng)前的目的(是未來的目的hhh)。
要想分析基金,少不了歷年的數(shù)據(jù),為了分析方便,我還是覺得先把所有的數(shù)據(jù)爬下來,然后再做進(jìn)一步處理。
接口分析
爬數(shù)據(jù)需要先思考從哪里爬?經(jīng)過一番搜索和考慮,我發(fā)現(xiàn)天天基金網(wǎng)的數(shù)據(jù)既比較全,又十分容易爬取,所以就從它入手了。
首先,隨便點(diǎn)開一支基金,我們可以看到域名就是該基金的代碼,十分方便,其次下面有生成的凈值圖。

打開chrome的開發(fā)者調(diào)試,選擇Network,然后刷新一下,很快我們就能發(fā)現(xiàn)我們想要的東西了。可以看到,這是基金代碼加當(dāng)前時(shí)間的一個(gè)接口,請(qǐng)求的url是http://fund.eastmoney.com/pingzhongdata/003511.js?v=20190304115823
也就是說我們可以簡(jiǎn)單的通過http://fund.eastmoney.com/pingzhongdata/基金代碼.js?v=當(dāng)前時(shí)間這樣一個(gè)接口就能獲取到相應(yīng)的數(shù)據(jù)了。

現(xiàn)在我們來看看這個(gè)文件的具體內(nèi)容是什么?
顯然,這里面的東西就是我們想要的,Data_netWorthTrend里面的"y"就包含了每一天的凈值

獲取數(shù)據(jù)
現(xiàn)在我們的接口已經(jīng)十分明確了,就是http://fund.eastmoney.com/pingzhongdata/基金代碼.js?v=當(dāng)前時(shí)間
通過基金代碼和當(dāng)前時(shí)間我們就能夠獲取到相應(yīng)的數(shù)據(jù),接下來就是需要將我們想要的數(shù)據(jù)從獲取的文件中提取出來了,也就是我們說的數(shù)據(jù)清洗的過程。
這個(gè)網(wǎng)站提供的數(shù)據(jù)不是常見的json格式,因此提取會(huì)有點(diǎn)麻煩,比如通過字符串查找等,但是由于這個(gè)是js文件,因此,我找到了更合適的方法——利用了PyExecJs模塊就能很方便地編譯解析js代碼啦。
現(xiàn)在直接上代碼。
首先終端里,pip install PyExecJs安裝上該模塊。然后引入這些模塊
import requests
import time
import execjs
接口構(gòu)造
構(gòu)造一個(gè)url
def getUrl(fscode):
head = 'http://fund.eastmoney.com/pingzhongdata/'
tail = '.js?v='+ time.strftime("%Y%m%d%H%M%S",time.localtime())
return head+fscode+tail
獲取凈值
def getWorth(fscode):
#用requests獲取到對(duì)應(yīng)的文件
content = requests.get(getUrl(fscode))
#使用execjs獲取到相應(yīng)的數(shù)據(jù)
jsContent = execjs.compile(content.text)
name = jsContent.eval('fS_name')
code = jsContent.eval('fS_code')
#單位凈值走勢(shì)
netWorthTrend = jsContent.eval('Data_netWorthTrend')
#累計(jì)凈值走勢(shì)
ACWorthTrend = jsContent.eval('Data_ACWorthTrend')
netWorth = []
ACWorth = []
#提取出里面的凈值
for dayWorth in netWorthTrend[::-1]:
netWorth.append(dayWorth['y'])
for dayACWorth in ACWorthTrend[::-1]:
ACWorth.append(dayACWorth[1])
print(name,code)
return netWorth, ACWorth
查看數(shù)據(jù)
這樣我們就可以通過基金代碼來查到對(duì)應(yīng)的數(shù)據(jù)啦
netWorth, ACWorth = getWorth('003511')
print(netWorth)
可以看到,最近一天的凈值是1.0831,從網(wǎng)站上我們也可以驗(yàn)證一下這個(gè)數(shù)據(jù)是否正確


當(dāng)然,我們也可以自己畫一個(gè)走勢(shì)圖來驗(yàn)證一下
import matplotlib.pyplot as plt
plt.figure(figsize=(10,5))
plt.plot(netWorth[:60][::-1])
plt.show()

可以看到,和天天基金網(wǎng)畫的是一樣的。
不過這個(gè)方法獲取的數(shù)據(jù)有個(gè)小問題,就是無法獲得對(duì)應(yīng)的確切日期。我們?nèi)绻治鲎罱鼛讉€(gè)周、幾個(gè)月的數(shù)據(jù),其實(shí)也可以不需要了解具體某一天的數(shù)據(jù),取最近20天、40天等方式即可。當(dāng)然,也可以從當(dāng)天開始逆推回去,給每個(gè)凈值標(biāo)上日期,不過這個(gè)需要忽略節(jié)假日,處理起來比較麻煩且必要性不大,我就沒有做這個(gè)處理。
獲取所有基金數(shù)據(jù)
這里我通過同樣的方式,找到了所有基金列表的接口。
通過'http://fund.eastmoney.com/js/fundcode_search.js'便可以直接獲取到所有的基金代碼,再通過基金代碼可以遍歷爬取所有基金的數(shù)據(jù),具體就不再演示了,下面提供一個(gè)可用的代碼供參考。
我將下載的數(shù)據(jù)存成了csv,方便excel打開或用代碼讀取。當(dāng)然,總共有近8000支基金,爬取需要大量的時(shí)間,因此我將它放在了服務(wù)器后臺(tái)爬取,如果你想提高效率,可以改寫成多進(jìn)程同步爬取,時(shí)間將會(huì)大大縮短。
import requests
import time
import execjs
def getUrl(fscode):
head = 'http://fund.eastmoney.com/pingzhongdata/'
tail = '.js?v='+ time.strftime("%Y%m%d%H%M%S",time.localtime())
return head+fscode+tail
# 根據(jù)基金代碼獲取凈值
def getWorth(fscode):
content = requests.get(getUrl(fscode))
jsContent = execjs.compile(content.text)
name = jsContent.eval('fS_name')
code = jsContent.eval('fS_code')
#單位凈值走勢(shì)
netWorthTrend = jsContent.eval('Data_netWorthTrend')
#累計(jì)凈值走勢(shì)
ACWorthTrend = jsContent.eval('Data_ACWorthTrend')
netWorth = []
ACWorth = []
for dayWorth in netWorthTrend[::-1]:
netWorth.append(dayWorth['y'])
for dayACWorth in ACWorthTrend[::-1]:
ACWorth.append(dayACWorth[1])
print(name,code)
return netWorth, ACWorth
def getAllCode():
url = 'http://fund.eastmoney.com/js/fundcode_search.js'
content = requests.get(url)
jsContent = execjs.compile(content.text)
rawData = jsContent.eval('r')
allCode = []
for code in rawData:
allCode.append(code[0])
return allCode
allCode = getAllCode()
netWorthFile = open('./netWorth.csv','w')
ACWorthFile = open('./ACWorth.csv','w')
for code in allCode:
try:
netWorth, ACWorth = getWorth(code)
except:
continue
if len(netWorth)<=0 or len(ACWorth)<0:
print(code+"'s' data is empty.")
continue
netWorthFile.write("\'"+code+"\',")
netWorthFile.write(",".join(list(map(str, netWorth))))
netWorthFile.write("\n")
ACWorthFile.write("\'"+code+"\',")
ACWorthFile.write(",".join(list(map(str, ACWorth))))
ACWorthFile.write("\n")
print("write "+code+"'s data success.")
netWorthFile.close()
ACWorthFile.close()
這是我用服務(wù)器爬取的數(shù)據(jù),可以看到,總共大概35M+35M。
