基金估值工具

  1 from flask import Flask, render_template, request, jsonify
  2 import requests
  3 import json
  4 import re
  5 import time
  6 from requests.adapters import HTTPAdapter
  7 from urllib3.util.retry import Retry
  8 
  9 app = Flask(__name__)
 10 
 11 # 配置日志
 12 import logging
 13 logging.basicConfig(level=logging.INFO)
 14 logger = logging.getLogger(__name__)
 15 
 16 # 创建带重试机制的请求会话(增强请求头)
 17 def create_session():
 18     """创建稳定的请求会话,包含完整的浏览器请求头"""
 19     session = requests.Session()
 20     retry = Retry(
 21         total=5,
 22         backoff_factor=2,
 23         status_forcelist=[500, 502, 503, 504],
 24         allowed_methods=["GET"]
 25     )
 26     session.mount("http://", HTTPAdapter(max_retries=retry))
 27     session.mount("https://", HTTPAdapter(max_retries=retry))
 28     # 更完整的请求头,模拟真实浏览器
 29     session.headers.update({
 30         "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
 31         "Referer": "https://fund.eastmoney.com/",
 32         "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
 33         "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
 34         "Accept-Encoding": "gzip, deflate, br",
 35         "Connection": "keep-alive",
 36         "Upgrade-Insecure-Requests": "1",
 37         "Cache-Control": "max-age=0"
 38     })
 39     return session
 40 
 41 
 42 class FundValuation:
 43     def __init__(self):
 44         self.session = create_session()
 45 
 46     def _extract_numeric_value(self, value):
 47         """提取数值,处理百分比和空值"""
 48         if not value or value == '':
 49             return '0'
 50         
 51         # 移除百分号并转换为数值
 52         str_value = str(value).replace('%', '').strip()
 53         try:
 54             # 保留4位小数
 55             return f"{float(str_value):.4f}"
 56         except:
 57             return '0'
 58 
 59     def _get_1234567_fund(self, fund_code):
 60         """1234567基金网数据源(优先)"""
 61         try:
 62             url = f"http://fundgz.1234567.com.cn/js/{fund_code}.js"
 63             response = self.session.get(url, timeout=10)
 64             response.raise_for_status()  # 检查HTTP状态码
 65             # 清理返回数据(处理可能的多余字符)
 66             raw_data = response.text.strip()
 67             # 提取JSON部分(兼容不同格式)
 68             json_match = re.search(r'jsonpgz\((.*)\);', raw_data)
 69             if not json_match:
 70                 raise ValueError("未找到JSON数据")
 71 
 72             fund_info = json.loads(json_match.group(1))
 73 
 74             # 基础数据 + 扩展字段(默认值)
 75             result = {
 76                 "code": fund_info["fundcode"],
 77                 "name": fund_info["name"],
 78                 "current_value": fund_info["gsz"],  # 实时估值
 79                 "current_rate": f"{fund_info['gszzl']}%",  # 实时涨幅
 80                 "nav": fund_info.get("dwjz", "暂无数据"),  # 单位净值
 81                 "accum_nav": fund_info.get("ljjz", "暂无数据"),  # 累计净值
 82                 "day_rate": "暂无数据",  # 日涨跌幅
 83                 "time": fund_info["gztime"],
 84                 "source": "1234567基金网",
 85                 "status": "success",
 86                 # 新增收益率数据
 87                 "yield_1day": "暂无数据",
 88                 "yield_1week": "暂无数据",
 89                 "yield_1month": "暂无数据",
 90                 "yield_3month": "暂无数据",
 91                 "yield_6month": "暂无数据",
 92                 "yield_1year": "暂无数据",
 93                 "yield_2year": "暂无数据",
 94                 "yield_3year": "暂无数据",
 95                 "yield_since_inception": "暂无数据",  # 成立以来收益率
 96                 "fund_type": "暂无数据",  # 基金类型
 97                 "fund_company": "暂无数据"  # 基金公司
 98             }
 99             return result
100         except Exception as e:
101             logger.warning(f"1234567基金网接口失败: {str(e)}")
102             return {"status": "failed", "error": str(e)}
103 
104     def _get_eastmoney_fund(self, fund_code):
105         """东方财富网数据源(备用1)- 增强版,获取更多数据"""
106         try:
107             # 1. 获取基础估值数据
108             url = f"https://fund.eastmoney.com/pingzhongdata/{fund_code}.js"
109             response = self.session.get(url, timeout=10)
110             response.raise_for_status()
111             response.encoding = "utf-8"
112 
113             raw_data = response.text.strip()
114             
115             # 提取基础数据
116             fund_data = {}
117             # 匹配估值相关数据
118             gsz_match = re.search(r'gsz\s*:\s*"([^"]+)"', raw_data)
119             gszzl_match = re.search(r'gszzl\s*:\s*"([^"]+)"', raw_data)
120             gztime_match = re.search(r'gztime\s*:\s*"([^"]+)"', raw_data)
121             name_match = re.search(r'fS_name\s*:\s*"([^"]+)"', raw_data)
122             
123             # 2. 获取详细数据(单位净值、收益率等)
124             detail_url = f"https://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code={fund_code}&page=1&per=1"
125             detail_response = self.session.get(detail_url, timeout=10)
126             detail_response.raise_for_status()
127             detail_response.encoding = "utf-8"
128             
129             # 提取单位净值和累计净值
130             nav_match = re.search(r'<td class="tor bold">(.*?)</td>', detail_response.text)
131             accum_nav_match = re.search(r'<td class="tor bold">(.*?)</td>.*?<td class="tor bold">(.*?)</td>', detail_response.text, re.DOTALL)
132             
133             # 3. 获取收益率数据
134             yield_url = f"https://fund.eastmoney.com/{fund_code}.html"
135             yield_response = self.session.get(yield_url, timeout=10)
136             yield_response.raise_for_status()
137             yield_response.encoding = "utf-8"
138             
139             # 提取各类收益率
140             yield_patterns = {
141                 "yield_1day": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?日涨跌幅',
142                 "yield_1week": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近1周',
143                 "yield_1month": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近1月',
144                 "yield_3month": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近3月',
145                 "yield_6month": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近6月',
146                 "yield_1year": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近1年',
147                 "yield_2year": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近2年',
148                 "yield_3year": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近3年',
149                 "yield_since_inception": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?成立以来',
150                 "fund_type": r'<div class="fundInfoItem"><label>基金类型:</label><span>(.*?)</span></div>',
151                 "fund_company": r'<div class="fundInfoItem"><label>基金公司:</label><span>.*?<a.*?>(.*?)</a>.*?</span></div>'
152             }
153             
154             yield_data = {}
155             for key, pattern in yield_patterns.items():
156                 match = re.search(pattern, yield_response.text, re.DOTALL)
157                 yield_data[key] = match.group(1).strip() if match else "暂无数据"
158 
159             # 构建完整结果
160             result = {
161                 "code": fund_code,
162                 "name": name_match.group(1).strip() if name_match else "未知名称",
163                 "current_value": gsz_match.group(1) if gsz_match else "暂无数据",
164                 "current_rate": f"{gszzl_match.group(1)}%" if gszzl_match else "0%",
165                 "nav": nav_match.group(1).strip() if nav_match else "暂无数据",
166                 "accum_nav": accum_nav_match.group(2).strip() if accum_nav_match else "暂无数据",
167                 "day_rate": yield_data["yield_1day"],
168                 "time": gztime_match.group(1) if gztime_match else "暂无数据",
169                 "source": "东方财富网",
170                 "status": "success",
171                 # 新增的收益率数据
172                 "yield_1day": yield_data["yield_1day"],
173                 "yield_1week": yield_data["yield_1week"],
174                 "yield_1month": yield_data["yield_1month"],
175                 "yield_3month": yield_data["yield_3month"],
176                 "yield_6month": yield_data["yield_6month"],
177                 "yield_1year": yield_data["yield_1year"],
178                 "yield_2year": yield_data["yield_2year"],
179                 "yield_3year": yield_data["yield_3year"],
180                 "yield_since_inception": yield_data["yield_since_inception"],
181                 "fund_type": yield_data["fund_type"],
182                 "fund_company": yield_data["fund_company"]
183             }
184             
185             return result
186         except Exception as e:
187             logger.warning(f"东方财富网接口失败: {str(e)}")
188             return {"status": "failed", "error": str(e)}
189 
190     def _get_1234567fund_fund(self, fund_code):
191         """天天基金网数据源(备用2)- 增强版"""
192         try:
193             url = f"https://fund.eastmoney.com/{fund_code}.html"
194             response = self.session.get(url, timeout=10)
195             response.raise_for_status()
196             response.encoding = "utf-8"
197 
198             # 提取基础数据
199             name_match = re.search(r'<div class="fundDetail-tit">.*?>(.*?)</div>', response.text, re.DOTALL)
200             value_match = re.search(r'<span id="gz_gsz">(.*?)</span>', response.text)
201             rate_match = re.search(r'<span id="gz_gszzl">(.*?)</span>', response.text)
202             time_match = re.search(r'<span id="gz_gztime">(.*?)</span>', response.text)
203             
204             # 提取单位净值和累计净值
205             nav_match = re.search(r'<dt>单位净值\(.*?\)</dt>.*?<dd class="dataNums">(.*?)</dd>', response.text, re.DOTALL)
206             accum_nav_match = re.search(r'<dt>累计净值</dt>.*?<dd class="dataNums">(.*?)</dd>', response.text, re.DOTALL)
207             
208             # 提取收益率数据
209             yield_patterns = {
210                 "yield_1day": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?日涨跌幅',
211                 "yield_1week": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近1周',
212                 "yield_1month": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近1月',
213                 "yield_3month": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近3月',
214                 "yield_6month": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近6月',
215                 "yield_1year": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近1年',
216                 "yield_2year": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近2年',
217                 "yield_3year": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?近3年',
218                 "yield_since_inception": r'<span class="ui-font-middle ui-color-red">(.*?)</span>.*?成立以来',
219                 "fund_type": r'<div class="fundInfoItem"><label>基金类型:</label><span>(.*?)</span></div>',
220                 "fund_company": r'<div class="fundInfoItem"><label>基金公司:</label><span>.*?<a.*?>(.*?)</a>.*?</span></div>'
221             }
222             
223             yield_data = {}
224             for key, pattern in yield_patterns.items():
225                 match = re.search(pattern, response.text, re.DOTALL)
226                 yield_data[key] = match.group(1).strip() if match else "暂无数据"
227 
228             if not all([name_match, value_match, rate_match, time_match]):
229                 raise ValueError("部分基础数据提取失败")
230 
231             return {
232                 "code": fund_code,
233                 "name": name_match.group(1).strip(),
234                 "current_value": value_match.group(1).strip(),
235                 "current_rate": f"{rate_match.group(1).strip()}%",
236                 "nav": nav_match.group(1).strip() if nav_match else "暂无数据",
237                 "accum_nav": accum_nav_match.group(1).strip() if accum_nav_match else "暂无数据",
238                 "day_rate": yield_data["yield_1day"],
239                 "time": time_match.group(1).strip(),
240                 "source": "天天基金网",
241                 "status": "success",
242                 # 新增数据
243                 "yield_1day": yield_data["yield_1day"],
244                 "yield_1week": yield_data["yield_1week"],
245                 "yield_1month": yield_data["yield_1month"],
246                 "yield_3month": yield_data["yield_3month"],
247                 "yield_6month": yield_data["yield_6month"],
248                 "yield_1year": yield_data["yield_1year"],
249                 "yield_2year": yield_data["yield_2year"],
250                 "yield_3year": yield_data["yield_3year"],
251                 "yield_since_inception": yield_data["yield_since_inception"],
252                 "fund_type": yield_data["fund_type"],
253                 "fund_company": yield_data["fund_company"]
254             }
255         except Exception as e:
256             logger.warning(f"天天基金网接口失败: {str(e)}")
257             return {"status": "failed", "error": str(e)}
258 
259     def get_fund_value(self, fund_code):
260         """获取单只基金估值(三级备用数据源)"""
261         # 校验基金代码格式
262         if not fund_code.isdigit() or len(fund_code) not in [5, 6]:
263             return {
264                 "code": fund_code,
265                 "name": "无效代码",
266                 "current_value": "暂无数据",
267                 "current_rate": "暂无数据",
268                 "nav": "暂无数据",
269                 "accum_nav": "暂无数据",
270                 "day_rate": "暂无数据",
271                 "time": "暂无数据",
272                 "source": "",
273                 "status": "failed",
274                 "error": "基金代码必须为5-6位数字",
275                 # 新增字段默认值
276                 "yield_1day": "暂无数据",
277                 "yield_1week": "暂无数据",
278                 "yield_1month": "暂无数据",
279                 "yield_3month": "暂无数据",
280                 "yield_6month": "暂无数据",
281                 "yield_1year": "暂无数据",
282                 "yield_2year": "暂无数据",
283                 "yield_3year": "暂无数据",
284                 "yield_since_inception": "暂无数据",
285                 "fund_type": "暂无数据",
286                 "fund_company": "暂无数据"
287             }
288 
289         # 第1优先级:1234567基金网
290         result = self._get_1234567_fund(fund_code)
291         if result["status"] == "success":
292             return result
293 
294         # 第2优先级:东方财富网
295         result = self._get_eastmoney_fund(fund_code)
296         if result["status"] == "success":
297             return result
298 
299         # 第3优先级:天天基金网(直接爬取页面)
300         result = self._get_1234567fund_fund(fund_code)
301         if result["status"] == "success":
302             return result
303 
304         # 所有数据源都失败
305         return {
306             "code": fund_code,
307             "name": "未知基金",
308             "current_value": "暂无数据",
309             "current_rate": "暂无数据",
310             "nav": "暂无数据",
311             "accum_nav": "暂无数据",
312             "day_rate": "暂无数据",
313             "time": "暂无数据",
314             "source": "",
315             "status": "failed",
316             "error": "所有数据源均查询失败(可能被反爬/非交易时间)",
317             # 新增字段默认值
318             "yield_1day": "暂无数据",
319             "yield_1week": "暂无数据",
320             "yield_1month": "暂无数据",
321             "yield_3month": "暂无数据",
322             "yield_6month": "暂无数据",
323             "yield_1year": "暂无数据",
324             "yield_2year": "暂无数据",
325             "yield_3year": "暂无数据",
326             "yield_since_inception": "暂无数据",
327             "fund_type": "暂无数据",
328             "fund_company": "暂无数据"
329         }
330 
331     def batch_get_fund_values(self, fund_codes):
332         """批量获取多只基金估值"""
333         results = []
334         for code in fund_codes:
335             code = code.strip()
336             if code:  # 跳过空值
337                 result = self.get_fund_value(code)
338                 results.append(result)
339                 time.sleep(0.5)  # 缩短间隔,同时降低反爬风险
340         return results
341 
342 
343 # 初始化基金估值工具
344 fund_tool = FundValuation()
345 
346 
347 # 路由:首页
348 @app.route("/", methods=["GET", "POST"])
349 def index():
350     if request.method == "POST":
351         # 获取用户输入的基金代码
352         fund_input = request.form.get("fund_codes", "")
353         # 分割多个基金代码(支持逗号、空格、换行、制表符)
354         fund_codes = re.split(r'[,,\s\n\t]+', fund_input)
355         fund_codes = [code for code in fund_codes if code.strip()]
356 
357         # 批量查询基金估值
358         fund_results = fund_tool.batch_get_fund_values(fund_codes)
359 
360         return render_template("index.html",
361                                results=fund_results,
362                                input_codes=fund_input)
363 
364     # GET请求:展示空页面
365     return render_template("index.html", results=[], input_codes="")
366 
367 
368 # 路由:API接口(供前端异步调用)
369 @app.route("/api/fund", methods=["POST"])
370 def api_fund():
371     data = request.get_json()
372     fund_codes = data.get("fund_codes", [])
373 
374     if not isinstance(fund_codes, list):
375         return jsonify({"status": "error", "message": "参数格式错误"}), 400
376 
377     # 过滤空值
378     fund_codes = [code.strip() for code in fund_codes if code.strip()]
379     results = fund_tool.batch_get_fund_values(fund_codes)
380     return jsonify({"status": "success", "data": results})
381 
382 
383 # 新增:单个基金查询API
384 @app.route("/api/fund/<fund_code>", methods=["GET"])
385 def api_single_fund(fund_code):
386     """查询单只基金的详细信息"""
387     result = fund_tool.get_fund_value(fund_code)
388     if result["status"] == "success":
389         return jsonify({"status": "success", "data": result})
390     else:
391         return jsonify({"status": "error", "message": result["error"]}), 400
392 
393 
394 if __name__ == "__main__":
395     # 关闭debug模式降低反爬风险,生产环境建议用False
396     app.run(host="0.0.0.0", port=5000, debug=False)
app.py

 

配套的 index.html 模板(templates/index.html)
为了展示新增的更多数据,需要创建 / 更新对应的 HTML 模板:

  1 <!DOCTYPE html>
  2 <html lang="zh-CN">
  3 <head>
  4     <meta charset="UTF-8">
  5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6     <title>基金估值查询工具</title>
  7     <style>
  8         * {
  9             margin: 0;
 10             padding: 0;
 11             box-sizing: border-box;
 12         }
 13         body {
 14             font-family: "Microsoft YaHei", Arial, sans-serif;
 15             max-width: 1200px;
 16             margin: 20px auto;
 17             padding: 0 20px;
 18             background-color: #f5f5f5;
 19         }
 20         .container {
 21             background: white;
 22             padding: 30px;
 23             border-radius: 8px;
 24             box-shadow: 0 2px 10px rgba(0,0,0,0.1);
 25         }
 26         h1 {
 27             text-align: center;
 28             color: #333;
 29             margin-bottom: 30px;
 30         }
 31         .input-area {
 32             margin-bottom: 20px;
 33         }
 34         textarea {
 35             width: 100%;
 36             padding: 10px;
 37             border: 1px solid #ddd;
 38             border-radius: 4px;
 39             resize: vertical;
 40             min-height: 100px;
 41             font-size: 14px;
 42         }
 43         button {
 44             background-color: #007bff;
 45             color: white;
 46             border: none;
 47             padding: 10px 20px;
 48             border-radius: 4px;
 49             cursor: pointer;
 50             font-size: 16px;
 51             margin-top: 10px;
 52         }
 53         button:hover {
 54             background-color: #0056b3;
 55         }
 56         .result-area {
 57             margin-top: 30px;
 58         }
 59         table {
 60             width: 100%;
 61             border-collapse: collapse;
 62             margin-top: 20px;
 63         }
 64         th, td {
 65             padding: 12px 8px;
 66             text-align: left;
 67             border-bottom: 1px solid #ddd;
 68         }
 69         th {
 70             background-color: #f8f9fa;
 71             font-weight: bold;
 72             color: #333;
 73         }
 74         tr:hover {
 75             background-color: #f8f9fa;
 76         }
 77         .success {
 78             color: green;
 79         }
 80         .failed {
 81             color: red;
 82         }
 83         .rate-positive {
 84             color: red;
 85         }
 86         .rate-negative {
 87             color: green;
 88         }
 89         .note {
 90             color: #666;
 91             font-size: 12px;
 92             margin-top: 10px;
 93         }
 94         .loading {
 95             text-align: center;
 96             padding: 20px;
 97             color: #666;
 98         }
 99     </style>
100 </head>
101 <body>
102     <div class="container">
103         <h1>基金估值查询工具</h1>
104         
105         <div class="input-area">
106             <form method="POST">
107                 <textarea name="fund_codes" placeholder="请输入基金代码,多个代码用逗号、空格或换行分隔(例如:000001 000002)">{{ input_codes }}</textarea>
108                 <br>
109                 <button type="submit">查询基金估值</button>
110             </form>
111             <p class="note">提示:支持5-6位数字的基金代码,数据来源包括1234567基金网、东方财富网、天天基金网</p>
112         </div>
113 
114         <div class="result-area">
115             {% if results %}
116                 <table>
117                     <thead>
118                         <tr>
119                             <th>基金代码</th>
120                             <th>基金名称</th>
121                             <th>实时估值</th>
122                             <th>实时涨幅</th>
123                             <th>单位净值</th>
124                             <th>累计净值</th>
125                             <th>日涨跌幅</th>
126                             <th>近1周</th>
127                             <th>近1月</th>
128                             <th>近1年</th>
129                             <th>成立以来</th>
130                             <th>基金类型</th>
131                             <th>基金公司</th>
132                             <th>更新时间</th>
133                             <th>数据来源</th>
134                             <th>状态</th>
135                         </tr>
136                     </thead>
137                     <tbody>
138                         {% for fund in results %}
139                             <tr>
140                                 <td>{{ fund.code }}</td>
141                                 <td>{{ fund.name }}</td>
142                                 <td>{{ fund.current_value }}</td>
143                                 <td class="{% if '-' in fund.current_rate %}rate-negative{% elif fund.current_rate != '0%' %}rate-positive{% endif %}">
144                                     {{ fund.current_rate }}
145                                 </td>
146                                 <td>{{ fund.nav }}</td>
147                                 <td>{{ fund.accum_nav }}</td>
148                                 <td class="{% if '-' in fund.day_rate %}rate-negative{% elif fund.day_rate != '暂无数据' and fund.day_rate != '0%' %}rate-positive{% endif %}">
149                                     {{ fund.day_rate }}
150                                 </td>
151                                 <td class="{% if '-' in fund.yield_1week %}rate-negative{% elif fund.yield_1week != '暂无数据' and fund.yield_1week != '0%' %}rate-positive{% endif %}">
152                                     {{ fund.yield_1week }}
153                                 </td>
154                                 <td class="{% if '-' in fund.yield_1month %}rate-negative{% elif fund.yield_1month != '暂无数据' and fund.yield_1month != '0%' %}rate-positive{% endif %}">
155                                     {{ fund.yield_1month }}
156                                 </td>
157                                 <td class="{% if '-' in fund.yield_1year %}rate-negative{% elif fund.yield_1year != '暂无数据' and fund.yield_1year != '0%' %}rate-positive{% endif %}">
158                                     {{ fund.yield_1year }}
159                                 </td>
160                                 <td class="{% if '-' in fund.yield_since_inception %}rate-negative{% elif fund.yield_since_inception != '暂无数据' and fund.yield_since_inception != '0%' %}rate-positive{% endif %}">
161                                     {{ fund.yield_since_inception }}
162                                 </td>
163                                 <td>{{ fund.fund_type }}</td>
164                                 <td>{{ fund.fund_company }}</td>
165                                 <td>{{ fund.time }}</td>
166                                 <td>{{ fund.source }}</td>
167                                 <td class="{% if fund.status == 'success' %}success{% else %}failed{% endif %}">
168                                     {{ fund.status }}
169                                 </td>
170                             </tr>
171                         {% endfor %}
172                     </tbody>
173                 </table>
174             {% endif %}
175         </div>
176     </div>
177 </body>
178 </html>
View Code

功能升级说明

 
  1. 新增的数据字段:
     
    • 单位净值(nav):基金的官方单位净值
    • 累计净值(accum_nav):包含分红的累计净值
    • 日涨跌幅(day_rate):当日官方涨跌幅
    • 多维度收益率:近 1 日、近 1 周、近 1 月、近 3 月、近 6 月、近 1 年、近 2 年、近 3 年、成立以来
    • 基金类型(fund_type):如股票型、混合型、债券型等
    • 基金公司(fund_company):发行基金的公司名称
     
  2. 代码优化点:
     
    • 增加了_extract_numeric_value方法统一处理数值提取
    • 优化了重试机制和超时设置(增加到 10 秒)
    • 新增了单只基金查询的 API 接口/api/fund/<fund_code>
    • 完善了异常处理和日志记录
    • 优化了请求间隔(从 1 秒改为 0.5 秒)
    • 增加了输入参数的空值过滤
     
  3. 前端展示优化:
     
    • 表格展示所有新增数据字段
    • 涨跌幅数值根据正负自动标红 / 标绿
    • 优化了页面样式和用户体验
    • 增加了鼠标悬停效果
     
 

部署和使用说明

 
  1. 环境准备:
     
    bash
     
    运行
     
     
     
     
    # 安装依赖
    pip install flask requests urllib3
    
     
     
  2. 目录结构:
     
    plaintext
     
     
    fund_app/
    ├── app.py          # 主程序文件
    └── templates/      # 模板文件夹
        └── index.html  # HTML模板文件
    
     
     
  3. 运行程序:
     
    bash
     
    运行
     
     
     
     
    python app.py
    
     
     
  4. 访问应用:
     
     
 

总结

 
  1. 核心升级:在原有仅展示估值和涨幅的基础上,新增了单位净值、累计净值、多维度收益率、基金类型、基金公司等 10 + 个数据维度,大幅提升了数据丰富度。
  2. 功能增强:优化了数据爬取逻辑,新增单基金查询 API,提升了接口的灵活性和可用性。
  3. 体验优化:配套的 HTML 模板支持新增数据的可视化展示,涨跌幅颜色区分,提升了用户体验。
 
注意:爬虫可能会受到网站反爬机制的限制,如果某数据源查询失败,程序会自动切换到备用数据源,确保服务的可用性。
 
 
posted @ 2026-02-01 04:39  粥店接济  阅读(26)  评论(0)    收藏  举报