Flask股票数据采集分析可视化系统:爬虫、接口与ECharts联动实践

发布时间:2026/10/22 18:15:53
Flask股票数据采集分析可视化系统:爬虫、接口与ECharts联动实践 简介这是一份面向计算机专业毕业设计场景的完整项目资料主题为Flask股票数据采集分析可视化系统整合Python网络爬虫、金融数据清洗与可视化技术覆盖从实时/历史股票数据抓取到趋势分析、图表展示的完整链路适合需要完成类似选题的高校学生作为架构参考、代码模板与实验环境搭建依据。包体共95个文件、约25.14MB包含HTML/CSS/JS前端页面、jpg/png/svg等图片素材、Flask后端代码、依赖清单requirements.txt、数据库文件db以及项目说明文档和操作演示mp4便于快速理解系统结构与运行方式。目前已有136人学习下载除完整源码外还提供了【必看】项目说明.txt、说明.txt等文档涵盖安装指南、系统架构、功能介绍与使用方法配合项目演示视频可帮助读者节省环境配置和代码调研时间直接聚焦核心业务逻辑与可视化模块的实现思路并将项目直接用于毕业设计演示与二次开发。1. 这套 Flask 股票数据采集分析可视化系统核心是把数据链路一次打通很多毕业设计做到最后卡住你的往往不是算法而是「采集的数据进不了数据库、数据库的数据到不了前端、前端图表一片空白」这串连环问题。这套 Flask 股票数据采集分析可视化系统本质就是用 python 爬虫把行情数据落库再用 Flask 提供接口最后由 ECharts 把蜡烛图、成交量和均线指标渲染成一个可交互页面。它真正值钱的地方在于把金融数据从「原始抓取」到「可视化展示」的整条链路完整串联答辩时你可以当场演示数据是实时更新的而不是贴一张写死的截图。适合正在做毕设或者想快速入门金融数据方向开发的从业者照着复现。2. 先把数据流立住总体架构、行情数据源选型与项目骨架2.1 行情数据的四条获取路线怎么选写采集模块之前先要把「数据从哪来」定死。我见过不少开发者一上来就写爬虫解析网页结果被页面改版折腾两周。常见的行情数据来源大致有四类差异主要在是否要 token、更新频率和字段完整度上。路线是否需要 token适合的数据范围维护成本毕设推荐度需 token 的免费行情接口是A股日线、复权因子、财务字段较全低注意调用频率推荐字段最稳无需 token 的开源行情 SDK否历史日K、分钟线够用低但部分接口不稳定推荐适合兜底官方或交易所公开接口视情况指数、基础行情中需要自己解析格式可选直接解析网页 HTML无任何页面可见数据高页面结构一变就废不推荐作为主源我的建议是「一主一备」。主力接口负责日常增量更新备用接口在主力临时失效时顶上。两条路线都封装成同一套接口签名这样后面换数据源时业务代码一行都不用改。选型时还有一个容易被忽略的点复权字段。做技术指标必须用前复权或后复权数据否则除权除息当天 K 线会出现假跳空均线也会算错。如果某个数据源只给原始价那就得自己维护复权因子表工作量会明显变大。2.2 四层数据流采集层、存储层、接口层、展示层整个系统的架构我习惯拆成四层层与层之间只通过数据结构和 SQL 交互。采集层负责定时抓取日K线做增量更新和基础清洗。存储层用 SQLite 保存行情数据和计算好的指标结果。接口层是 Flask 应用只做一件事把数据库里的数据按约定格式返回 JSON。展示层是浏览器里的 ECharts 页面不直接碰数据库只 fetch 接口。这个分层最大的好处是每一层都能单独验证。采集层可以独立跑脚本看落库条数接口层可以用 curl 验证返回结构展示层哪怕后端没起也能用一份导出好的 JSON 临时渲染。答辩演示时哪一层出问题马上能定位到具体环节不会对着白屏干着急。数据流是单向的定时任务触发采集采集结果写入 daily_quote 表指标计算读取 daily_quote把结果写入 analysis_result 表Flask 查询这两张表返回 JSON前端拿到 JSON 渲染图表。注意不要在展示层边查边算那会让首次请求明显变慢。2.3 项目骨架与依赖第一天就定好的目录结构我一般会建议把项目按职责拆成下面这个结构而不是把所有代码堆在 app.py 里。stock-visual-system/ ├── app.py # Flask 应用入口与路由 ├── config.py # 数据库路径、定时任务开关、接口参数 ├── requirements.txt ├── models/ │ ├── __init__.py │ ├── database.py # SQLite 连接与建表 │ └── indicators.py # MA、RSI 等指标计算 ├── services/ │ ├── __init__.py │ ├── data_fetcher.py # 行情接口封装统一返回 DataFrame │ ├── update_daily.py # 增量更新逻辑 │ └── scheduler.py # 定时任务配置 ├── static/ │ ├── echarts.min.js │ └── style.css └── templates/ └── index.html # 可视化页面requirements.txt 保持精简核心就这几项flask3.0 pandas2.0 sqlalchemy2.0 apscheduler3.10 requests2.31用 SQLAlchemy 是为了能用 pandas 的 read_sql 直接读表省掉手写大量数据转换代码。APScheduler 负责定时采集。requests 是爬虫基础库但如果选用的是开源行情 SDK它内部会自己处理请求这个依赖只在走 HTML 解析时才真正用到。3. 数据采集与存储落地接口封装、增量更新、定时任务和 SQLite 建表3.1 把数据源封装成一个 data_fetcher统一返回清洗后的 DataFrame常见做法是写一个 DataFetcher 类不管底层是哪个数据源对外都只暴露一个方法传入股票代码、起始日期、结束日期和复权方式返回干净的 DataFrame。这样做的好处是主备数据源切换时调用方无感知。# services/data_fetcher.py import time import pandas as pd class DataFetcher: 行情数据源封装统一返回 date/open/high/low/close/volume 的 DataFrame。 def __init__(self, sourceprimary, max_retry3): self.source source self.max_retry max_retry def fetch_kline(self, code, start_date, end_date, adjustqfq): code: 股票代码如 600000 start_date / end_date: 格式 YYYY-MM-DD adjust: qfq 前复权 / hfq 后复权 / 原始价 for attempt in range(self.max_retry): try: df self._read_from_source(code, start_date, end_date, adjust) if df is None or df.empty: return pd.DataFrame() # 统一字段名 df df.rename(columns{ date: trade_date, vol: volume, }) df df[[trade_date, open, high, low, close, volume, amount]] # 按日期升序并重置索引后续 rolling 计算才不会乱 df df.sort_values(trade_date).reset_index(dropTrue) return df except Exception: if attempt self.max_retry - 1: raise # 指数退避0s, 2s, 8s避免在限频接口上反复撞车 time.sleep(2 ** attempt) def _read_from_source(self, code, start_date, end_date, adjust): # 这里根据 self.source 选择主备数据源 # 主源走带 token 的行情接口备源走开源 SDK底层实现细节不暴露 raise NotImplementedError几个关键参数要说明reset_index 很重要不然按 trade_date 排序后索引还是乱的后面 rolling 算均线时容易出现错位。max_retry 设为 3 就够因为行情接口的失败通常是限频或网络抖动三次指数退避已经能覆盖绝大多数情况。如果三次都失败直接抛出异常让上层记录日志不要无限重试。补一个细节amount 字段是成交额单位是元。部分数据源返回的是千元或万元如果不统一单位后面算换手率时会完全错误。我一般会在清洗阶段做一次单位校准并在日志里打印样本值核对。3.2 增量更新与停牌处理别每次都全量拉取全量拉取历史数据只做一次。之后每天的更新应该走增量逻辑从本地最新日期开始拉而不是从头再跑一遍。这样既省请求配额也大幅缩短更新时间。# services/update_daily.py from sqlalchemy import create_engine, text import pandas as pd def incremental_update(fetcher, code, db_path, start_date2015-01-01): engine create_engine(fsqlite:///{db_path}) # 查本地最新日期首次使用时表为空回退到配置的起始日期 latest pd.read_sql( SELECT MAX(trade_date) AS d FROM daily_quote WHERE code :code, engine, params{code: code} )[d].iloc[0] if latest is None: begin start_date else: # 往前多取两天避免主力数据源因节假日漏数据 begin (pd.Timestamp(latest) - pd.Timedelta(days2)).strftime(%Y-%m-%d) df fetcher.fetch_kline(code, begin, pd.Timestamp.today().strftime(%Y-%m-%d)) if df.empty: return 0 # 与本地已有数据合并按 (code, trade_date) 去重保留新值 df[code] code old pd.read_sql( SELECT code, trade_date, open, high, low, close, volume, amount FROM daily_quote WHERE code :code, engine, params{code: code} ) merged pd.concat([old, df]).drop_duplicates( subset[code, trade_date], keeplast ).sort_values(trade_date) merged.to_sql(daily_quote, engine, if_existsreplace, indexFalse) return len(df) - len(old)这里有几个实际场景中的边界。停牌日不会有新数据所以合并后总数不会增加不是 bug。如果股票发生过除权除息前复权数据在除权日当天会被整体平移这时会出现「同样的日期新拉的数据和库里旧数据数值不同」的情况keeplast 保证了以新数据为准。要注意的是这段示例为了可读性用了整表 replace。如果数据量很大更稳妥的做法是使用 SQLite 的 INSERT OR REPLACE 或事务批量更新。毕设规模下整表 replace 问题不大但你要明确知道它是在重写全表。3.3 定时刷新用 APScheduler 而不是手写死循环定时任务我一般用 APScheduler 的 BackgroundScheduler而不是在 Flask 里加一个 while True。后者在调试模式下会被 reloader 重复启动而且进程退出时无法优雅关闭。# services/scheduler.py from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger import atexit def start_scheduler(job_func): scheduler BackgroundScheduler(timezoneAsia/Shanghai) # 交易日收盘后再跑选 17:30 避开数据源更新高峰 trigger CronTrigger( day_of_weekmon-fri, hour17, minute30, timezoneAsia/Shanghai ) scheduler.add_job( job_func, triggertrigger, max_instances1, # 同一时间只允许一个实例防止重入 coalesceTrue, # 错过执行时间后只补跑一次 misfire_grace_time600, # 错过 10 分钟内的任务仍执行 ) scheduler.start() atexit.register(lambda: scheduler.shutdown(waitFalse))max_instances1 是必填项否则某次任务执行时间过长跨到了下一个触发点会同时跑两个采集任务。coalesceTrue 表示如果任务因关机或休眠错过了多次触发恢复后只执行一次。timezone 必须明确写否则 APScheduler 会使用服务器本地时区跨机器部署时容易差八小时。这里要特别提醒如果你用 Flask 的 debug 模式开发定时任务请用flask run --no-reload启动否则 werkzeug 的 reloader 会把 Flask 进程复制一份你的定时任务也会被执行两次。3.4 SQLite 三张表的建表设计存储层不复杂但表结构决定了后面所有查询的写法。我常用三张表stock_info 存股票基本信息daily_quote 存日K行情analysis_result 存计算好的指标结果。# models/database.py from sqlalchemy import create_engine, text SCHEMA CREATE TABLE IF NOT EXISTS stock_info ( code TEXT PRIMARY KEY, name TEXT NOT NULL, industry TEXT, list_date TEXT ); CREATE TABLE IF NOT EXISTS daily_quote ( code TEXT NOT NULL, trade_date TEXT NOT NULL, open REAL, high REAL, low REAL, close REAL, volume INTEGER, amount REAL, PRIMARY KEY (code, trade_date) ); CREATE INDEX IF NOT EXISTS idx_quote_date ON daily_quote (trade_date); CREATE TABLE IF NOT EXISTS analysis_result ( code TEXT NOT NULL, trade_date TEXT NOT NULL, ma5 REAL, ma10 REAL, ma20 REAL, rsi14 REAL, pct_change REAL, signal TEXT, PRIMARY KEY (code, trade_date) ); def init_db(db_pathstock.db): engine create_engine(fsqlite:///{db_path}) with engine.begin() as conn: conn.execute(text(SCHEMA)) # 开启 WAL读请求不会被写请求阻塞 with engine.begin() as conn: conn.execute(text(PRAGMA journal_modeWAL)) return enginetrade_date 我用 TEXT 而不是 DATETIME。原因很简单行情日期只需要按字符串比较和排序TEXT 在 SQLite 里排序规则与日期完全一致而且避免时区和格式化问题。primary key (code, trade_date) 直接承担去重约束就算上层增量逻辑写错了重复数据也插不进去。4. 指标计算与 Flask 接口预计算策略、向量化算 MA/RSI 与 JSON 返回结构4.1 指标计算放到采集后而不是放在每次请求里指标计算放哪个环节是很多人会纠结的点。我的选择是定时任务采完数据后立即计算指标把结果存在 analysis_result 表里。这样前端请求时只是读表几十毫秒就能返回。如果放在请求时实时算虽然数据永远是最新的但每次请求都要读全表再跑一遍 pandas接口响应会明显变慢。一个例外是「最新一个交易日的指标」。定时任务在收盘后运行算出的最新值就是当日收盘指标所以不存在“盘中数据没更新”的问题。如果你要做盘中实时刷新那才需要把指标计算挪到接口层并加一层缓存。4.2 用 pandas 向量化计算 MA 和 RSI注意窗口与除零指标计算全部用 pandas 的向量化操作不要用 for 循环逐行算。效率是一方面更重要的是代码简洁、不容易错。# models/indicators.py import pandas as pd def compute_ma(close: pd.Series, window: int) - pd.Series: 简单移动平均线窗口为连续的 window 个交易日。 return close.rolling(windowwindow, min_periodswindow).mean() def compute_rsi(close: pd.Series, period: int 14) - pd.Series: RSI 采用 Wilder 平滑口径与主流行情软件更接近。 delta close.diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.ewm(alpha1 / period, min_periodsperiod).mean() avg_loss loss.ewm(alpha1 / period, min_periodsperiod).mean() rs avg_gain / avg_loss.replace(0, float(nan)) rsi 100 - 100 / (1 rs) return rsi def compute_pct_change(close: pd.Series) - pd.Series: 日涨跌幅保留百分比数值例如 0.023 表示 2.3%。 return close.pct_change()三个细节要讲清楚。一是 rolling 的 min_periods 参数窗口不足时返回 NaN前端对 NaN 要单独处理否则 JSON 序列化会报错。二是 RSI 计算口径我选用 ewm 配合 alpha1/period 的 Wilder 平滑而不是简单移动平均因为行情软件普遍采用这个口径不然你算出来的 RSI 和同花顺对不上。三是 avg_loss 为 0 时会出现除零所以先用 replace(0, NaN) 把值改为缺失。4.3 Flask 路由与 JSON 返回结构设计接口层我习惯设计两个核心路由一个返回 K 线数据一个返回最新指标快照。返回结构要一次到位前端拿到的直接是能塞进 ECharts 的数组。# app.py from flask import Flask, jsonify, request import pandas as pd from models.database import init_db from models.indicators import compute_ma, compute_rsi, compute_pct_change app Flask(__name__) engine init_db() def _query_klines(code: str, limit: int, offset: int) - list: df pd.read_sql( SELECT trade_date, open, high, low, close, volume, amount FROM daily_quote WHERE code :code ORDER BY trade_date DESC LIMIT :limit OFFSET :offset , engine, params{code: code, limit: limit, offset: offset} ) # 前端需要升序展示接口统一返回升序 df df.sort_values(trade_date) return df.to_dict(orientrecords) app.route(/api/klines) def api_klines(): code request.args.get(code, 600000) limit int(request.args.get(limit, 500)) offset int(request.args.get(offset, 0)) data _query_klines(code, limitlimit, offsetoffset) # NaN 直接输出为 null避免 JSON 序列化报错 return jsonify({ code: code, count: len(data), klines: data, }) app.route(/api/analysis) def api_analysis(): code request.args.get(code, 600000) df pd.read_sql( SELECT trade_date, ma5, ma10, ma20, rsi14, pct_change, signal FROM analysis_result WHERE code :code ORDER BY trade_date DESC LIMIT 1 , engine, params{code: code} ) row df.to_dict(orientrecords) return jsonify({code: code, latest: row[0] if row else None})字段名全部用 snake_case前端 JavaScript 里直接对应不用二次转驼峰。注意 SQL 里 order by trade_date desc 取了最近 500 条但返回前端前又排成了升序这是为了让 K 线从左到右时间递增同时保证 limit 取的是「最近的 500 条」而不是「最旧的 500 条」。4.4 limit 分页与索引的关系数据量大之后分页比一次性返回全部数据稳妥。默认 limit 500 对应大约两年的日K线足够展示。前端如果拖动滑块要看更早数据再按 offset 增量请求。这里有个容易忽略的点LIMIT OFFSET 的分页在 SQLite 上性能还可以但必须建立在索引之上。我在建表时加了 idx_quote_date 索引同时 WHERE 条件按 code 过滤。如果只按 trade_date 索引而没走 code查询会全表扫描响应时间会从几十毫秒涨到几百毫秒。5. 可视化联动ECharts 蜡烛图、成交量与 dataZoom 参数调优5.1 前端只做一件事fetch 接口喂给 ECharts可视化页面我直接用单个 HTML 模板引入本地 ECharts 文件不引外链避免答辩现场没有外网时图表加载不出来。页面onload后第一件事是请求/api/klines拿到数据后填进图表。// templates/index.html 中的核心片段 async function loadKline(code) { const resp await fetch(/api/klines?code${code}limit500offset0); const result await resp.json(); if (!result.klines || result.klines.length 0) { document.getElementById(emptyTip).style.display block; return; } const dates result.klines.map(item item.trade_date); // 注意 ECharts 蜡烛图的数据顺序是 [open, close, lowest, highest] const klineData result.klines.map(item [item.open, item.close, item.low, item.high] ); const volumeData result.klines.map(item [item.trade_date, item.volume]); klineChart.setOption({ xAxis: { data: dates }, series: [{ data: klineData }, { data: volumeData }] }, true); }第二个参数true表示 notMerge也就是用新数据完全覆盖旧配置。因为股票代码切换时日期范围会变如果不覆盖ECharts 会尝试合并新旧数据导致图表错乱。setOption 的 merge 行为是默认值这里必须显式传 true。另外注意蜡烛图数据结构是 [open, close, low, high]不是 [open, high, low, close]。这个顺序很容易写反写反后 K 线图上的影线和实体是颠倒的形态完全错误但接口返回值看起来又正常排查起来比较隐蔽。5.2 蜡烛图和成交量共用一个 dataZoom左右拖动才不乱K 线和成交量通常放在两个 grid 里但缩放要联动。用 xAxisIndex 同时绑定两个坐标轴是最省事的做法。option { grid: [ { left: 60, right: 20, top: 30, height: 55% }, { left: 60, right: 20, top: 72%, height: 18% } ], xAxis: [ { type: category, gridIndex: 0, data: dates }, { type: category, gridIndex: 1, data: dates } ], yAxis: [ { gridIndex: 0, scale: true }, { gridIndex: 1, scale: true } ], dataZoom: [ { type: inside, xAxisIndex: [0, 1], // 同时控制两个 grid 的 x 轴 start: 70, end: 100 }, { type: slider, xAxisIndex: [0, 1], top: 92%, height: 20 } ], series: [ { name: K线, type: candlestick, data: klineData }, { name: 成交量, type: bar, xAxisIndex: 1, yAxisIndex: 1, data: volumeData } ] };dataZoom 里 start: 70 表示默认展示最后 30% 的数据也就是最近约 150 个交易日这对日K线来说是比较舒服的初始视野。inside 类型的缩放响应鼠标滚轮slider 类型提供一个可视化拖动条。两段 xAxis 必须都给 data否则成交量图表的位置会偏。5.3 时间轴类型、图例遮挡和空数据提示三个常见坑如果你把 xAxis 的 type 写成 time而数据里 trade_date 又是 2024-05-20 这种字符串ECharts 也能解析但折线会变成不连续的散点因为节假日和停牌日没有数据点。K线图场景下我统一用 type: category把日期数组作为空档数据ECharts 会把缺失日期视为天然间距不会硬拉成连续时间轴。图例遮挡主要发生在设置 dataZoom 滑块之后。slider 默认放在底部如果页面高度不够会压住成交量图的 x 轴坐标。我把 slider 的 top 设为 92%同时把成交量 grid 的 bottom 预留足够空间。空数据提示一定得有否则接口返回空数组时页面只显示一个空白区域答辩时很容易被问住。6. 常见问题排查与避坑清单这些坑我都替你先踩过了6.1 复权后的K线在除权日前后出现明显跳空现象某只股票除权除息后K 线图在除权日当天出现一条突兀的向下跳空缺口但真实走势并没有这样。原因这是典型的数据源口径混用问题。增量更新时主力数据源默认返回前复权数据而历史数据是原始价两条数据拼在一起等于把不同坐标系的数值放进了同一张图。解决在 DataFetcher 里统一强制指定 adjust 参数并且落库时把复权价和原始价分成两个字段。我的建议是 daily_quote 表存原始价analysis_result 计算时读取原始价后再做复权这样指标口径一致以后做回测也有原始价可用。如果一定要存复权价那所有历史数据必须用同一个复权方式一次性重算不能用增量方式混合。6.2 增量更新后数据越来越多图表出现重复K线现象运行几次增量更新后daily_quote 表里同一只股票的记录数明显超过交易日数量。原因增量更新的起始日期算错了。常见错误是把「本地最新日期减 1 天」当作增量起点但行情数据源在节假日和停牌日不返回数据下次更新时本地最新日期还停留在上一个交易日导致漏掉的数据永远补不回来。更隐蔽的是如果数据源自带缓存重复请求相同日期范围时会返回同一条数据配合 to_sql 的 append 模式就会产生重复记录。解决建立两层防线。第一层是 primary key (code, trade_date) 保证数据库层面不重复第二层是更新前用 max(trade_date) 做判断往前多取两天并 drop_duplicates 去重。这样即使上游数据有调整也能用 keeplast 覆盖旧值。6.3 Flask 调试模式启动后定时任务执行了两次现象打开 debug 模式后日志里出现两个采集任务同时跑数据库写入报锁错误。原因Flask 的 debug 模式默认开启 werkzeug reloader它会启动一个监视进程和一个工作进程。如果你在模块导入时执行了 scheduler.start()两个进程都会启动定时器自然跑双份。解决开发时用flask run --no-reload启动生产或演示时用waitress-serve --threads 4 app:app这类纯 WSGI 方式启动不用 Flask 自带服务器。同时在 APScheduler 里配置 max_instances1即使多进程也最多只有一个实例在跑。如果项目用了多进程部署还要保证定时任务只在一个进程里启动可以在启动参数里显式控制不要依赖环境变量猜。6.4 接口返回正常但前端图表一片空白现象浏览器里访问 /api/klines 能看到完整 JSON但页面图表区域空白没有报错。原因通常是前端字段名对不上。比如 pandas 输出到 JSON 后字段叫trade_date而前端代码写成了date或者蜡烛图数据顺序写成了 [open, high, low, close]。也有一种可能是 NaN 值没有被处理JSON 序列化时直接抛异常接口实际返回了 500。解决前端先打印 result.klines[0] 确认字段结构再对照 ECharts 文档检查数据顺序。后端在 jsonify 之前把 DataFrame 里的 NaN 用 None 替换一下用df.where(pd.notnull(df), None)就能一次处理完。6.5 K线接口首次打开要等很久翻页更慢现象本地跑得好好的放服务器上之后首次请求耗时两秒以上往后翻页越来越卡。原因没有对 daily_quote 表建索引或者查询条件里 code 和 trade_date 的顺序写反了。SQLite 在未命中索引时会把整个表扫一遍数据量到几万行之后明显变慢。解决确认建表语句里有PRIMARY KEY (code, trade_date)并且单独执行CREATE INDEX idx_quote_date ON daily_quote(trade_date)。接口层默认只返回最近 500 条配合 limit 和 offset 分页。还有一个值得做的优化SQLAlchemy 连接池默认关闭后每次查库都会重新打开文件把这个引擎设置成单例复用性能提升很可观。6.6 离线演示技巧把行情数据落成本地快照再跑答辩前我会做一次「断网预演」确保现场没外网也能完整演示。做法是写一个小脚本把近 500 条日K线和指标结果导出成一个 JSON 文件Flask 增加一个数据源开关离线时接口直接读本地文件在线时读数据库。# app.py 中增加一个数据源开关 import json import os OFFLINE os.environ.get(OFFLINE) 1 SNAPSHOT_PATH os.path.join(os.path.dirname(__file__), snapshot.json) def _query_klines_offline(code: str, limit: int, offset: int) - list: with open(SNAPSHOT_PATH, r, encodingutf-8) as f: data json.load(f) rows [row for row in data if row[code] code] rows rows[-limit - offset:][offset:] if offset else rows[-limit:] return rows app.route(/api/klines) def api_klines(): code request.args.get(code, 600000) limit int(request.args.get(limit, 500)) offset int(request.args.get(offset, 0)) data _query_klines_offline(code, limit, offset) if OFFLINE else _query_klines(code, limit, offset) return jsonify({code: code, count: len(data), klines: data})这个技巧的本质是讲清楚「数据从哪来」和「数据怎么用」是解耦的。接口层只依赖约定好的 JSON 结构不管数据来自数据库还是本地文件。演示时先用在线模式跑一遍实时更新再手动切到离线模式刷新页面证明缓存和后端逻辑都可靠。我习惯把接口冒烟验证写成一段 curl 脚本每改一次后端就批量跑一遍确认字段结构没变再去调前端样式。这样整套系统从采集到展示的每一步都有验证依据答辩时被追问也能给出明确边界。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询