简介该项目是一款基于Python语言的新闻资讯平台完整源码面向Web开发学习者、全栈开发者及需要快速搭建新闻发布系统的个人或团队。平台综合运用HTML、CSS、JavaScript与Python涵盖前端页面布局、动态交互、后端逻辑处理与数据管理具备新闻编辑、发布、展示等一站式功能。压缩包共295个文件包含125个JavaScript文件用于交互功能、24个CSS文件定义视觉风格、22个HTML文件搭建页面骨架、25个Python脚本处理核心逻辑另有64个图像文件、7个字体文件及配置文件、说明文档等包体大小5.53MB。目前已有252人学习下载。通过该项目可系统掌握Python全栈开发流程理解数据库配置、用户认证、前端展示到后端处理的完整链路适合作为课程设计、毕业设计或技术进阶的参考实践。1. 基于Python语言的新闻资讯平台设计源码这套代码到底值不值得自己搭如果你在搜“基于Python语言的新闻资讯平台设计源码”大概率不是想找一个几百行的爬虫脚本而是想要一套能撑起内容发布、列表展示、搜索和评论闭环的工程骨架。这类源码在GitHub、码云和各类Python源码站里不少但质量差距极大有的用Flask写了三个路由就号称平台有的Django项目结构完整但文档为零。我的建议很直接——用Django做新闻资讯平台是目前从业者最稳的路线因为后台管理、ORM、分页、认证这些新闻站刚需它都内置了。这篇文章我按自己做过的方案把这套源码从数据模型到部署验证拆开讲新手的每一步都能照做熟手可以直接跳过废话看避坑那一章。2. 选型先立住为什么新闻资讯平台用Django而不是Flask或Node2.1 新闻资讯场景对框架的三个硬要求新闻资讯平台不是那种“能跑就行”的内部工具它有三个绕不开的硬要求。第一个是内容管理效率编辑要能发稿、改稿、下稿你不能让运营同事去数据库里敲SQL第二个是列表页和详情页的访问压力新闻站的高峰期流量是脉冲式的框架至少要能撑住千级并发第三个是业务扩展空间今天做新闻列表明天可能加专题、加评论审核、加推送框架的生态和插件体系得兜得住。Django恰好卡在这三个点上。自带的Admin后台直接解决内容录入问题ORM加迁移机制让数据模型能跟着业务迭代走第三方包几乎覆盖了缓存、搜索、任务队列所有周边需求。有人会提FlaskFlask确实轻新闻站这种功能模块固定的项目用Flask意味着路由、ORM、模板、表单、认证全都自己拼拼到后面你会发现维护成本比节约的那点启动时间高得多。至于Node的Express或Nest前后端分离的新闻站确实有人用但如果你手里的源码定位是“设计源码”Python方向明显更主流文档和案例都更容易找。2.2 一套合格的“设计源码”应该长什么样你在Free Python源码大全或GitHub上搜到的新闻资讯平台源码很多只是把能跑通但目录乱成一团的脚本打包了。我的从业判断是一套值得拿来改的源码至少要有三层结构。第一层是工程化目录按Django的标准把项目配置、应用模块、静态资源、模板分开第二层是数据模型新闻、分类、评论这些核心表要独立建模而不是塞进一个json文件或SQLite临时表第三层是前后端链路从URL路由到视图函数再到模板渲染每一步都能追踪。如果你拿到的源码缺了这三层中的任何一层比如新闻数据直接写死在HTML里或者没有分页逻辑那这套源码改起来比重新写还累。反过来结构化清晰的源码修改成本很低——换数据库配置、加一个新的新闻字段、调整列表页排序规则都是半小时以内的事。这也是为什么我强烈建议优先看Django写的新闻资讯平台源码因为Django的App机制天然逼着作者按模块组织代码哪怕作者水平一般大框架也不会歪。3. 按字段把新闻平台的数据模型立起来MySQL建表与ORM映射3.1 新闻平台最少需要哪几张表做新闻资讯平台很多人一上来就设计十张表结果一半是冗余。我做过几个内容站之后总结下来第一版最少只需要四张表新闻表、分类表、评论表、用户表。用户表直接用Django自带的auth.User扩展不用自己写。新闻表承载标题、摘要、正文、封面图、浏览量这些核心字段分类表做一级分类就够如果做多级分类可以加自关联外键评论表挂在新闻表下同时记录评论人。有一点要注意浏览量这种高频更新字段和正文放在同一张表在写库压力大时会有锁竞争。但第一版不必过早优化先把功能跑通更重要。真正要提前想清楚的是状态字段——新闻的“草稿/已发布/下线”状态必须建模否则后台操作人员没法控制内容上架下架。Django的IntegerField加choices或者用CharField存字符串状态都可以我倾向于用SmallIntegerField加数字常量查库排序都快一点。3.2 models.py实现Django ORM里那些容易忽略的参数下面是一份我常用的新闻资讯平台核心模型代码直接贴到Django应用的models.py里就能用。需要说明的是我这里以Django 4.2 LTS为例Python版本建议用3.8以上3.10最稳。from django.db import models from django.contrib.auth.models import User from django.utils import timezone class Category(models.Model): name models.CharField(max_length50, uniqueTrue, verbose_name分类名) slug models.SlugField(max_length60, uniqueTrue, verbose_nameURL标识) sort_order models.IntegerField(default0, verbose_name排序权重) class Meta: ordering [sort_order, id] verbose_name 新闻分类 def __str__(self): return self.name class News(models.Model): STATUS_DRAFT 0 STATUS_PUBLISHED 1 STATUS_OFFLINE 2 STATUS_CHOICES [ (STATUS_DRAFT, 草稿), (STATUS_PUBLISHED, 已发布), (STATUS_OFFLINE, 已下线), ] title models.CharField(max_length200, verbose_name标题) summary models.TextField(max_length500, blankTrue, verbose_name摘要) content models.TextField(verbose_name正文) cover_image models.ImageField(upload_tonews/covers/%Y/%m/, blankTrue, nullTrue, verbose_name封面图) category models.ForeignKey(Category, on_deletemodels.PROTECT, related_namenews_set, verbose_name分类) author models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, related_nameauthored_news, verbose_name作者) status models.SmallIntegerField(choicesSTATUS_CHOICES, defaultSTATUS_DRAFT, db_indexTrue, verbose_name状态) views_count models.PositiveIntegerField(default0, verbose_name浏览量) is_top models.BooleanField(defaultFalse, db_indexTrue, verbose_name是否置顶) published_at models.DateTimeField(defaulttimezone.now, db_indexTrue, verbose_name发布时间) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) updated_at models.DateTimeField(auto_nowTrue, verbose_name更新时间) class Meta: ordering [-is_top, -published_at, -id] indexes [ models.Index(fields[status, published_at]), ] verbose_name 新闻 def __str__(self): return self.title class Comment(models.Model): news models.ForeignKey(News, on_deletemodels.CASCADE, related_namecomments, verbose_name所属新闻) user models.ForeignKey(User, on_deletemodels.CASCADE, related_namecomments, verbose_name评论人) content models.TextField(max_length1000, verbose_name评论内容) created_at models.DateTimeField(auto_now_addTrue, db_indexTrue, verbose_name评论时间) is_approved models.BooleanField(defaultFalse, verbose_name是否审核通过) class Meta: ordering [created_at] verbose_name 评论 def __str__(self): return f{self.user.username} 评论了 {self.news.title[:20]}这套模型里几个参数是我写每一个内容站都会反复确认的。on_deletemodels.PROTECT用在新闻的外键上意思是分类底下还有新闻时不允许删除分类这比默认的CASCADE安全得多——否则运营误删一个分类整个分类的新闻全没了连后悔药都没有。作者字段用SET_NULL因为用户注销时账号记录要保留新闻不能跟着删。db_indexTrue加在status和published_at上是因为新闻列表页最频繁的查询就是“按状态过滤并排序”。Meta里的ordering指定了默认排序规则置顶优先、然后按发布时间倒序这样视图层写起来不用每次重复排序逻辑。评论的is_approved字段建议保留新闻网站的评论审核不是可选项是内容安全的底线。3.3 从模型到MySQL库表迁移命令与常见报错模型写完之后要把Python类映射成MySQL里的真实表。这里有个顺序问题先建数据库再迁移否则Django会报Unknown database。我一般在MySQL里手动建库字符集指定utf8mb4否则后面存emoji或生僻字会翻车。# 1. 进入MySQL创建数据库注意字符集必须是 utf8mb4 mysql -uroot -p -e CREATE DATABASE news_platform CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 2. 在 Django 项目的 settings.py 里配置数据库连接 # DATABASES { # default: { # ENGINE: django.db.backends.mysql, # NAME: news_platform, # USER: news_user, # PASSWORD: your_password, # HOST: 127.0.0.1, # PORT: 3306, # OPTIONS: {charset: utf8mb4}, # } # } # 3. 生成迁移文件并执行迁移 python manage.py makemigrations news python manage.py migrate第三步里面有个细节makemigrations后面跟了应用名news这是只对这个应用生成迁移。如果项目里有多个应用不带应用名会扫描全部应用产出大量无关迁移不利于后续排查。执行migrate之后用SHOW CREATE TABLE news_news\G看一眼建表语句确认utf8mb4生效。如果迁移时报django.db.utils.OperationalError: (1064, You have an error in your SQL syntax)九成是MySQL版本太老Django 4.2要求MySQL 8.0以上MySQL 5.7有些新特性不支持。另一个坑是没装mysqlclient或pymysqlpip安装时报错就换用pip install mysqlclient它在多数Linux发行版上都有预编译包。4. 把新闻流程走通列表、搜索、详情与评论的代码链路4.1 列表页与分页Django内置分页器的正确打开方式新闻列表页是流量入口核心逻辑就两件事按状态和分类过滤数据再做分页。Django的Paginator是现成的但很多人用错了地方——在视图里把所有数据查出来再分页数据量一大就卡死。正确写法是用ORM的filter先缩小数据集再交给分页器。from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.shortcuts import render, get_object_or_404 from django.views.decorators.http import require_GET from .models import News, Comment require_GET def news_list(request): # 先过滤出已发布状态 queryset News.objects.filter(statusNews.STATUS_PUBLISHED) # 支持分类筛选URL形如 /news/?categorytech category_id request.GET.get(category) if category_id: queryset queryset.filter(category_idcategory_id) # 每页 12 条 paginator Paginator(queryset, 12) page_num request.GET.get(page, 1) try: page_obj paginator.page(page_num) except PageNotAnInteger: page_obj paginator.page(1) except EmptyPage: page_obj paginator.page(paginator.num_pages) return render(request, news/list.html, {page_obj: page_obj})这段视图里有两个值得记住的点。queryset是惰性的filter只是构造查询条件真正执行SQL发生在Paginator(queryset, 12)触发的count查询和分页后的切片查询上。这也是为什么必须先把status过滤掉再分页——如果不过滤Django会先SELECT COUNT(*)统计全表再取12条两张表的全表扫描直接打满数据库。EmptyPage异常处理是新闻列表最容易忽略的边界情况。用户在地址栏手动把page改成9999或者后台删了数据导致页码漂移不处理就会返回500。我的习惯是越界时回退到最后一页用户体验比报错好得多。4.2 搜索功能标题加正文的icontains方案够不够用新闻站的搜索需求一般分两档。第一档是标题和摘要的关键词过滤用ORM的icontains就够了第二档是全文检索需要引入Elasticsearch或PostgreSQL的全文索引。第一版源码我建议先做icontains方案逻辑简单、代码量少数据量在十万条以内响应速度还能接受。require_GET def news_search(request): keyword request.GET.get(q, ).strip() queryset News.objects.filter(statusNews.STATUS_PUBLISHED) if keyword: # icontains 表示不区分大小写的模糊匹配 queryset queryset.filter( models.Q(title__icontainskeyword) | models.Q(summary__icontainskeyword) | models.Q(content__icontainskeyword) ) paginator Paginator(queryset, 10) page_num request.GET.get(page, 1) try: page_obj paginator.page(page_num) except (PageNotAnInteger, EmptyPage): page_obj paginator.page(1) return render(request, news/search.html, { page_obj: page_obj, keyword: keyword, })Q对象是这里的关键三个icontains条件用|连接翻译成SQL就是WHERE title LIKE %keyword% OR summary LIKE %keyword% OR content LIKE %keyword%。要注意的是icontains在MySQL里对应LIKE是用不了索引的数据量上来后会全表扫描。到那时候再换全文检索但第一版千万不要过度设计搜索功能先能用、结果准确比什么都重要。有个细节关键词一定要先strip()去掉首尾空格否则用户多敲一个空格搜索结果变成空列表看起来就像系统出了bug。另外搜索页建议把keyword回传到模板里让搜索框保留用户输入的内容这个小体验很多源码都没做。4.3 详情页与评论提交一次完整的POST请求闭环详情页除了展示新闻内容还要处理评论提交。这里最容易踩的坑是把GET请求和POST请求写在同一个视图里用request.method判断代码又臭又难维护。我建议用Django的View类或者干脆写两个独立的视图函数——一个渲染详情一个处理评论。from django.views.decorators.http import require_POST from django.contrib.auth.decorators import login_required from django.shortcuts import redirect from django.contrib import messages def news_detail(request, news_id): # 获取已发布的新闻避免草稿被前端猜出URL直接访问 news get_object_or_404( News, idnews_id, statusNews.STATUS_PUBLISHED ) # 获取审核通过的评论 comments news.comments.filter(is_approvedTrue)[:50] # 浏览量自增用F表达式避免竞态条件 News.objects.filter(idnews_id).update(views_countmodels.F(views_count) 1) return render(request, news/detail.html, { news: news, comments: comments, }) require_POST login_required def comment_submit(request, news_id): news get_object_or_404(News, idnews_id, statusNews.STATUS_PUBLISHED) content request.POST.get(content, ).strip() if not content: messages.error(request, 评论内容不能为空) return redirect(news_detail, news_idnews_id) if len(content) 1000: messages.error(request, 评论内容不能超过1000字) return redirect(news_detail, news_idnews_id) # 默认不通过审核后台人工审核后展示 Comment.objects.create( newsnews, userrequest.user, contentcontent, is_approvedFalse, ) messages.success(request, 评论已提交审核通过后展示) return redirect(news_detail, news_idnews_id)这里有个我踩过坑的细节浏览量自增不能用news.views_count 1; news.save()因为两步操作之间如果有并发请求后一个请求读到的还是旧值最终结果会少计。用F(views_count) 1直接在数据库层面自增原子操作多线程下不会丢数据。评论默认不审核通过这个策略要顶住运营的压力——很多产品经理会要求评论即时展示但新闻网站的监管风险担不起。评论先落库、后台审核、再展示是一条安全线。4.4 Admin后台定制让运营同事不用碰代码就能发新闻Django自带的Admin是新闻资讯平台源码最大的卖点之一但默认的Admin界面太简陋要定制之后运营才好用。我在admin.py里一般注册模型并指定列表显示字段和筛选器from django.contrib import admin from .models import Category, News, Comment admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display [name, slug, sort_order] prepopulated_fields {slug: [name]} admin.register(News) class NewsAdmin(admin.ModelAdmin): list_display [title, category, author, status, published_at, views_count, is_top] list_filter [status, category, is_top] search_fields [title, summary] date_hierarchy published_at actions [make_published, make_offline] admin.action(description设为已发布) def make_published(self, request, queryset): queryset.update(statusNews.STATUS_PUBLISHED) admin.action(description设为下线) def make_offline(self, request, queryset): queryset.update(statusNews.STATUS_OFFLINE) admin.register(Comment) class CommentAdmin(admin.ModelAdmin): list_display [news, user, content_short, is_approved, created_at] list_filter [is_approved] actions [approve_comments] admin.action(description审核通过所选评论) def approve_comments(self, request, queryset): queryset.update(is_approvedTrue) def content_short(self, obj): return obj.content[:30] ... if len(obj.content) 30 else obj.content content_short.short_description 评论内容prepopulated_fields很实用在后台输入分类名时自动填slug不用手动敲URL标识。date_hierarchy按发布时间生成日期层级筛选运营找某个时间段发过的稿子非常方便。自定义action批量发布和下架比一条条编辑高效得多。后台定制这部分是很多免费Python源码大全里的项目最薄弱的环节——代码能跑但运营没法用。新闻资讯平台终究是给编辑和运营用的工具Admin体验直接影响内容产出效率这块值得花时间打磨。5. 源码跑起来之后5个高频翻车点与排查顺序5.1 静态文件404CSS全丢了页面裸奔现象后台和前端页面能打开但所有CSS、JS、图片都加载不出来浏览器控制台一堆404。原因Django开发服务器默认不主动服务静态文件你需要在settings.py里配STATIC_URL和STATICFILES_DIRS然后在主路由的urls.py里加一段静态文件映射。很多从免费源码站下载的项目作者本地跑通了但没把这步写进README。解决在settings.py中确认配置开发环境用django.contrib.staticfiles的runserver自动处理生产环境则用python manage.py collectstatic把静态文件集中后交给Nginx。5.2 时区问题新闻发布时间显示“8小时前”或跑到未来现象后台发布的新闻前端显示的发布时间比本地时间慢了8小时或者排序时“明天的新闻”排在前面。原因Django的TIME_ZONE默认是UTC而中国是UTC8。如果你用timezone.now()作为发布时间默认值存进数据库的是UTC时间模板渲染时又没做时区转换显示就会错乱。解决在settings.py里设置TIME_ZONE Asia/Shanghai和USE_TZ True。注意USE_TZ不要改成False否则Django的DateTimeField行为会退化未来升级麻烦。模板中渲染时间时用Django内置的localtime过滤器。5.3 MySQL报错“Specified key was too long”现象执行migrate时报1071 - Specified key was too long; max key length is 3072 bytes。原因utf8mb4字符集下每个字符占4字节如果字段长度定义过大又加了索引索引字节数会超出MySQL限制。比如CharField(max_length200)加uniqueTrue换算成字节就是800字节多个这类字段组合索引很容易超限。解决给CharField适当缩减max_length或者把索引字段改为前缀索引。Django里可以用models.Index(fields[title], nametitle_idx)然后手工迁移时指定前缀长度。最简单的做法是避免在超长字符串字段上加唯一约束。5.4 评论提交后详情页报CSRF现象前端表单提交评论时提示CSRF verification failed请求被中断。原因Django默认开启CSRF中间件所有POST请求的模板表单里必须包含{% csrf_token %}。很多源码模板里漏写这个标签。解决在templates/news/detail.html的form标签内第一行加上{% csrf_token %}。如果你写的是前后端分离接口那就需要在视图上加csrf_exempt或者在请求头带CSRF Token但新闻资讯平台第一版通常是模板渲染直接加模板标签最省事。5.5 分页链接点击后丢失查询参数现象列表页筛选了分类点击第2页之后分类筛选失效又变回全部分类。原因模板里的分页链接只拼了page参数没有带上原来的category或q查询参数。这是我见过最多的源码bug。解决在模板分页组件里保留查询字符串用request.GET.urlencode()或手动拼接。标准做法是a href?{% if request.GET.category %}category{{ request.GET.category }}{% endif %}page{{ page_obj.next_page_number }}或者更通用地在视图里把request.GET的副本加上新的页码再传给模板。6. 从源码到上线最小部署命令与两个值得做的进阶优化新闻资讯平台源码拿到手在本地跑通只是第一步。真正值钱的是让它能在服务器上稳定运行。我一般用Gunicorn加Nginx做部署MySQL装在服务器本机。# 在服务器上创建虚拟环境并安装依赖 python3 -m venv venv source venv/bin/activate pip install -r requirements.txt pip install gunicorn # 收集静态文件 python manage.py collectstatic --noinput # 用 gunicorn 启动4 个 worker 应对常规流量 gunicorn news_platform.wsgi:application \ --bind 0.0.0.0:8000 \ --workers 4 \ --timeout 60 \ --access-logfile logs/access.log \ --error-logfile logs/error.log--workers 4不是拍脑袋定的经验公式是2 * CPU核心数 1一台2核4G的入门云服务器跑4个worker比较合适。--timeout 60很关键如果新闻正文里插入大图或外部资源请求偶尔会超过默认的30秒导致worker被强制杀掉。部署上去之后我看源码值不值得投入主要看两件事。第一件事是全文搜索当新闻量到了五万条以上icontains的LIKE查询响应时间会明显劣化。第二件事是缓存新闻详情页和首页的热点列表用Redis做缓存能扛住一次突发流量。这两个优化做完这套源码就能从“能跑的demo”变成“能上线运营的产品”。最后说点我的个人习惯每次拿到这类Python源码我第一件事不是跑起来而是先读requirements.txt和settings.py把数据库配置、SECRET_KEY、DEBUG开关这三个位置找出来确认一遍。DEBUGTrue直接上生产环境是新闻站最致命的安全漏洞一条错误页就能把代码结构和依赖版本全暴露出去。这个习惯帮我在项目上线前拦下过不少问题也希望帮到你。本文还有配套的精品资源点击获取