ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零搭建OpenStock:开源股票数据看板全流程实战

从零搭建OpenStock:开源股票数据看板全流程实战 最近不少朋友在后台问我OpenStock 到底怎么从零搭起来。我前阵子正好把一个开源的股票数据看板从无到有跑通了一遍从数据抓取到入库从计算指标到前端图表展示全套流程走下来踩了不少坑也摸出了一些比较顺手的路子。这篇就把整个搭建过程摊开来讲虽然是围绕 OpenStock 这个项目展开但里面的思路和细节对你做其他数据类小系统同样有参考价值。OpenStock 说白了就是一个开源的股票行情数据采集与展示系统核心价值在于把“数据获取、数据存储、指标计算、可视化看板”这条链路打通。适合三类人看一是刚入门想练手的数据开发同学二是对量化分析感兴趣、想自己拉数据做观察的投资者三是做 Web 应用想找一个完整实战案例的学生。即使你没有股票基础也没关系把它当成一个“实时数据管道”来看就行。我这次搭建不是直接拉一个大而全的现成框架而是采用“轻量组装”的方式用 Python 做数据处理、FastAPI 提供接口、SQLite 存储数据、ECharts 渲染图表前后端分离但不复杂单个开发者在自己的服务器上就能跑起来。下面我把整个设计思路和实操过程完整拆给你。1. 项目定位与整体设计思路1.1 OpenStock 到底是什么单看 OpenStock 这个名字很容易以为它只是一个查股价的网页工具。实际上它更像是一个私人的、可定制的股票数据分析终端。你可以把它理解成“自己的行情数据中心”每天定时拉取指定股票的日线数据存入本地数据库随后系统自动计算均线、涨跌幅等技术指标你在浏览器里打开页面就能看到完整的 K 线和各项指标图形。比起直接打开各种行情软件OpenStock 最大的优势在于数据属于自己的想怎么处理就怎么处理。你可以自己扩展数据源字段加入换手率、量比、北向资金流向也可以自由修改指标计算逻辑用自己研究的方法替代系统的默认公式还可以对接自己的选股策略生成候选股票池。这就是“开源 自托管”的核心价值。这次搭建我定位的是一个最小可用版本也就是能跑、能看、能扩展的 MVP。功能包含日线数据采集、数据库自动建表、基础指标计算、K 线图展示这几个核心模块。后续要加功能只需要在这个骨架上继续搭即可。1.2 技术选型为什么用这套组合技术选型上我花了一些心思最终选定的组合是 Python 3.10 FastAPI SQLite ECharts akshare全部采用开源方案成本为零。Python 做数据处理是绝对的红海生态akshare 直接解决了数据源这个大难题。它封装了大量公开财经数据接口只需一行代码就能拿到股票的历史行情省去了自己写爬虫对接网页的麻烦。FastAPI 负责提供 Web 接口自带交互式文档调试阶段非常方便。SQLite 则是零配置的文件型数据库对于单机场景、日均几千条数据的写入量性能和稳定性完全够用省去安装数据库服务的环节。前端没有用繁琐的前端框架而是直接采用服务端渲染页面加 ECharts 图表库。这样做的原因是项目核心在数据链路前端只要保证图表渲染流畅、交互直观即可没必要为了“技术栈高级”而引入构建工具链。整条链路简单清晰出了问题也容易定位数据不对查采集层接口不对查 API 层图表不对查前端。1.3 设计核心把拆分成数据层、服务层、展示层动手编码之前我在笔记本上画了一下模块划分最终确定三大层数据层负责采集和存储服务层负责指标计算和接口封装展示层负责页面渲染。数据层是最基础的一层包含采集器和数据访问对象。采集器从 akshare 拉取原始数据负责处理网络异常、字段重命名、数据清洗数据访问对象负责数据库写入和查询把原始的 SQLite 操作封装起来上层不需要关心数据库细节。服务层是业务逻辑所在地。它从数据库读取干净数据后计算 MA5、MA10、MA20 等常规指标再统一封装成 JSON 结构返回给前端。这一层还负责处理股票列表、交易日历等辅助数据。展示层则纯粹关注“用户看到什么”。页面通过 Ajax 请求服务层接口拿到数据后交给 ECharts 渲染 K 线图同时展示最新价格、涨跌幅等关键信息。这种三层结构最大的好处是每一层都可以独立测试、替换和升级。比如你不喜欢 akshare可以把数据源替换为其他接口只改动采集器即可上层完全不受影响。2. 搭建前的准备环境、数据源与目录规划2.1 服务器环境准备虽然 OpenStock 理论上可以在 Windows 上运行但我强烈建议准备一台 Linux 服务器我这次用的是一台 2 核 4G 的云服务器跑这个项目富余很多最低配置 1 核 1G 也可以。系统建议选择 Ubuntu 22.04 或 Debian 12这两个版本的 Python 包管理比较省心。需要安装的基础软件有 Python 3.10 以上版本、pip、git、vim 或 nano 编辑器。不同来源的服务器默认环境差异较大有的不带 git有的 Python 版本太老所以第一步总是先确认基础环境。sudo apt update sudo apt upgrade -y sudo apt install -y python3 python3-pip python3-venv git vim python3 --version如果 Python 版本低于 3.10建议先升级系统或使用 deadsnakes PPA 安装新版。我在一台老旧的 CentOS 机器上就碰到过 Python 3.6 不兼容 FastAPI 的坑最后干脆换系统重装节省了好多时间。2.2 数据源选型为什么用 akshareOpenStock 的数据全部来源自 akshare 这个开源接口库选择它主要基于三点免费、无需注册、覆盖范围广。它抓取的是公开财经数据但接口层做了统一封装返回的已经是格式化的 Pandas DataFrame极大降低了使用成本。不过使用第三方数据源要有一个心理预期接口字段名可能随上游调整接口服务可能有频率限制高峰时段可能响应慢。所以我在采集模块中做了数据缓存、重试机制和异常兜底确保一次数据拉取失败不会导致整个程序崩溃。有关炒股软件会有实时行情推送但 OpenStock 作为一个自建系统我们做的是“日线收盘数据”这个级别数据源是当日收盘后的数据足以支撑学习和分析用途。依赖所有工具之前记住数据源稳定性是项目长期运行的关键选 akshare 并不等于无脑用仍然要关注更新情况。2.3 项目目录结构规划动手写代码前先规划好目录可以避免后期结构混乱。我的目录规划如下openstock/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── config.py # 全局配置 │ ├── models.py # 数据模型 │ ├── collector.py # 数据采集模块 │ ├── indicators.py # 指标计算 │ ├── api.py # API 路由 │ └── static/ # 前端静态文件 ├── data/ │ └── stock.db # SQLite 数据库文件 ├── requirements.txt └── README.md这样的结构虽然简单但边界很清晰。config.py 集中管理股票代码列表、数据库路径、请求间隔等配置collector.py 只处理数据获取indicators.py 只做计算api.py 只做路由转发。后续扩展时比如新增一个资金流向采集模块直接在 app 下加文件即可。2.4 requirements.txt 和虚拟环境依赖管理是项目可复现的重要保障。我习惯为项目单独创建虚拟环境避免污染系统全局 Python。cd ~/openstock python3 -m venv venv source venv/bin/activate创建好虚拟环境后准备 requirements.txt 文件fastapi0.104.1 uvicorn0.24.0 pandas2.1.3 akshare1.12.0 SQLAlchemy2.0.23 requests2.31.0固定版本号很重要我踩过 akshare 新版接口变动导致旧代码报错的坑固定依赖版本可以保证过段时间重新部署时行为一致。安装依赖时如果用国内网络建议替换 pip 源速度差别非常大。pip install -r requirements.txt # 或者使用国内镜像 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple3. 手把手搭建实操过程3.1 初始化 FastAPI 项目入口环境准备好了以后先写最核心的应用入口文件 app/main.py。这一步的目标是让服务先跑起来后续再逐步填充其他模块。# app/main.py from fastapi import FastAPI from fastapi.staticfiles import StaticFiles app FastAPI(titleOpenStock - 开源股票数据看板) app.get(/) def index(): return {msg: OpenStock is running, status: ok} app.mount(/static, StaticFiles(directoryapp/static), namestatic)启动服务前先确保 static 目录存在。运行命令uvicorn app.main:app --host 0.0.0.0 --port 8000浏览器访问http://服务器IP:8000看到 JSON 返回说明 FastAPI 服务基础框架正常。这里要注意云服务器默认防火墙可能没有开放 8000 端口需要到安全组规则里放行否则外界访问不到。我当时卡在这浪费了十几分钟后来在云控制台开通端口才正常访问。3.2 实现股票数据采集模块服务骨架搭好后开始实现最关键的数据采集模块。这个模块的作用是调用 akshare 接口拉取指定股票的日线行情并存入 SQLite 数据库。# app/collector.py import akshare as ak import pandas as pd from datetime import datetime def fetch_daily_stock(code600519, adjustqfq): 获取个股日线数据默认前复权 df ak.stock_zh_a_hist( symbolcode, perioddaily, start_date20230101, end_datedatetime.now().strftime(%Y%m%d), adjustadjust ) df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, 成交额: amount, }) return df这段代码的核心是调用stock_zh_a_hist接口把返回的中文列名映射成英文便于后续存储和计算。注意adjust参数表示复权方式qfq是前复权常用在技术指标计算场景。实际运行前建议先手动执行一遍确认当前数据源可用。接着在 main.py 中增加数据入库逻辑。我采用最简单的方式用 pandas 的to_sql直接写入数据库表结构自动创建不需要手工建表。import sqlite3 from app.collector import fetch_daily_stock def save_to_db(code, df): conn sqlite3.connect(data/stock.db) df.to_sql(fstock_{code}, conn, if_existsreplace, indexFalse) conn.close()这里用了if_existsreplace也就是每次拉取都重新替换整张表。对于日线数据这种量级完全没问题也简化了增量更新的复杂度。如果想要增量更新可以通过判断最大日期来过滤新数据这个后面再展开。3.3 指标计算模块数据入库存之后指标计算模块负责加工出对分析有意义的内容。这里以上证指数的 MA5、MA10、MA20 三个均线指标为例实现方式非常直观用 pandas 的rolling方法。# app/indicators.py def calculate_indicators(df): if df.empty: return df df df.sort_values(date) df[ma5] df[close].rolling(window5).mean() df[ma10] df[close].rolling(window10).mean() df[ma20] df[close].rolling(window20).mean() df[pct_change] df[close].pct_change() * 100 return dfrolling(5).mean()意思是取最近 5 个交易日的收盘价平均值这就是 MA5 均线。rolling 窗口值越大曲线越平滑滞后性也越强。短周期均线反映短期趋势长周期均线反映长期趋势当短周期均线从下方穿越长周期均线时往往被视为趋势转强的信号这就是常见的“金叉”原理。指标计算最需要注意的是缺失值问题。前 5 个交易日由于没有足够的数据MA5 会出现 NaN这些空值必须在前端渲染时做过滤否则图表会中断。我在前端处理数据时统一filter(item item.value ! null)避免图中出现断层。3.4 Web API 接口封装数据层和计算模块都就绪后要写 API 接口把数据返回给前端。我这里设计了两个接口一个用于获取股票列表另一个用于获取某只股票的行情和指标数据。# app/api.py from fastapi import APIRouter import sqlite3 import pandas as pd router APIRouter(prefix/api) router.get(/stock/{code}) def get_stock_data(code: str): conn sqlite3.connect(data/stock.db) df pd.read_sql_query(fSELECT * FROM stock_{code} ORDER BY date, conn) conn.close() if df.empty: return {code: code, items: []} return { code: code, items: df.to_dict(orientrecords), }然后在 main.py 中注册路由。这里有一个很关键的安全细节SQLite 的表名是通过 f-string 拼进去的如果用户传入非法字符串可能导致 SQL 注入。虽然是个人项目但暴露到公网仍然建议做一层校验只允许数字和小写字母组合否则直接拒绝请求。import re from app.api import router as api_router app.include_router(api_router, prefix/api) def validate_code(code: str): return re.fullmatch(r[0-9]{6}, code) is not None3.5 前端页面与 K 线图渲染前端展示层我选用了 ECharts 的 candlestick 图。页面结构非常简单一个 HTML 文件加一个 JavaScript 文件通过 Ajax 请求获取接口数据再交给 ECharts 渲染。!-- app/static/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 titleOpenStock 数据看板/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script /head body select idstock-select option value600519贵州茅台/option option value000001平安银行/option option value300750宁德时代/option /select div idchart stylewidth:100%;height:600px;/div script src/static/app.js/script /body /html这里的 JS 部分需要注意时间格式转换。ECharts 的 candlestick 数据要求是[open, close, low, high]结构日期作为类目轴数据核心代码是// app/static/app.js async function loadStock(code) { const resp await fetch(/api/stock/${code}); const data await resp.json(); const dates data.items.map(item item.date); const values data.items.map(item [ item.open, item.close, item.low, item.high ]); myChart.setOption({ xAxis: { type: category, data: dates }, series: [{ type: candlestick, data: values }] }); }注意 ECharts 的 candlestick 数据顺序是开盘、收盘、最低、最高这个顺序非常容易搞反。我一开始按开盘、最高、最低、收盘传入图表怎么渲染都不对排查了半天才发现是顺序问题。前端页面调试时建议打开浏览器开发者工具的 Network 面板看 API 请求是否返回正常。如果页面能打开但图表空白基本可以确定是数据格式或接口异常导致的。3.6 整合主流程并验证所有模块写完后把入口 main.py 完整连接起来。最终访问流程是浏览器打开首页选择股票JS 调用项目 APIAPI 读取 SQLite 数据库把原始数值交给指标计算逻辑处理最后返回给前端渲染图表。启动服务后我建议按以下顺序验证先访问根路径确认服务在线再直接访问/api/stock/600519确认接口返回 JSON最后打开首页看图表是否正常。分步验证的好处是能快速定位问题出在采集层、存储层还是展示层不会一锅粥地排查。4. 功能扩展与部署优化4.1 定时更新数据手动拉数据只能满足一时需求作为长期使用的看板定时更新是必不可少的。Linux 下最轻量的方案是使用 cron 定时任务每天收盘后自动执行一次数据同步脚本。# 每天下午 18:00 执行数据更新 0 18 * * 1-5 cd /root/openstock /root/openstock/venv/bin/python -m app.update logs/update.log 21这里的 update 模块是一个简单 Python 脚本遍历配置中的股票列表依次拉取最新行情并存入数据库。定时任务踩坑最多的地方是 cron 环境变量与手动执行不同cron 中的 PATH 可能没有包含 Python 所在路径因此建议在命令中写全 Python 的绝对路径运行时日志写入文件方便排查。需要注意周末和法定节假日股票不开盘数据更新后和前一天没有差异。所以 cron 计划限定在周一至周五执行即可节假日是否处理可以根据自己需求决定不影响系统稳定运行。4.2 自选股与自定义指标最小版本跑通后很快会发现需求慢慢增多。第一个想加的功能大概率是自选股管理不在页面写死股票代码而是通过配置文件维护一个自选股列表在页面上增加下拉选择。我建议在 config.py 中维护一个股票池字典STOCK_POOL { 600519: 贵州茅台, 000001: 平安银行, 300750: 宁德时代, 601318: 中国平安, }后续可以扩展为一个简单的管理接口支持增删自选股再把名字传给前端下拉框。这个功能非常实用因为不同人的关注列表差异很大写死在 HTML 里每次改代码不现实。指标计算方面我的 rolling 均线是最基础的你还可以继续加 MACD、KDJ、RSI 等常见技术指标。原理都不复杂在 indicators.py 中新增函数复用 pandas 的滚动计算方法即可。这里建议把所有指标计算封装成组合函数统一输出到 DataFrame前端拿到后直接渲染避免每个指标单独请求。4.3 生产环境部署注意事项开发环境用uvicorn直接启动完全没问题但作为长期运行的服务建议使用 systemd 管理进程实现开机自启和崩溃恢复。创建一个服务文件/etc/systemd/system/openstock.service[Unit] DescriptionOpenStock Service Afternetwork.target [Service] Userroot WorkingDirectory/root/openstock ExecStart/root/openstock/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 Restartalways RestartSec5 [Install] WantedBymulti-user.target启用服务后使用systemctl enable openstock设置开机自启systemctl start openstock立即启动。这里有一个优化点uvicorn 默认单进程如果访问量增大可以加上--workers 2开启多进程但要确保代码中没有文件锁之类的冲突操作。数据库备份是另一个容易被忽略的问题。SQLite 就是单个文件最简单的备份方式是复制文件。我配置了一个每周备份的 cron 任务备份文件按日期命名保留最近一个月即可避免磁盘被备份文件占满。5. 常见问题与排查技巧实录5.1 数据获取失败或者返回为空这是使用 akshare 最常遇到的问题。表现为日志中出现请求异常或者接口返回的 DataFrame 为空。原因通常是数据源本身切换了接口或者网络不稳定导致请求超时也有可能是触发服务端频率限制。排查时先手动跑一下采集函数看返回是否正常。如果手动执行正常而定时任务失败优先怀疑环境问题如果手动执行也失败则需要查看 akshare 版本更新情况必要时升级库版本。我在代码中加入了三层重试机制每次失败后等待 3 秒再重新请求连续失败三次才报错这样能有效规避偶发性的网络抖动。5.2 数据库文件越来越大日线数据本身量不大但如果后续加入了分钟线数据数据库体量会快速增长。SQLite 文件一旦膨胀就很难自动收缩所以需要定期清理。最简单的方案是保留数据表最近 N 天数据其余清理掉。另外一个容易被忽略的点是SQLite 的写入并发能力有限。定时任务采集数据时如果同时有多个前端查询请求可能出现“database is locked”报错。解决思路是给连接设置超时时间比如sqlite3.connect(data/stock.db, timeout10)或者把写操作集中在非高峰时段。对于个人使用场景这个现象很少出现但扩展用户量时要提前考虑。5.3 前端图表空白但接口正常这个问题的根源几乎都是数据格式处理不对。K 线图要求的数据结构是四个数值的有序数组如果数据库中某个字段为 NULL或者转换后的数组长度不对图表就会默默吞掉错误只显示空白画布。我的排查方法是分两步第一步在浏览器 console 中打印接口返回的原始数据确认 JSON 结构是否完整第二步在渲染前打印 mapped 后的数组确认数据顺序是否符合 ECharts 要求。多数情况下问题出在 candlestick 数据顺序上按照“开盘、收盘、最低、最高”的顺序传入即可你可以把这句话记在项目 README 里。5.4 定时任务没有执行cron 定时任务不执行是一个老生常谈的问题。我自己遇到的情况是脚本手动执行正常加入 crontab 后却毫无反应日志文件也是空的。排查思路分四步首先确认 cron 服务是否在运行systemctl status cron其次确认 crontab 语法是否正确crontab -l查看然后确认脚本是否有执行权限chmod x加上权限最后检查脚本中的路径是否写全尤其不能用相对路径。还有一个隐蔽的坑cron 执行时的环境变量和手动执行时不同如果脚本里用了~或者依赖某些环境变量都会导致失败。最好的做法是在 Python 脚本开头把工作目录切到项目根目录并使用绝对路径。5.5 端口被占用或者无法访问服务启动报错提示端口被占用通常是有其他进程占用了 8000 端口。可以用lsof -i:8000查看占用进程必要时更换端口。无法从外部访问则需要确认两件事防火墙是否放行端口云服务器安全组是否放行端口。这两个环节容易混淆注意区分。部署时还有一个小技巧在 config.py 中把端口和绑定地址做成环境变量这样部署到不同环境时不用改代码。代码仓库中保留默认配置生产环境通过环境变量覆盖能够减少不少麻烦。写在最后的几点体会OpenStock 这个项目虽然不大但它把“采集、存储、计算、展示”这条数据链路完整串了一遍作为练手项目非常合适。搭建完成后的最大价值不是看板本身而是你会对数据系统运行的全过程建立起直观认知以后再接触更复杂的数据架构会轻松很多。最后再分享一个小技巧项目跑通后建议把数据更新情况做成一个简单的日志页面每次同步完成后记录股票数、最新日期、耗时等关键指标。这样定时任务有没有跑成功一目了然不用每次登录服务器查看。我自己就是加了这么个日志功能之后才真正觉得这个系统“活”了起来。需要提醒的是本文演示的 OpenStock 属于技术学习与数据处理项目所有行情数据和指标计算仅用于功能演示不构成任何投资建议。市场有风险参与需谨慎技术工具只是辅助认知的窗口真正做决策还是要建立自己的独立判断。
返回列表