ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Django股票信息查询系统开发实战:数据采集、缓存与可视化

Django股票信息查询系统开发实战:数据采集、缓存与可视化 每年毕业季都会有一批学生来做股票类的毕设选题django加股票信息加新闻聚合这套组合出现的频率一直不低。我手头刚好完成过一个这样的完整项目从数据采集、模型设计到页面展示都跑通了今天把整条技术链路和经验细节整理出来。这篇文章适合两类人看一是正在纠结毕设选题、想找一个技术覆盖全面又不至于太难下手的项目的人二是已经确定了这类题目、需要理清开发思路和答辩重点的人。先给个结论这类系统表面看只是“查股票、看新闻”但拆开之后它其实涵盖了Django开发里最核心的几个环节——数据模型设计、第三方接口对接、定时任务、缓存优化、图表可视化。做完一个完整项目基本等于把Django的主流开发路径走了一遍这也是它被大量选作毕设题目的根本原因。1. 一个典型的股票信息查询系统到底在查什么很多同学拿到这个题目之后第一反应是去搜“股票数据接口”然后被各种返回值搞得一头雾水。我的建议是反过来先别碰接口把系统功能拆清楚弄清楚每一块功能背后对应什么数据、什么页面、什么交互再去想数据怎么拿。1.1 系统功能拆解这类系统往细了说核心功能其实只有四大块。第一块是股票基本信息查询。输入股票代码或名称能看到这只股票的基础资料包括当前价格、涨跌幅、成交量、市盈率、所属行业、上市市场这些。这是整个系统的入口也是搜索功能的主体。第二块是历史行情数据。能看到某只股票最近一段时间比如近60个交易日的日K线数据通常包括开盘价、最高价、最低价、收盘价、成交量、成交额。这是可视化图表的原料也是数据模型里最核心的表。第三块是个股新闻聚合。把和某只股票相关的新闻、公告集中展示在股票详情页按发布时间倒序排列点击可以跳转到新闻来源。这块功能在技术上和股票行情完全独立但业务上关联性强能显著提升系统的“完整感”。第四块是用户侧的自选股功能。注册用户可以添加关注、移除股票系统提供独立的自选列表页面。这块是Django自带用户认证系统最自然的落地场景也是答辩时展示“完整业务闭环”的关键功能。1.2 毕设选题为什么偏爱这类系统毕设选题有个隐性要求难度要适中技术覆盖面要够广但开发量不能大到一个人做不完。股票信息查询系统恰好踩中了这个平衡点。数据获取路径清晰。公开接口很多不需要自己造数据也不用担心“没有数据源”这种致命问题。从技术角度看增删改查、列表详情、搜索分页、关联查询这些基本功全部能覆盖从展示效果看图表化呈现非常直观答辩时演示效果远好于普通的学生管理系统、图书管理系统。不过这个项目真正难的地方不在“查”和“看”而在几个容易被忽略的问题上数据从哪来、多久更新一次、接口挂了怎么办、数据量大之后页面还能不能撑住。这几个问题才是拉开档次的地方也是下文展开的重点。2. 为什么是Django选型逻辑与项目架构设计选定方向之后下一个问题是技术栈。这里直接说结论这个题目用Django做后端是最稳妥、性价比最高的选择没有之一。2.1 Django的MTV架构与毕设项目的天然契合Django在这个项目里最核心的价值是它的“全家桶”特性。毕设项目最忌讳技术栈过于分散结果东拼西凑无法完整运行。Django自带ORM、自带Admin后台、自带用户认证、自带模板引擎这四样东西刚好把股票系统的骨架全部覆盖。先看ORM。股票系统涉及两张核心表——股票基础信息表和历史行情表外加新闻表、自选股关系表。用Django ORM定义模型之后迁移、建表、增删改查一套流程非常顺外键关联查询比如查某只股票的所有新闻只需要一行filter搞定完全不需要手写SQL。再看Admin后台。绝大多数管理系统需要自己做一个后台管理页面但在Django里这几乎是零成本。注册一下模型后台就能直接管理股票列表、维护新闻数据。演示的时候打开Admin后台评委能看到一个完整的管理界面这在答辩时非常加分。用户认证模块也一样注册、登录、登出、session管理全部内置。自选股功能只需要建一个User和Stock的多对多关系表通过request.user判断当前登录状态然后做增删操作就行。2.2 从manage.py startproject开始的工程结构设计工程结构上我建议按功能模块拆分应用而不是把所有逻辑塞进一个app里。一个可参考的结构是这样的stock_news_system/ ├── manage.py ├── config/ # 项目配置原项目根目录 │ ├── settings/ │ │ ├── base.py # 公共配置 │ │ ├── dev.py # 开发环境配置 │ │ └── prod.py # 生产环境配置 │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── stocks/ # 股票信息模块 │ │ ├── models.py # 股票模型、行情模型 │ │ ├── views.py # 列表、详情、K线数据接口 │ │ ├── urls.py │ │ └── services.py # 数据抓取服务 │ ├── news/ # 新闻模块 │ │ ├── models.py │ │ ├── views.py │ │ └── services.py # 新闻抓取服务 │ └── users/ # 用户模块自选股 │ ├── models.py │ └── views.py ├── templates/ ├── static/ ├── requirements.txt └── .env # 环境变量把配置拆成base.py、dev.py、prod.py三份在毕设里看起来像是“过度设计”但实际收益很大。开发的时候用SQLite部署前切MySQL只需要改DATABASES配置DEBUG开关、SECRET_KEY、API Token这类敏感项统一放.env里不会写死在代码中。2.3 配置上的几个关键选择数据库选型方面开发期建议直接用SQLite零配置、随开随用。部署前换成MySQL在Django里只是改一下DATABASES配置ORM代码完全不用动。唯一要注意的是装好PyMySQL然后在项目__init__.py里加一段兼容配置。定时任务方面如果只是每天收盘后更新一次股票数据和新闻用django-crontab就够了轻量、配置简单。如果后续想加实时提醒、定时推送这类功能再上Celery也不迟。毕设项目用django-crontab是个很务实的选型。模板和静态文件方面前端采用Django模板加原生JS加ECharts的组合即可不需要引入Node.js构建流程降低整个项目的复杂度。3. 股票数据从哪来数据获取与存储模型设计股票数据是这类系统的心脏数据源选型直接决定开发效率和数据质量。3.1 数据源选型的对比与取舍我实际对比了四个主流方案各有优劣没有完美的选择只能按需组合。数据源类型费用优点缺点aksharePython库免费接口丰富、无需注册、文档齐全依赖上游接口稳定偶尔失效TusharePython库/API积分制数据规范、稳定、社区成熟高权限接口需要积分门槛新浪财经接口HTTP免费实时性高直接返回JSON无官方文档、参数靠抓包东方财富接口HTTP免费数据全新闻和行情都有接口变更频繁需要定期维护我最终的方案是组合使用实时行情走腾讯财经的HTTP接口历史K线走akshare新闻走东方财富的接口。这样组合的好处是实时性、历史数据完整性、新闻覆盖度三方面都有保障而且每个数据源只需要了解一种获取方式学习成本可控。3.2 核心表结构设计从需求到字段数据模型我用四张表解决股票基础信息表、历史行情表、新闻表、自选股关系表。股票基础信息表class Stock(models.Model): code models.CharField(股票代码, max_length10, uniqueTrue) name models.CharField(股票名称, max_length50) industry models.CharField(所属行业, max_length50, blankTrue) market models.CharField(市场, max_length10, choices( (SH, 上海), (SZ, 深圳), (BJ, 北京), )) is_active models.BooleanField(是否上市, defaultTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return f{self.code} {self.name}历史行情表class StockPrice(models.Model): stock models.ForeignKey(Stock, on_deletemodels.CASCADE, related_nameprices) date models.DateField(交易日期) open models.DecimalField(开盘价, max_digits10, decimal_places2) high models.DecimalField(最高价, max_digits10, decimal_places2) low models.DecimalField(最低价, max_digits10, decimal_places2) close models.DecimalField(收盘价, max_digits10, decimal_places2) volume models.BigIntegerField(成交量, default0) class Meta: unique_together (stock, date) ordering [date]这里有几个字段设计细节值得强调。价格字段必须用DecimalField不要用FloatField浮点数在金融数据里会有精度问题虽然毕设演示看不出差别但答辩时被追问会很难受。成交量用BigIntegerField因为日成交量可能超过32位int的范围。历史行情表要加(stock, date)联合唯一约束这是防止定时任务重复插入数据的兜底方案比在代码里写一堆判断逻辑可靠得多。3.3 定时任务与数据更新的执行策略数据更新的核心原则是增量更新不要每次全量拉取。股票列表的更新频率不高可以一周拉一次把代码、名称、行业这些基础信息同步过来。历史行情每天收盘后更新一次只拉取最近一个交易日的数据插入前先检查是否已存在。新闻建议每小时更新一次或者每天早上开盘前集中拉取一次具体看答辩演示的需求。定时任务的实现我用的是django-crontab# 每天 15:30 更新行情数据 30 15 * * * cd /path/to/project /usr/bin/python3 manage.py update_stock_prices # 每 2 小时更新一次新闻 0 */2 * * * cd /path/to/project /usr/bin/python3 manage.py fetch_news命令用Django自定义management command实现因为要复用项目里的ORM、配置和数据源工具比写独立脚本方便得多。另一个实用经验是抓取任务串行执行每次请求间隔1到2秒。免费接口普遍有限流高频请求容易被封IP。我的方案是数据量不大时单线程顺序抓取加上time.sleep(1)既稳定又够用。4. 股票新闻聚合接口选型与缓存策略新闻模块是这个系统里最容易做成“摆设”的部分——建一张表、爬一次数据、然后放着不管。但新闻恰恰是最能体现“实时性”设计思想的地方做得好会让系统整体观感提升一个档次。4.1 新闻数据源的设计取舍新闻来源我推荐东方财富的个股新闻接口它支持按股票代码查询相关新闻返回的数据包括标题、摘要、发布时间、来源。直接走HTTP请求用requests库几行代码就能拿到结构化数据不需要引入Scrapy这类重型爬虫框架。import requests import json def fetch_stock_news(stock_code, page_size20): url https://np-listapi.eastmoney.com/comm/web/getNewsByColumns params { client: web, code: stock_code, pageSize: page_size, type: 1, } headers {User-Agent: Mozilla/5.0} resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json() # 解析结果返回统一格式的字典列表 news_list [] for item in data.get(data, {}).get(list, []): news_list.append({ title: item.get(title), source: item.get(source), publish_time: item.get(showTime), url: item.get(url), summary: item.get(summary), }) return news_list这种实现方式的好处是核心逻辑只有几十行代码加上容错处理也不会超过一百行。坏处是接口没有官方文档可能变更。所以抓取代码里一定要加异常处理接口失效时不至于让整个定时任务崩溃最多就是当天新闻不更新系统其他功能不受影响。4.2 新闻抓取任务与数据库模型的匹配新闻表的模型设计关键在去重策略上class News(models.Model): stock models.ForeignKey(Stock, on_deletemodels.CASCADE, related_namenews) title models.CharField(新闻标题, max_length255) summary models.TextField(摘要, blankTrue) source models.CharField(来源, max_length50, blankTrue) url models.URLField(原文链接, uniqueTrue) published_at models.DateTimeField(发布时间) created_at models.DateTimeField(抓取时间, auto_now_addTrue) class Meta: ordering [-published_at]去重是整个新闻抓取最核心的问题。定时任务每次抓取都可能重复拉到同一条新闻最简单的方案是在url字段上加uniqueTrue插入时用get_or_create忽略冲突记录。如果URL也不可靠有些新闻源URL会带时间戳参数可以用标题的MD5值做唯一键两种方案都在实际项目中验证过。关联方式上用外键关联股票这样查询“某只股票的所有新闻”只需要stock.news.all()Django ORM的反向关联查询非常自然新闻列表页的模板渲染也因此变得很简洁。4.3 缓存策略让页面响应时间从2秒降到200毫秒缓存是整个系统性能优化的关键。个股详情页要展示近60天K线数据和最新50条新闻如果不做缓存每次请求都要查多张表响应时间轻松超过1秒。加上缓存之后响应时间能压到200毫秒以内体验是完全不同的。我用的策略是分层缓存。第一层是Django框架自带的视图缓存对新闻列表这种更新频率不高、读取频繁的页面直接缓存整个HTTP响应from django.views.decorators.cache import cache_page cache_page(60 * 5) # 缓存5分钟 def stock_news(request, code): news_list News.objects.filter(stock__codecode)[:50] return render(request, news/list.html, {news_list: news_list})第二层是数据层缓存对K线数据这种确定性很高的查询结果缓存到Redis前端通过AJAX请求时直接命中缓存不再查数据库from django.core.cache import cache def kline_data(request, code): cache_key fkline:{code}:60 cached cache.get(cache_key) if cached: return JsonResponse(cached) prices StockPrice.objects.filter(stock__codecode).order_by(date)[:60] data [[p.date.strftime(%Y-%m-%d), float(p.open), float(p.close), float(p.low), float(p.high), int(p.volume)] for p in prices] result {code: code, data: data} cache.set(cache_key, result, timeout60 * 30) # 缓存30分钟 return JsonResponse(result)缓存时间按数据更新频率来定K线数据缓存到下一次收盘前新闻缓存5到10分钟股票列表缓存1小时。这样既保证数据不会太旧又能把数据库压力降下来。5. 前端展示与图表联动从数据到可视化后端数据链路打通之后剩下的是把结果呈现出来。很多毕设挂在这一步——功能全有但页面丑、图表不会动演示效果大打折扣。5.1 Django模板渲染还是前后端分离这是这个项目里最需要做取舍的地方。我的建议是Django模板加原生JS最稳妥也是毕设场景里最推荐的方式。前后端分离Django DRF Vue在真实企业项目里是主流但对毕设来说如果对Vue不够熟很容易把项目搞成“两种技术都不精通”的状态。本来写Django模板只需要几天换成前后端分离之后接口设计、跨域处理、前端工程化这些问题全冒出来时间成本翻倍。当然如果对前后端分离已经很熟练用DRF提供接口、Vue做前端技术上确实更亮眼前提是时间和能力都够。这时Django后端只要写好API接口返回JSON前端负责渲染和交互项目结构更贴近真实企业开发。5.2 用ECharts把行情数据画成图表图表可视化用ECharts这个没有悬念。它对K线图的支持非常完善折线图、柱状图、蜡烛图都是开箱即用。关键是处理好数据格式。我的做法是后端写一个专门的JSON接口返回K线数据前端拿到后直接setOption不在前端做复杂的数据转换fetch(/api/kline/${stockCode}/) .then(res res.json()) .then(data { const klineChart echarts.init(document.getElementById(kline)); const option { grid: { left: 60, right: 20, top: 40, bottom: 30 }, tooltip: { trigger: axis }, xAxis: { type: category, data: data.data.map(d d[0]) }, yAxis: { scale: true, type: value }, dataZoom: [{ type: inside }, { type: slider }], series: [{ type: candlestick, data: data.data.map(d [d[1], d[2], d[3], d[4]]), itemStyle: { color: #ef232a, // 阳线红色 color0: #14b143, // 阴线绿色 borderColor: #ef232a, borderColor0: #14b143 } }] }; klineChart.setOption(option); });这里注意K线数据的顺序ECharts的candlestick系列要求数据格式是[open, close, lowest, highest]不是常见的[open, high, low, close]。这个坑我踩过一次图表出来完全不对排查了半天才发现是数据顺序问题。后端接口返回时直接按ECharts需要的顺序拼好前端就不容易搞错。除了K线图还可以加一个成交量柱状图和K线图共用x轴通过grid属性上下排列。成交量数据正好在K线数据数组的第5个位置前端直接映射到另一个series就行成本很低但视觉效果好很多。5.3 自选股、搜索、分页这些细节功能怎么设计核心的详情页做完之后要补几个支撑性功能。搜索功能用Django ORM的icontains实现模糊匹配支持股票代码或名称的模糊搜索。输入“600”能查出所有以600开头的股票输入“茅台”也能查到对应记录。搜索页放在首页顶部实时返回结果列表点击跳转详情页。自选股功能依赖用户认证。登录之后在股票详情页显示“添加自选”或“移除自选”按钮状态根据request.user和股票代码实时判断。自选股列表页就是一个简单的表格展示股票代码、名称、最新价格、涨跌幅每行带一个删除按钮。这个页面用Django模板渲染加AJAX删除就能实现不需要单独建前端工程。分页用Django的Paginator默认每页20条。首页的热门股票列表和新闻列表都做分页避免页面无限拉长。分页样式用Bootstrap的分页组件就能做简单且美观。6. 完成毕设之后部署、答辩与几个高频问题项目开发完成只是第一步部署到云服务器、准备答辩、处理突发事件这些环节同样重要甚至更影响最终成绩。6.1 部署到云服务器的完整链路推荐部署方案是云服务器 Linux 宝塔面板 Gunicorn Nginx MySQL。宝塔面板对国内学生非常友好图形化界面操作省去大量命令行配置时间。部署关键步骤就这几步# 1. 安装Python环境和虚拟环境 # 2. 创建虚拟环境并安装依赖 python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # 3. 迁移数据库 python manage.py migrate # 4. 收集静态文件 python manage.py collectstatic # 5. 用Gunicorn启动Django应用 gunicorn config.wsgi:application -b 127.0.0.1:8000然后配置Nginx反向代理把80端口请求转发到8000端口静态文件直接用Nginx托管。这里有个关键细节settings配置里DEBUG必须改成FalseALLOWED_HOSTS要配置成域名或服务器IP。否则页面上的CSS、JS全部加载不出来浏览器控制台会报一堆静态文件404。这个坑几乎每个第一次部署的人都会踩。环境变量管理也很重要。数据库密码、SECRET_KEY、API密钥这些敏感信息不要写死在代码里从.env文件读取。部署时在服务器上创建一份真实的.env开发环境的配置不带到生产环境避免账号泄露。6.2 答辩时评委最关注的几个技术点答辩环节评委通常不会要求你现场改代码但会针对几个技术点追问。我总结了四个高频问题。第一个是数据来源和实时性。需要准确说出用的是哪些接口、数据多长时间更新一次、通过什么机制更新定时任务。回答时强调“增量更新”和“定时任务保证数据新鲜度”这两个点清晰准确。第二个是ORM的使用。评委可能会问外键关联查询怎么写、select_related和prefetch_related的区别、如何避免N1查询。比如新闻列表页循环展示每条新闻时如果不用select_related(stock)每条新闻都会额外执行一次股票表查询数据量大了性能会明显下降。准备答辩时把这个场景讲清楚很加分。第三个是缓存机制。缓存解决什么问题、为什么需要两级缓存、缓存失效时间怎么设计。第四个是数据库设计。字段类型为什么这样选、联合索引加在哪、为什么(stock, date)要加唯一约束。能讲清楚“为什么”比背结论重要得多。6.3 实测中容易翻车的5个隐蔽问题最后分享几个实际开发中遇到的隐蔽问题都是那种“不踩不知道、踩了很浪费时间”的坑。时区问题是第一个。Django默认开启USE_TZTrue而股票数据是中国时间。存数据库时如果不注意时区转换查出来的时间会差8个小时。处理方式是在写入数据前统一用timezone.make_aware转换成本地时间读取时用模板过滤器格式化输出。数据库连接超时是第二个。MySQL默认的wait_timeout是8小时如果定时任务长时间不执行连接池里的连接会失效下次访问时报“MySQL server has gone away”。解决方式是在Django的数据库配置里加上CONN_MAX_AGE参数或者在每次定时任务执行前重新建立连接。测试数据与真实数据混用是第三个。如果开发时用真实账号测试自选股功能部署到服务器后要把测试数据清掉否则答辩演示时打开自选股列表是空的或者出现本地测试的数据。这种事一旦发生对评分影响很大。接口返回格式变化是第四个。第三方接口没有文档今天返回的字段明天可能就变了。我的处理是写一个数据解析函数用.get()方法取字段取不到就给默认值保证即使字段缺失也不会让整个任务崩溃。静态文件缓存是第五个。改完CSS或JS后浏览器可能加载的还是旧文件。解决方式是在模板里给静态文件URL加版本参数比如style.css?v20250601强制浏览器重新加载。最后再分享一点实际体会做这类项目最容易陷入的误区是“一开始就追求完美”。数据源要选最稳定的、图表要做得最炫、功能要全部覆盖结果项目拖到截止日期还没完成。我个人的经验是第一版先把主流程跑通——股票列表能打开、详情页有数据、新闻能显示这个最小闭环先完成。然后再逐步加缓存、加图表优化、加自选股功能。每加一个功能都有明确的完成标准项目推进节奏就完全可控。这个思路放在毕设项目上非常实用希望正在做这个题目的同学能少走点弯路。
返回列表