用Python搞了個(gè)基金查詢機(jī)器人,還可以拓展!
??????關(guān)注我,和老表一起學(xué)Python、云服務(wù)器
跟老表一起學(xué)云服務(wù)器開發(fā)相關(guān)文章(如果是第一次閱讀該系列文章,強(qiáng)烈建議先學(xué)習(xí)下面文章):
先導(dǎo)篇:擁有有一臺(tái)服務(wù)器后,我竟然這么酷?
手把手教大家如何給域名申請(qǐng)免費(fèi)SSL證書
一、說點(diǎn)東西
老早就想搞個(gè)基金監(jiān)控機(jī)器人了,方便自己查看自己關(guān)注基金的各種指數(shù)漲跌情況,及時(shí)進(jìn)行止損或者止盈,從今天開始,我們先建樓基,手把手帶大家實(shí)現(xiàn)一個(gè)基金查詢機(jī)器人,目前主要可以查詢基金指定日期段數(shù)據(jù)和查看基金凈值走勢圖,后面慢慢新增功能。

二、開始動(dòng)手動(dòng)腦
2.1 環(huán)境準(zhǔn)備
Linux、Mac、Windows 都可以 python 3.7及以上 相關(guān)第三方包:pandas(數(shù)據(jù)處理)、requests(爬取數(shù)據(jù))、re(文本內(nèi)容解析)、akshare(獲取基金股票數(shù)據(jù))、matplotlib(數(shù)據(jù)可視化)、dataframe-image(dataframe表格轉(zhuǎn)成圖片)
2.2 獲取指定日期段基金數(shù)據(jù)
基金數(shù)據(jù)可以從一些金融相關(guān)的網(wǎng)站獲取到,比如天天基金網(wǎng)、新浪基金網(wǎng)等,可以自己寫爬蟲程序獲取網(wǎng)站數(shù)據(jù),也可以使用現(xiàn)成的工具包獲取數(shù)據(jù),比如:一行代碼獲取股票、基金數(shù)據(jù),并繪制K線圖里用到的akshare。
這里我們同時(shí)介紹下兩種方法:
2.2.1 回顧下akshare獲取基金數(shù)據(jù)
目前akshare不支持獲取指定日期范圍內(nèi)的基金凈值數(shù)據(jù),但是可以一次獲取到基金歷史凈值數(shù)據(jù),調(diào)用函數(shù)fund_em_open_fund_info獲取基金歷史數(shù)據(jù),然后自己根據(jù)日期選取時(shí)間斷進(jìn)行分析。

import akshare as ak
fund_data = ak.fund_em_open_fund_info(fund='005827', indicator='單位凈值走勢')
print(fund_data)

自己調(diào)用現(xiàn)成數(shù)據(jù)接口
本質(zhì)上akshare也是從一些金融相關(guān)的網(wǎng)站獲取到數(shù)據(jù),我們也可以自己寫代碼進(jìn)行獲取,通過瀏覽器我們很快能搜索到基金數(shù)據(jù)接口,來自東方財(cái)富的天天基金網(wǎng)。
f'http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code={code}&page={page}&sdate={start_date}&edate={end_date}&per={per}'
code - 基金代碼
page - 基金數(shù)據(jù)頁碼
start_date - 數(shù)據(jù)開始日期
end_date - 數(shù)據(jù)結(jié)束日期
per - 每頁展現(xiàn)數(shù)據(jù)量,最多40

根據(jù)指定參數(shù),瀏覽器會(huì)返回指定參數(shù),一段js賦值代碼,包括了 基金數(shù)據(jù)(content)、總記錄條數(shù)(records)、總頁數(shù)(pages)、當(dāng)前頁數(shù)(curpage)。
格式非常規(guī)整,我們可以直接通過正則提取數(shù)據(jù),
'''
獲取單頁面 基金數(shù)據(jù)
'''
def get_html(code, start_date, end_date, page=1, per=40):
url = f'http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code={code}&page={page}&sdate={start_date}&edate={end_date}&per={per}'
# print(url)
rsp = requests.get(url)
html = rsp.text
return html

通過返回的數(shù)據(jù)可以發(fā)現(xiàn),基金數(shù)據(jù)部分是一個(gè)由table標(biāo)簽包裹的html代碼,那我們可以直接使用pandas的read_html來解析數(shù)據(jù)。
# 從html中解析出數(shù)據(jù)表部分 并解析成df
def parses_table(html):
# 獲取基金數(shù)據(jù)表
pattern = 'content:"<table(.*)</table>",'
table = re.search(pattern, html).group(1)
table = '<table' + table + '</table>'
fund_data = pd.read_html(table)[0]
return fund_data
前面有提到,基金數(shù)據(jù)接口返回?cái)?shù)據(jù)每頁最多展示40條,所以要想獲取所有數(shù)據(jù),我們可能需要遍歷每一頁,那么我們還需要通過正則將總頁數(shù)pages獲取到,然后遍歷調(diào)用get_html和parses_table函數(shù)解析出所有數(shù)據(jù)。
# 獲取指定日期內(nèi) 累計(jì)凈值 等數(shù)據(jù)
def get_fund_data(code, start_date, end_date):
first_page = get_html(code, start_date, end_date)
# 獲取總頁數(shù)
pattern = 'pages:(.*),'
pages = re.search(pattern, first_page).group(1)
# 轉(zhuǎn)成int數(shù)據(jù)
try:
pages = int(pages)
except Exception as e:
r = f'【錯(cuò)誤信息】{e}'
# print(r)
return r
# 存放每頁獲取到的基金數(shù)據(jù) dataframe格式 便于后面合并
fund_df_list = []
# 循環(huán)便利所有頁面
for i in range(pages):
if i == 0:
fund_data = parses_table(first_page)
else:
page_html = get_html(code, start_date, end_date, page=i+1)
fund_data = parses_table(page_html)
fund_df_list.append(fund_data)
# 將每頁的數(shù)據(jù)合并到一起
fund_df = pd.concat(fund_df_list)
return fund_df

上面兩種方法都可以獲取到基金凈值數(shù)據(jù),最后我選擇了akshare方式獲取,設(shè)置一個(gè)定時(shí)任務(wù),每天三點(diǎn)更新自己關(guān)注的基金所有數(shù)據(jù),存儲(chǔ)到本地,后面要查詢的時(shí)候直接讀取本地文件查詢即可。
定時(shí)任務(wù):每天早上3點(diǎn)獲取所有關(guān)注的基金歷史數(shù)據(jù),存儲(chǔ)到本地
# 定時(shí)任務(wù):每天早上3點(diǎn)獲取所有關(guān)注的基金歷史數(shù)據(jù),存儲(chǔ)到本地
def get_all():
try:
# 從文件讀取 自己關(guān)注的基金代碼列表
with open('./FD/funds.txt') as f:
funds = [i.strip() for i in f.readlines()]
# 遍歷 一個(gè)個(gè)更新數(shù)據(jù)
for fund in funds:
fund_df = ak.fund_em_open_fund_info(fund, indicator='單位凈值走勢')
fund_df = fund_df.sort_values(by=['凈值日期'], ascending=False)
fund_df.to_csv(f"./FD/DATA/F{fund}_data.csv", index=False)
# print(f"./FD/DATA/F{fund}_data.csv")
time.sleep(random.randint(1,5))
return '基金數(shù)據(jù)更新完成'
except Exception as e:
r = f"【錯(cuò)誤信息】{e}"
return r
獲取指定基金 指定日期段 凈值數(shù)據(jù)
# 獲取指定基金 指定日期段 凈值數(shù)據(jù)
def get_fund_data(fund, start_d, end_d):
fund_df = pd.read_csv(f'./FD/DATA/{fund}_data.csv')
result_df = fund_df.query(f"'{start_d}'<=凈值日期<='{end_d}'")
return result_df
2.3 返回?cái)?shù)據(jù)呈現(xiàn)方式
目前先簡單點(diǎn),設(shè)置規(guī)則如下:
1)如果數(shù)據(jù)量小于等于30條,就返回原始數(shù)據(jù)圖
原始數(shù)據(jù)圖就是直接將獲取到的數(shù)據(jù)轉(zhuǎn)成圖片的方式發(fā)送給用戶,這里我們使用dataframe-image這個(gè)第三方包,使用非常簡單,pip安裝后,直接調(diào)用export函數(shù)即可快速將datafrmae數(shù)據(jù)轉(zhuǎn)成圖片。
# 將dtaframe表格轉(zhuǎn)變成圖片
def df_to_img(fund_df, fund, start_d, end_d):
if fund_df.shape[0] <=1:
dfi.export(fund_df, f'./FD/IMG/{fund}_{start_d}_{end_d}_data.png')
return
# 格式化表格 凸顯最大最小值
fund_df = fund_df.style.highlight_max(subset=['單位凈值'], color='red')\
.highlight_min(subset=['單位凈值'], color='green')\
.format({'日增長率': '{:}%'})
dfi.export(fund_df, f'./FD/IMG/{fund}_{start_d}_{end_d}_data.png')
為了圖片數(shù)據(jù)更好看,我們還使用了df.style設(shè)置數(shù)據(jù)表格樣式(單位凈值最大值、最小值高亮和日增長率添加百分號(hào))。

2)如果數(shù)據(jù)量大于30條,就返回原始數(shù)據(jù)趨勢圖
原始數(shù)據(jù)趨勢圖就是將數(shù)據(jù)可視化下,然后返回給用戶,這里我們選擇繪制數(shù)據(jù)的走(趨)勢圖,使用matplotlib進(jìn)行繪制。
# 繪制基金單位凈值走勢圖
def draw_fund_line(fund_df, fund, start_d, end_d):
plt.rcParams['figure.figsize'] = (8.0, 4.0) # 設(shè)置figure_size尺寸
plt.rcParams['savefig.dpi'] = 300 #保存圖片分辨率
# 不顯示右、上邊框
ax=plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# 設(shè)置坐標(biāo)網(wǎng)格
plt.grid(axis="y", color='gray')
# 計(jì)算最大值 最小值坐標(biāo) 并標(biāo)注到圖中
fund_max = fund_df.loc[fund_df['單位凈值'].idxmax()]
fund_min = fund_df.loc[fund_df['單位凈值'].idxmin()]
ax.annotate(f'({fund_max[0]},{fund_max[1]})', xy=(fund_max[0], fund_max[1]), color='red')
ax.annotate(f'({fund_min[0]},{fund_min[1]})', xy=(fund_min[0], fund_min[1]), color='green')
# 畫圖
plt.plot(fund_df['凈值日期'],fund_df['單位凈值'], color="c")
plt.title('基金單位凈值走勢圖')
plt.xticks(rotation=30)
plt.xlabel('凈值日期')
plt.ylabel('單位凈值')
plt.savefig(f'./FD/IMG/{fund}_{start_d}_{end_d}_data.png')
這里使用的是折線圖,有一些對(duì)圖片樣式的設(shè)置,比如:大小、邊框、最大/小值標(biāo)注,但依然不是很美觀,后面繼續(xù)優(yōu)化。

完整調(diào)用
# 返回?cái)?shù)據(jù)
def response_data(fund, start_d, end_d):
# 本地查看 查詢結(jié)果是否已存在
imgs = os.listdir('./FD/IMG/')
if f'{fund}_{start_d}_{end_d}_data.png' in imgs:
return f'./FD/IMG/{fund}_{start_d}_{end_d}_data.png'
# 獲取數(shù)據(jù)
fund_df = get_fund_data(fund, start_d, end_d)
# 如果數(shù)據(jù)量小于等于30條,就返回原始數(shù)據(jù)圖
if fund_df.shape[0]<= 30:
df_to_img(fund_df, fund, start_d, end_d)
else:
# 否則返回?cái)?shù)據(jù)趨勢圖
fund_df = fund_df.sort_values(by=['凈值日期'])
draw_fund_line(fund_df, fund, start_d, end_d)
return f'./FD/IMG/{fund}_{start_d}_{end_d}_data.png'
2.4 對(duì)接釘釘機(jī)器人設(shè)置守護(hù)程序
目前項(xiàng)目中使用到了之前介紹過的兩種機(jī)器人:釘釘群機(jī)器人、企業(yè)機(jī)器人,相關(guān)配置方法和代碼可以查看之前的文章:如何用Python發(fā)送告警通知到釘釘? 和 如何打造一個(gè)能自動(dòng)回復(fù)的釘釘機(jī)器人,非常詳細(xì)。
釘釘群機(jī)器人主要用來匯報(bào)每天自動(dòng)匯報(bào)基金數(shù)據(jù)更新情況,后面還可以加基金漲跌檢測情況等。
企業(yè)機(jī)器人主要用來做基金數(shù)據(jù)查詢自動(dòng)回復(fù)功能,也可以拓展主動(dòng)發(fā)消息給用戶,后面研究研究。
2.5 遇到問題及解決方法
2.5.1 Linux上datafrmae-image轉(zhuǎn)圖片出錯(cuò)
最開始是提示沒有chrom,然后按網(wǎng)上教程安裝了google chrom。
參考:https://segmentfault.com/a/1190000022425145

安裝后,運(yùn)行代碼提示SyntaxError: not a PNG file。

看錯(cuò)誤提示以為是Pillow和matplotlib的問題,修改到了和本地一樣的版本也不行。
最后看了源碼,發(fā)現(xiàn)可以轉(zhuǎn)換方法除了使用chrom,還可以用matplotlib,修改后,確實(shí)可以正常生成圖片了,但是沒有格式?。?!

最后改回默認(rèn)table_conversion,仔細(xì)看,發(fā)現(xiàn)有提示以下內(nèi)容,大概意思linux下不能直接使用root用戶權(quán)限允許谷歌chrome,最簡單的方法就是創(chuàng)建一個(gè)普通用戶。
[0209/162730.350381:ERROR:zygote_host_impl_linux.cc(90)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180
在root權(quán)限下,新建一個(gè)用戶od,并將/root目錄權(quán)限授予給它,然后輸入su指令切換到新用戶下。
useradd od
chown -R od /root
su od
再次運(yùn)行確實(shí)能解決圖片生成和數(shù)據(jù)格式問題,但是有新問題:表頭中文無法顯示。。。

百般搜索,看源碼調(diào)試、看項(xiàng)目倉庫問題都沒解決,最后,最后我突然想到,我本地可以,兩個(gè)包的版本又是一樣,應(yīng)該不是代碼問題,會(huì)不會(huì)是因?yàn)閘inux里沒有安裝中文字體,所以無法顯示中文?
root用戶權(quán)限下,先創(chuàng)建一個(gè)目錄,存放中文字體,創(chuàng)建好后,可以直接利用寶塔將本地的SimHei字體上傳到對(duì)應(yīng)目錄即可。
mkdir -p /usr/share/fonts/my_fonts
可以通過下面指令查看中文字體是否安裝成功,
fc-list :lang=zh

再次運(yùn)行代碼,生成的圖片就正常啦~開心!

2.5.2 matplotlib圖片中文顯示問題

之前有寫過詳細(xì)解決方法,可以直接查看之前文章:永久解決matplotlib中文亂碼
2.5.3 釘釘機(jī)器人無法直接傳輸圖片
釘釘機(jī)器人目前只支持傳輸:普通文本、markdown文本、連接、actionCard消息和feedCard消息類型。
如果我想要將生成的基金數(shù)據(jù)圖發(fā)送給用戶,最好的方法是和之前一樣,先將圖片轉(zhuǎn)成鏈接,然后通過markdown形式傳輸。
如果系統(tǒng)僅個(gè)人使用,數(shù)據(jù)量不大,我們不必選擇網(wǎng)絡(luò)上現(xiàn)有的圖床工具(這樣我們還要寫接口對(duì)接代碼),可以直接開放個(gè)http端口去共享我們的圖片,本身企業(yè)機(jī)器人就使用到了flask,所以我們可以更簡單的實(shí)現(xiàn)這個(gè)功能。
app = Flask(__name__, static_folder='xxx/FD/IMG', static_url_path='/static')
在初始化flask app時(shí),指定靜態(tài)文件所在目錄和靜態(tài)文件路由后綴即可,這樣,我們就可以通過:http://服務(wù)器IP地址:端口號(hào)/static/圖片文件名,訪問到對(duì)應(yīng)圖片了。

然后將圖片鏈接嵌入到markdown中,即可正常返回給用戶了。

2.6 最終效果圖
指定查詢
查看某基金某個(gè)時(shí)間段內(nèi)的基金凈值數(shù)據(jù)。(30條以內(nèi)數(shù)據(jù),表格展示;大于30條,趨勢圖展示)
查詢格式: F基金代碼 起始日期 結(jié)束日期,如:F005827 2021-12-03 2022-02-10

普通查詢
查看某基金近10天內(nèi)凈值和日增長率數(shù)據(jù)+趨勢圖
查詢格式: F基金代碼,如:F005827

三、后言后語
這項(xiàng)目說大不大,說小也不小,百行代碼,本機(jī)測試還是很順暢的,主要是遷移到Linux上后出現(xiàn)一些問題,從最開始的python版本問題(安裝了一個(gè)3.7.9),到datafrmae-image問題,延展出來的Linux安裝谷歌、設(shè)置新用戶、分配權(quán)限,以及源碼測試學(xué)習(xí)。
遇到問題、解決問題的過程確實(shí)花費(fèi)了我很長時(shí)間,一度還讓我很苦惱,但是,這個(gè)過程也讓我覺得很有益,是一個(gè)不斷積累、不斷練習(xí)、不斷鞏固的過程,解決問題后更會(huì)為自己歡呼。
目前基金監(jiān)測機(jī)器人還比較簡陋,甚至都沒有監(jiān)測功能(目前只支持?jǐn)?shù)據(jù)查詢和更新),但是這個(gè)樓基很穩(wěn)、很深,后面添加其他功能會(huì)簡單、便捷許多,歡迎大家評(píng)論區(qū)留言,說說你想為這個(gè)機(jī)器人添加的功能。
代碼在下個(gè)版本文章發(fā)布時(shí)一起分享出來,這里招聘5個(gè)‘產(chǎn)品經(jīng)理’,一起來體驗(yàn)這個(gè)基金查詢機(jī)器人,給反饋、提需求,有興趣的可以微信私聊我(需要有2年以上的基金或者股票投資經(jīng)驗(yàn)或者理財(cái)資產(chǎn)超過10w)。
堅(jiān)持 and 努力 :終有所獲。
點(diǎn)贊 在看 留言 轉(zhuǎn)發(fā) ,四連支持,原創(chuàng)不易。好的,那么下期見,我是愛貓愛技術(shù),更愛思思的老表???( ˙?˙ )???
如何找到我:
