代碼位址如下:
http://www.demodashi.com/demo/14734.html
項目介紹
本項目的基金資料來自天天基金網,但該網站使用者體驗較差,内容備援,故自己實作一個輕量級網站,從8個名額次元對股票基金和債券基金進行挑選,可對進行收益進行排序,并且可檢視基金的同類排名與曆史漲跌幅,友善快捷的篩選選出優質基金進行投資,最大化收益率。
網站已部署在 http://134.175.24.108:52571 可點選連結檢視效果,為了不影響其他使用者的體驗,部署的網站隻顯示前100隻基金的資料,删除和自選的操作使用了本地存儲,存在浏覽器端。下載下傳的代碼壓縮包中包含了本地存儲和服務端存儲兩種版本。
項目截圖如下:
項目檔案結構
fund # 代碼檔案夾
├── exclude.txt # 已删除基金清單
├── own.txt # 自選基金清單
├── requirements.txt # python第三方庫依賴檔案
├── fund.py # 服務端檔案
├── static # 靜态檔案夾
│ ├── highstock.js
│ ├── index.css
│ ├── index.js
│ └── jj.png
└── templates # 模闆檔案夾,存放html頁面
└── index.html
此外還有一個
fund_local
檔案夾,結構與
fund
檔案夾一緻,其中的代碼為本地存儲版本,使用localStorage
項目運作
- 安裝python
- 安裝python第三方依賴庫,項目檔案夾中有依賴檔案
,依賴的庫主要有flask,flask-bootstrap,pandas,gevent,requests。pip install -r requirements.txt
- 運作
,成功運作會輸出python fund.py
Listening at http://localhost:52571
- 通路 http://localhost:52571
代碼實作
使用flask架構,網站前端架構使用bootstrap,表格控件為bootstrap-table,趨勢圖為highstock,文章最後有用到幾個架構的文檔網址,可自行閱讀。
後端實作
首先進行初始化
def __init__(self, fund_type, num=100):
self.app = Flask(__name__)
self.num = num # 基金數量
self.last_time = 0 # 最後一次更新時間
self.app.debug = True
self.fund_type = fund_type # 基金類型,(股票,債券或者混合型)
self.GetExcludeAndOwn() # 擷取已删除和自選的基金清單
self.GetData() # 擷取資料
Bootstrap(self.app) # 初始化bootstrap
初始化完成之後
# 讀取檔案,擷取删除的基金和自選的基金
def GetExcludeAndOwn(self):
with open("exclude.txt", "a+") as fs:
self.exclude = map(lambda x: x.strip(), fs.readlines())
self.exclude = set(self.exclude)
print("exclude id: ", self.exclude)
with open("own.txt", "a+") as fs:
self.own = map(lambda x: x.strip(), fs.readlines())
self.own = set(self.own)
print("own id: ", self.own)
從天天基金網擷取基金資料,通過抓包得到資料的連結,爬蟲與抓包這裡不做展開,有興趣可單獨聯系。
基金資料每天更新一次,擷取到基金資料之後,存入DateFrame,去掉多餘的列,隻保留8個名額,然後将資料轉為html,友善後面直接傳回給前端展示。
# 擷取近一年基金資料
def GetData(self):
now = time.time()
if (now - self.last_time < 60 * 60 * 24): # 24小時更新一次
return self.fund_table
self.last_time = now
s = requests.Session()
now = datetime.today()
# 起始時間為一年前的今天,結束時間為今天
sd = date2str(now - timedelta(days=365)).strftime('%Y-%m-%d')
ed = date2str(now).strftime('%Y-%m-%d')
res = s.get('http://fund.eastmoney.com/data/rankhandler.aspx?op=ph&dt=kf&ft=%s&rs=&gs=0&sc=1yzf&st=desc&sd=%s&ed=%s&qdii=|&tabSubtype=,,,,,&pi=1&pn=%d&dx=1&v=%lf' %
(self.fund_type, sd, ed, self.num, random())).content
res = res[res.find("["):res.rfind("]") + 1]
obj = json.loads(res)
arr = []
for fund in obj:
arr.append(fund.split(",")[:-4])
columns = ["基金代碼", "基金簡稱", "", "", "機關淨值", "累計淨值", "日增長率", "近1周", "近1月",
"近3月", "近6月", "近1年", "近2年", "近3年", "今年來", "成立來", "", "", "", "", "手續費"]
self.fund_df = pd.DataFrame(arr, columns=columns)
# 去掉多餘的列
self.fund_df.drop(self.fund_df.columns[[-2, -3, -4, -5, -6, -7, 2, 3, 4, 5, 6]],
axis=1, inplace=True)
self.fund_df.insert(0, "checkbox", "")
# 自選基金資料
self.own_df = self.fund_df[self.fund_df['基金代碼'].isin(self.own)]
self.own_table = self.own_df.to_html(index=False)
# 其他基金資料
self.fund_df = self.fund_df[~self.fund_df['基金代碼'].isin(self.exclude)]
self.fund_table = self.fund_df.to_html(index=False)
return self.fund_table
設定路由,監聽前端請求,删除基金的時候,将基金代碼寫入檔案并更新基金資料,下次請求将不再傳回已删除的基金
@self.app.route("/fund", methods=["GET", "DELETE"])
def fund():
# 檢視全部基金
if request.method == "GET":
return self.GetData()
# 删除選中的基金
else:
id_list = request.form.getlist("id[]")
self.fund_df = self.fund_df[~self.fund_df['基金代碼'].isin(id_list)]
self.fund_table = self.fund_df.to_html(index=False)
self.exclude |= set(id_list)
with open("exclude.txt", "w") as fs:
fs.writelines(map(lambda x: "%s\n" % x, self.exclude))
return "OK"
關于自選基金,加自選時,将基金代碼添加到自選基金清單,并且添加到已删除清單,保證記錄的唯一性。取消自選時從自選清單删除,重新在全部基金裡顯示。
@self.app.route("/fund", methods=["GET", "DELETE"])
def fund():
# 檢視全部基金
if request.method == "GET":
return self.GetData()
# 删除選中的基金
else:
id_list = request.form.getlist("id[]")
self.fund_df = self.fund_df[~self.fund_df['基金代碼'].isin(id_list)]
self.fund_table = self.fund_df.to_html(index=False)
self.exclude |= set(id_list)
with open("exclude.txt", "w") as fs:
fs.writelines(map(lambda x: "%s\n" % x, self.exclude))
return "OK"
@self.app.route("/own", methods=["GET", "POST", "DELETE"])
def own():
# 檢視自選基金
if request.method == "GET":
return self.own_table
# 加自選
if request.method == "POST":
id_list = request.form.getlist("id[]")
self.own_df = self.own_df.append(
self.fund_df[self.fund_df['基金代碼'].isin(id_list)]
)
self.own_table = self.own_df.to_html(index=False)
self.own |= set(id_list)
with open("own.txt", "w") as fs:
fs.writelines(map(lambda x: "%s\n" % x, self.own))
self.fund_df = self.fund_df[
~self.fund_df['基金代碼'].isin(id_list)
]
self.fund_table = self.fund_df.to_html(index=False)
self.exclude |= set(id_list)
with open("exclude.txt", "w") as fs:
fs.writelines(map(lambda x: "%s\n" % x, self.exclude))
return "OK"
# 取消自選
else:
id_list = request.form.getlist("id[]")
self.fund_df = self.own_df[
self.own_df['基金代碼'].isin(id_list)
].append(self.fund_df)
self.fund_table = self.fund_df.to_html(index=False)
self.exclude -= set(id_list)
with open("exclude.txt", "w") as fs:
fs.writelines(map(lambda x: "%s\n" % x, self.exclude))
self.own_df = self.own_df[
~self.own_df['基金代碼'].isin(id_list)
]
self.own_table = self.own_df.to_html(index=False)
self.own -= set(id_list)
with open("own.txt", "w") as fs:
fs.writelines(map(lambda x: "%s\n" % x, self.own))
return "OK"
前端實作
頁面加載時向背景請求資料,然後使用bootstrap-table進行表格渲染
// 擷取全部基金資料
$.ajax({
url: '/fund',
type: 'GET',
success: function (res) {
$("#panel-all").html(res);
$all_table = $('#panel-all table');
$all_table.bootstrapTable(Object.assign({ toolbar: "#all-toolbar" }, fund_table_option));
$("#all-toolbar").show();
}
});
// 擷取自選基金資料
$.ajax({
url: '/own',
type: 'GET',
success: function (res) {
$("#panel-own").html(res);
$own_table = $('#panel-own table');
$own_table.bootstrapTable(Object.assign({ toolbar: "#own-toolbar" }, fund_table_option));
$("#own-toolbar").show();
}
});
基金删除,加自選,取消自選
// 删除選中的基金
$("#delete").click(function () {
var selections = $all_table.bootstrapTable('getAllSelections').map(function (row) {
var uid = row["基金代碼"];
$all_table.bootstrapTable("removeByUniqueId", uid);
return uid;
});
$.ajax({
url: '/fund',
type: 'DELETE',
data: { "id": selections },
success: function () { }
});
});
// 單隻基金 加自選
var add_own = function (code, ajax) {
var row = $all_table.bootstrapTable("getRowByUniqueId", code);
row["checkbox"] = false;
$own_table.bootstrapTable("append", row);
$all_table.bootstrapTable("removeByUniqueId", code);
if (ajax) {
$.ajax({
url: '/own',
type: 'POST',
data: { "id": [code] },
success: function () { }
});
}
};
// 選中的基金 批量加自選
$("#batch_add_own").click(function () {
var selections = $all_table.bootstrapTable('getAllSelections').map(function (row) {
var uid = row["基金代碼"];
add_own(uid, false);
return uid;
});
$.ajax({
url: '/own',
type: 'POST',
data: { "id": selections },
success: function () { }
});
});
var del_own = function (code, ajax) {
var row = $own_table.bootstrapTable("getRowByUniqueId", code);
row["checkbox"] = false;
$all_table.bootstrapTable("prepend", row);
$own_table.bootstrapTable("removeByUniqueId", code);
if (ajax) {
$.ajax({
url: '/own',
type: 'DELETE',
data: { "id": [code] },
success: function () { }
});
}
};
// 選中的基金取消自選
$("#batch_del_own").click(function () {
var selections = $own_table.bootstrapTable('getAllSelections').map(function (row) {
var uid = row["基金代碼"];
del_own(uid, false);
return uid;
});
$.ajax({
url: '/own',
type: 'DELETE',
data: { "id": selections },
success: function () { }
});
});
在本地存儲的版本中,以上操作不需要與背景互動,不需發送ajax請求,隻需将變化的基金代碼清單存入
localStorage
。如果對flask不太了解的話,建議先熟悉local版的代碼,背景代碼較為簡單,熟悉了之後再閱讀完整的版本。
####參考網站:
天天基金網:
http://fund.eastmoney.com/
flask中文文檔:
https://dormousehole.readthedocs.io/en/latest/
bootstrap-table中文文檔:
http://bootstrap-table.wenzhixin.net.cn/zh-cn/documentation/
highstock示範:
https://www.hcharts.cn/demo/highstock
Python-Flask實作基金自選網站
代碼位址如下:
http://www.demodashi.com/demo/14734.html
注:本文著作權歸作者,由demo大師發表,拒絕轉載,轉載需要作者授權