程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

用Python搞了個基金查詢機器人,再也不用時刻盯著自己的基金了。

編輯:Python

一、說點東西

老早就想搞個基金監控機器人了,方便自己查看自己關注基金的各種指數漲跌情況,及時進行止損或者止盈,從今天開始,我們先建樓基,手把手帶大家實現一個基金查詢機器人,目前主要可以查詢基金指定日期段數據和查看基金淨值走勢圖,後面慢慢新增功能。

二、開始動手動腦

2.1 環境准備

  • Linux、Mac、Windows 都可以

  • python 3.7及以上

  • 相關第三方包:pandas(數據處理)、requests(爬取數據)、re(文本內容解析)、akshare(獲取基金股票數據)、matplotlib(數據可視化)、dataframe-image(dataframe表格轉成圖片)

2.2 獲取指定日期段基金數據

基金數據可以從一些金融相關的網站獲取到,比如天天基金網、新浪基金網等,可以自己寫爬蟲程序獲取網站數據,也可以使用現成的工具包獲取數據,比如:一行代碼獲取股票、基金數據,並繪制K線圖裡用到的akshare

這裡我們同時介紹下兩種方法:

2.2.1 回顧下akshare獲取基金數據

目前akshare不支持獲取指定日期范圍內的基金淨值數據,但是可以一次獲取到基金歷史淨值數據,調用函數fund_em_open_fund_info獲取基金歷史數據,然後自己根據日期選取時間斷進行分析。

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

自己調用現成數據接口

本質上akshare也是從一些金融相關的網站獲取到數據,我們也可以自己寫代碼進行獲取,通過浏覽器我們很快能搜索到基金數據接口,來自東方財富的天天基金網。

f'http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code={code}&page={page}&sdate={start_date}&edate={end_date}&per={per}'
code - 基金代碼
page - 基金數據頁碼
start_date - 數據開始日期
end_date - 數據結束日期
per - 每頁展現數據量,最多40

根據指定參數,浏覽器會返回指定參數,一段js賦值代碼,包括了 基金數據(content)、總記錄條數(records)、總頁數(pages)、當前頁數(curpage)。

格式非常規整,我們可以直接通過正則提取數據,

'''
獲取單頁面 基金數據
'''
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

通過返回的數據可以發現,基金數據部分是一個由table標簽包裹的html代碼,那我們可以直接使用pandas的read_html來解析數據。

# 從html中解析出數據表部分 並解析成df
def parses_table(html):
    # 獲取基金數據表
    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

前面有提到,基金數據接口返回數據每頁最多展示40條,所以要想獲取所有數據,我們可能需要遍歷每一頁,那麼我們還需要通過正則將總頁數pages獲取到,然後遍歷調用get_htmlparses_table函數解析出所有數據。

# 獲取指定日期內 累計淨值 等數據
def get_fund_data(code, start_date, end_date):
    first_page = get_html(code, start_date, end_date)
    # 獲取總頁數
    pattern = 'pages:(.*),'
    pages = re.search(pattern, first_page).group(1)
    # 轉成int數據
    try:
        pages = int(pages)
    except Exception as e:
        r = f'【錯誤信息】{e}'
        # print(r)
        return r 
    
    # 存放每頁獲取到的基金數據 dataframe格式 便於後面合並
    fund_df_list = []
    
    # 循環便利所有頁面
    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)
    
    # 將每頁的數據合並到一起
    fund_df = pd.concat(fund_df_list)
    
    return fund_df

上面兩種方法都可以獲取到基金淨值數據,最後我選擇了akshare方式獲取,設置一個定時任務,每天三點更新自己關注的基金所有數據,存儲到本地,後面要查詢的時候直接讀取本地文件查詢即可。

  • 定時任務:每天早上3點獲取所有關注的基金歷史數據,存儲到本地

# 定時任務:每天早上3點獲取所有關注的基金歷史數據,存儲到本地
def get_all():
    try:
        # 從文件讀取 自己關注的基金代碼列表
        with open('./FD/funds.txt') as f:
            funds = [i.strip() for i in f.readlines()]
        # 遍歷 一個個更新數據
        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 '基金數據更新完成'
    except Exception as e:
        r = f"【錯誤信息】{e}"
        return r
  • 獲取指定基金 指定日期段 淨值數據

# 獲取指定基金 指定日期段 淨值數據
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 返回數據呈現方式

目前先簡單點,設置規則如下:

  • 1)如果數據量小於等於30條,就返回原始數據圖

原始數據圖就是直接將獲取到的數據轉成圖片的方式發送給用戶,這裡我們使用dataframe-image這個第三方包,使用非常簡單,pip安裝後,直接調用export函數即可快速將datafrmae數據轉成圖片。

# 將dtaframe表格轉變成圖片
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')

為了圖片數據更好看,我們還使用了df.style設置數據表格樣式(單位淨值最大值、最小值高亮和日增長率添加百分號)。

  • 2)如果數據量大於30條,就返回原始數據趨勢圖

原始數據趨勢圖就是將數據可視化下,然後返回給用戶,這裡我們選擇繪制數據的走(趨)勢圖,使用matplotlib進行繪制。

# 繪制基金單位淨值走勢圖
def draw_fund_line(fund_df, fund, start_d, end_d):
    plt.rcParams['figure.figsize'] = (8.0, 4.0) # 設置figure_size尺寸
    plt.rcParams['savefig.dpi'] = 300 #保存圖片分辨率
    # 不顯示右、上邊框
    ax=plt.gca() 
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    # 設置坐標網格
    plt.grid(axis="y", color='gray')  
    # 計算最大值 最小值坐標 並標注到圖中
    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')

這裡使用的是折線圖,有一些對圖片樣式的設置,比如:大小、邊框、最大/小值標注,但依然不是很美觀,後面繼續優化。

  • 完整調用

# 返回數據
def response_data(fund, start_d, end_d):
    # 本地查看 查詢結果是否已存在
    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'
    
    # 獲取數據
    fund_df = get_fund_data(fund, start_d, end_d)
    
    # 如果數據量小於等於30條,就返回原始數據圖
    if fund_df.shape[0]<= 30:
        df_to_img(fund_df, fund, start_d, end_d)
    else:
        # 否則返回數據趨勢圖
        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 對接釘釘機器人設置守護程序

釘釘群機器人主要用來匯報每天自動匯報基金數據更新情況,後面還可以加基金漲跌檢測情況等。

企業機器人主要用來做基金數據查詢自動回復功能,也可以拓展主動發消息給用戶,後面研究研究。

2.5 遇到問題及解決方法

2.5.1 Linux上datafrmae-image轉圖片出錯

最開始是提示沒有chrom,然後按網上教程安裝了google chrom。

參考:https://segmentfault.com/a/1190000022425145

安裝後,運行代碼提示SyntaxError: not a PNG file

看錯誤提示以為是Pillow和matplotlib的問題,修改到了和本地一樣的版本也不行。

最後看了源碼,發現可以轉換方法除了使用chrom,還可以用matplotlib,修改後,確實可以正常生成圖片了,但是沒有格式!!!

最後改回默認table_conversion,仔細看,發現有提示以下內容,大概意思linux下不能直接使用root用戶權限允許谷歌chrome,最簡單的方法就是創建一個普通用戶。

[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權限下,新建一個用戶od,並將/root目錄權限授予給它,然後輸入su指令切換到新用戶下。

useradd od
chown -R od /root
su od

再次運行確實能解決圖片生成和數據格式問題,但是有新問題:表頭中文無法顯示。。。

百般搜索,看源碼調試、看項目倉庫問題都沒解決,最後,最後我突然想到,我本地可以,兩個包的版本又是一樣,應該不是代碼問題,會不會是因為linux裡沒有安裝中文字體,所以無法顯示中文?

root用戶權限下,先創建一個目錄,存放中文字體,創建好後,可以直接利用寶塔將本地的SimHei字體上傳到對應目錄即可。

mkdir -p /usr/share/fonts/my_fonts

可以通過下面指令查看中文字體是否安裝成功,

fc-list :lang=zh

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

2.5.2 matplotlib圖片中文顯示問題

之前有寫過詳細解決方法,可以直接查看之前文章:永久解決matplotlib中文亂碼

2.5.3 釘釘機器人無法直接傳輸圖片

釘釘機器人目前只支持傳輸:普通文本、markdown文本、連接、actionCard消息和feedCard消息類型。

如果我想要將生成的基金數據圖發送給用戶,最好的方法是和之前一樣,先將圖片轉成鏈接,然後通過markdown形式傳輸。

如果系統僅個人使用,數據量不大,我們不必選擇網絡上現有的圖床工具(這樣我們還要寫接口對接代碼),可以直接開放個http端口去共享我們的圖片,本身企業機器人就使用到了flask,所以我們可以更簡單的實現這個功能。

app = Flask(__name__, static_folder='xxx/FD/IMG', static_url_path='/static')

在初始化flask app時,指定靜態文件所在目錄和靜態文件路由後綴即可,這樣,我們就可以通過:http://服務器IP地址:端口號/static/圖片文件名,訪問到對應圖片了。

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

2.6 最終效果圖

  • 指定查詢

查看某基金某個時間段內的基金淨值數據。(30條以內數據,表格展示;大於30條,趨勢圖展示)

查詢格式: F基金代碼 起始日期 結束日期,如:F005827 2021-12-03 2022-02-10

效果圖

  • 普通查詢

查看某基金近10天內淨值和日增長率數據+趨勢圖

查詢格式: F基金代碼,如:F005827

最近10天內,只有兩個交易日

三、後言後語

這項目說大不大,說小也不小,百行代碼,本機測試還是很順暢的,主要是遷移到Linux上後出現一些問題,從最開始的python版本問題(安裝了一個3.7.9),到datafrmae-image問題,延展出來的Linux安裝谷歌、設置新用戶、分配權限,以及源碼測試學習。需要完整項目源代碼的關注公眾號:Python源碼 即可獲取


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved