Django股票信息查询系统开发实战:数据采集、缓存与可视化
2026/9/9 13:23:02 网站建设 项目流程

每年毕业季都会有一批学生来做股票类的毕设选题,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.pydev.pyprod.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_length=10, unique=True) name = models.CharField('股票名称', max_length=50) industry = models.CharField('所属行业', max_length=50, blank=True) market = models.CharField('市场', max_length=10, choices=( ('SH', '上海'), ('SZ', '深圳'), ('BJ', '北京'), )) is_active = models.BooleanField('是否上市', default=True) created_at = models.DateTimeField('创建时间', auto_now_add=True) def __str__(self): return f'{self.code} {self.name}'

历史行情表:

class StockPrice(models.Model): stock = models.ForeignKey(Stock, on_delete=models.CASCADE, related_name='prices') date = models.DateField('交易日期') open = models.DecimalField('开盘价', max_digits=10, decimal_places=2) high = models.DecimalField('最高价', max_digits=10, decimal_places=2) low = models.DecimalField('最低价', max_digits=10, decimal_places=2) close = models.DecimalField('收盘价', max_digits=10, decimal_places=2) volume = models.BigIntegerField('成交量', default=0) 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_size=20): 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, params=params, headers=headers, timeout=10) 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_delete=models.CASCADE, related_name='news') title = models.CharField('新闻标题', max_length=255) summary = models.TextField('摘要', blank=True) source = models.CharField('来源', max_length=50, blank=True) url = models.URLField('原文链接', unique=True) published_at = models.DateTimeField('发布时间') created_at = models.DateTimeField('抓取时间', auto_now_add=True) class Meta: ordering = ['-published_at']

去重是整个新闻抓取最核心的问题。定时任务每次抓取都可能重复拉到同一条新闻,最简单的方案是在url字段上加unique=True,插入时用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__code=code)[: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 = f'kline:{code}:60' cached = cache.get(cache_key) if cached: return JsonResponse(cached) prices = StockPrice.objects.filter(stock__code=code).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, timeout=60 * 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_relatedprefetch_related的区别、如何避免N+1查询。比如新闻列表页循环展示每条新闻时,如果不用select_related('stock'),每条新闻都会额外执行一次股票表查询,数据量大了性能会明显下降。准备答辩时把这个场景讲清楚,很加分。

第三个是缓存机制。缓存解决什么问题、为什么需要两级缓存、缓存失效时间怎么设计。

第四个是数据库设计。字段类型为什么这样选、联合索引加在哪、为什么(stock, date)要加唯一约束。能讲清楚“为什么”,比背结论重要得多。

6.3 实测中容易翻车的5个隐蔽问题

最后分享几个实际开发中遇到的隐蔽问题,都是那种“不踩不知道、踩了很浪费时间”的坑。

时区问题是第一个。Django默认开启USE_TZ=True,而股票数据是中国时间。存数据库时如果不注意时区转换,查出来的时间会差8个小时。处理方式是在写入数据前统一用timezone.make_aware转换成本地时间,读取时用模板过滤器格式化输出。

数据库连接超时是第二个。MySQL默认的wait_timeout是8小时,如果定时任务长时间不执行,连接池里的连接会失效,下次访问时报“MySQL server has gone away”。解决方式是在Django的数据库配置里加上CONN_MAX_AGE参数,或者在每次定时任务执行前重新建立连接。

测试数据与真实数据混用是第三个。如果开发时用真实账号测试自选股功能,部署到服务器后要把测试数据清掉,否则答辩演示时打开自选股列表是空的,或者出现本地测试的数据。这种事一旦发生,对评分影响很大。

接口返回格式变化是第四个。第三方接口没有文档,今天返回的字段明天可能就变了。我的处理是写一个数据解析函数,用.get()方法取字段,取不到就给默认值,保证即使字段缺失也不会让整个任务崩溃。

静态文件缓存是第五个。改完CSS或JS后,浏览器可能加载的还是旧文件。解决方式是在模板里给静态文件URL加版本参数,比如style.css?v=20250601,强制浏览器重新加载。

最后再分享一点实际体会

做这类项目,最容易陷入的误区是“一开始就追求完美”。数据源要选最稳定的、图表要做得最炫、功能要全部覆盖,结果项目拖到截止日期还没完成。

我个人的经验是:第一版先把主流程跑通——股票列表能打开、详情页有数据、新闻能显示,这个最小闭环先完成。然后再逐步加缓存、加图表优化、加自选股功能。每加一个功能都有明确的完成标准,项目推进节奏就完全可控。这个思路放在毕设项目上非常实用,希望正在做这个题目的同学能少走点弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询