新闻推荐客户端实战:Django后端、Xadmin与Android闭环构建
2026/9/17 5:20:00 网站建设 项目流程

简介:这份资源是一套完整的本科安卓毕业设计项目,主题为基于Android的新闻推荐客户端,配套基于Django+Xadmin实现的Web后台管理端。资源包含全部源代码和文档说明,适合计算机相关专业在校生用于毕设参考、课程设计或项目初期演示,也适合具备一定基础的学习者在此基础上二次开发。压缩包共475个文件,大小约3.04MB,主要涵盖Python后端文件、Android前端JS与HTML页面、CSS样式表及Django框架相关配置文件,前后端代码结构清晰,便于对照学习。目前已有95人浏览学习。代码经过测试运行成功,作者表示可远程教学,下载后如有运行问题可私聊咨询。除源码外,还附有项目说明文档,能帮助读者理解系统架构与实现思路。

1. 选题拆解:毕设标题里的“新闻推荐”到底在考什么

新闻推荐客户端听起来是算法题,大部分毕设却挂在工程上。题目真正要交付的不是一个模型,而是一条能演示的闭环:Django+Xadmin 负责内容管理和后台发布,Android 端负责展示和点击,推荐逻辑把用户行为变成新的排序。答辩评委最关心三个点:推荐数据从哪来、接口怎么设计、换一个用户结果会不会变。只在本地写死推荐列表,或只做一个能刷新闻的 App,都会在追问中露馅。后续按后端、推荐、客户端、文档交付的顺序把这条链路走通,适合要独立完成 Android + Django 闭环的本科生,也适合想快速搭内容分发 Demo 的工程师。先放下算法执念,从内容骨架开始搭。

2. Django+Xadmin 后端:先建内容骨架,再谈推荐

2.1 项目初始化和 app 拆分

按常见的毕设做法,先把 Django 项目拆成内容、行为两个 app,推荐逻辑不要塞在视图里。执行这些命令:

django-admin startproject news_backend cd news_backend python manage.py startapp news python manage.py startapp behavior pip install django xadmin djangorestframework

startproject生成外层目录和同名配置包,startapp生成 app 目录。把news放新闻模型、序列化器和推荐函数,behavior只保存用户行为。Xadmin 是第三方后台组件,不是 Django 默认的 admin,安装后要挂在项目的/xadmin/路径上。

装依赖时最容易出问题的是 Xadmin 与 Django 的兼容性。如果pip install xadmin后启动报错,常见处理是把 Django 回退到 2.x 系列,或者换用源码安装方式。这个细节不能省,很多项目代码写好了,却因为后台起不来被扣分。目录结构我一般保持这样:

news_backend/ ├── manage.py ├── news/ │ ├── models.py │ ├── serializers.py │ ├── views.py │ └── recommender.py ├── behavior/ │ └── models.py └── news_backend/ └── urls.py

recommender.py是新增文件,专门放推荐算法。这样目录本身就是文档,之后写说明时可以直接说清楚每个文件的职责。

2.2 定义新闻与行为的 Model

新闻模型要兼顾列表展示和推荐特征,标题、分类、发布时间是必填项,封面地址留空也能跑通:

# news/models.py from django.db import models class News(models.Model): title = models.CharField(max_length=200, verbose_name="标题") summary = models.TextField(blank=True, verbose_name="摘要") content = models.TextField(verbose_name="正文") category = models.CharField(max_length=50, db_index=True, verbose_name="分类") source = models.CharField(max_length=100, blank=True, verbose_name="来源") cover_url = models.URLField(blank=True, verbose_name="封面地址") is_published = models.BooleanField(default=False, db_index=True, verbose_name="是否发布") published_at = models.DateTimeField(auto_now_add=True, verbose_name="发布时间") def __str__(self): return self.title

db_index=True给分类和时间加索引,推荐接口按分类过滤、按发布时间倒序时不会慢。is_published是发布开关,未发布的文章不会出现在 App 列表里。published_at后面要做“新文章加权”因子。

行为模型负责回收 Android 端的点击和阅读时长。user_id不用外键关联 Django User,直接存 Android 端生成的设备 ID,省掉注册登录模块:

# behavior/models.py from django.db import models class Behavior(models.Model): user_id = models.CharField(max_length=64, db_index=True) news = models.ForeignKey("news.News", on_delete=models.CASCADE) behavior_type = models.CharField(max_length=16) duration = models.IntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True)

外键用字符串引用避免循环 import。created_at用来筛选推荐窗口,duration是推荐评分里最直接的打分源。如果不想做用户系统,设备 ID 是毕设最合理的方案,能区分用户,又不用处理登录和 token 流程。

后台字段整理成表,方便写文档时直接复用:

字段类型用途
news_id外键关联新闻文章
user_idCharField匿名设备标识,不依赖用户表
behavior_typeCharField点击、阅读、点赞、分享
durationIntegerField详情页停留秒数,推荐权重主值
created_atDateTimeField取最近 N 天的行为数据

2.3 接入 Xadmin 并配置列表可操作

news/下新建adminx.py,Xadmin 会自动扫描这个文件名:

# news/adminx.py import xadmin from .models import News class NewsAdmin: list_display = ["title", "category", "source", "is_published", "published_at"] list_filter = ["category", "is_published"] search_fields = ["title", "category"] list_per_page = 20 list_editable = ["is_published"] xadmin.site.register(News, NewsAdmin)

list_editable能在列表页直接切换发布状态,不用点进编辑页,演示“后台发布一篇新闻,App 立刻出现”很方便。list_filter按分类筛选,方便集中构造某几类新闻的初始数据。

别忘了在settings.pyINSTALLED_APPS中追加xadmin及其依赖模块,并注册newsbehavior。不同版本依赖项不同,安装后如果报ModuleNotFoundError,按报错名字补装就可以。接着执行:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver

打开http://127.0.0.1:8000/xadmin/,用刚创建的账号登录。在后台录入 20 篇带分类的新闻,分类越集中,推荐效果越容易看出来。第一次跑通后,再用脚本或管理命令批量导入一些数据,避免答辩现场一条条手敲。

2.4 用 Django REST Framework 暴露列表和详情接口

后台页面是给运营用的,App 需要 JSON 接口。用 DRF 实现只读接口,代码量最少:

# news/serializers.py from rest_framework import serializers from .models import News class NewsSerializer(serializers.ModelSerializer): class Meta: model = News fields = ["id", "title", "summary", "category", "source", "cover_url", "published_at"]

序列化器决定接口返回哪些字段。正文不返回,App 列表页不需要全文,详情页再单独取。views.py里用 ViewSet:

# news/views.py from rest_framework import viewsets from .models import News from .serializers import NewsSerializer class NewsViewSet(viewsets.ReadOnlyModelViewSet): queryset = News.objects.filter(is_published=True).order_by("-published_at") serializer_class = NewsSerializer

ReadOnlyModelViewSet自动提供 list 和 retrieve 两个动作,列表和详情都有了。is_published=True写在 queryset 里,请求时实时过滤。最后注册路由:

# news_backend/urls.py import xadmin from django.urls import path, include from rest_framework.routers import DefaultRouter from news.views import NewsViewSet router = DefaultRouter() router.register("api/news", NewsViewSet, basename="news") urlpatterns = [ path("xadmin/", xadmin.site.urls), path("", include(router.urls)), ]

DefaultRouter会自动生成api/news/api/news/{id}/。Android 模拟器访问宿主机时,接口地址写http://10.0.2.2:8000而不是 localhost,否则请求会打到模拟器自己身上。到这里,后台能发文章,接口能取文章,下一步该补推荐排序。

3. 推荐逻辑:在 Django 中实现新闻推荐排序

3.1 为什么先做基于内容的标签加权

新闻场景有两个特点:用户行为数据量不够,文章更新快。协同过滤需要“用户-文章”交互矩阵,本科毕设里通常只有几百条行为,矩阵非常稀疏,算出来的相似度没有说服力。基于内容的推荐只看文章属性和用户历史分类,新文章没有阅读量也能推出去。答辩时你只需要说清楚“用分类和来源作为内容特征,用历史点击和阅读时长作为权重”,评委一般不会苛求调参结果。

我一般把推荐拆成三层:召回、去重、排序。召回取最近 7 天有行为的分类;去重过滤掉已经看过的文章;排序用行为权重和发布时间加权。初版不需要上深度学习,逻辑清晰比准确率重要,也更容易写进论文的实验章节。

3.2 在 Django 里构建用户兴趣画像

推荐逻辑放在news/recommender.py,先从behavior表聚合出每个分类的加权得分:

# news/recommender.py from datetime import timedelta from django.db.models import Sum, Q from django.utils import timezone from behavior.models import Behavior from news.models import News def build_user_profile(user_id, days=7): now = timezone.now() records = ( Behavior.objects.filter( Q(user_id=user_id) & Q(created_at__gte=now - timedelta(days=days)) ) .values("news__category") .annotate(score=Sum("duration")) .order_by("-score") ) return {r["news__category"]: float(r["score"]) for r in records}

values("news__category")按分类分组,Sum("duration")把阅读时长加总成该分类的兴趣分。Q对象把用户和时间条件写在一组,以后加条件时逻辑不会被filter的参数顺序搞乱。duration为 0 的点击不影响总时长,可以不过滤。

排序函数要把“只看匹配分类”和“冷启动兜底”分开。完全按分数排序会让不相关但很新的文章混进来,演示效果会变差:

def recommend_for_user(user_id, top_n=10, days=7): profile = build_user_profile(user_id, days) if not profile: return News.objects.filter(is_published=True).order_by("-published_at")[:top_n] now = timezone.now() scored = [] for item in News.objects.filter(is_published=True): score = profile.get(item.category, 0.0) if score <= 0: continue recency = max(0.0, 1.0 - (now - item.published_at).days / 30.0) scored.append((score * 0.8 + recency * 0.2, item)) scored.sort(key=lambda x: x[0], reverse=True) result = [item for _, item in scored[:top_n]] if len(result) < top_n: result.extend( News.objects.filter(is_published=True) .exclude(id__in=[item.id for item in result]) .order_by("-published_at")[: top_n - len(result)] ) return result

没有历史行为时直接按发布时间取最新文章。有历史时,只取用户点过的分类,内容匹配度占 0.8,新鲜度占 0.2。如果匹配结果不够,再用最新文章填满 top_n,保证推荐接口永远不会返回空列表。

调参关系整理成表,写文档和答辩都能用:

参数默认值作用调优建议
days7只看最近多少天的行为数据少调到 14,但老兴趣权重会变大
top_n10返回推荐条数App 首屏建议 15
recency_days30新闻新鲜度衰减跨度新闻站建议 3~7
content_weight0.8内容匹配分权重用户行为越多越可以提高到 0.9
recency_weight0.2新鲜度分数权重内容分升高后,冷启动效果会变弱

3.3 推荐接口、去重和测试参数

把推荐函数包成 API。为了演示方便,不在路径里写死 user_id,而是用查询参数传:

from rest_framework.decorators import api_view from rest_framework.response import Response from behavior.models import Behavior from news.serializers import NewsSerializer from news.recommender import recommend_for_user @api_view(["GET"]) def recommend_api(request): user_id = request.query_params.get("user_id", "") top_n = int(request.query_params.get("top_n", 10)) if not user_id: return Response({"error": "user_id is required"}, status=400) news_list = recommend_for_user(user_id, top_n) viewed_ids = Behavior.objects.filter( user_id=user_id, behavior_type="detail" ).values_list("news_id", flat=True) news_list = [n for n in news_list if n.id not in viewed_ids] return Response(NewsSerializer(news_list, many=True).data)

top_n用查询参数传,是为了演示时一条命令能看不同条数,不用改代码。过滤viewed_ids是去重逻辑:已经读过详情页的新闻不再推,否则评委点开几篇后,推荐永远不变。user_id保持字符串处理,不要转成 int,Android 端传的设备 ID 可能是 UUID 或一长串随机字符。

测试步骤可以这样走:在 Xadmin 后台只发布科技和体育两类新闻,用test001在 App 里点击 5 篇科技文章,再调用推荐接口;换test002不点击,返回的应该是最新文章而不是科技类。这个对比能直接验证“推荐是针对用户的”这一核心结论,也是论文里最值得截图的部分。

4. Android 客户端:把推荐结果变成可点击的新闻流

4.1 用 Android Studio 建立 MVVM 目录和依赖

Android 端选 MVVM,不要把请求写在 Activity 里。先建三个包:data放网络和本地数据,ui放页面,viewmodel放状态。Gradle 至少需要 Retrofit、Gson、RecyclerView、Lifecycle:

implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'

logging-interceptor会在 Logcat 里打印请求和响应,联调时直接确认有没有访问到api/recommend。协程和 ViewModel 配合,页面销毁后请求不会继续跑,避免内存泄漏。RetrofitClient 的baseUrl写成http://10.0.2.2:8000/,再根据真机环境改成电脑局域网 IP。

4.2 Retrofit 接口定义和推荐回退逻辑

新建NewsApi.kt

interface NewsApi { @GET("api/recommend/") suspend fun getRecommend( @Query("user_id") userId: String, @Query("top_n") topN: Int = 10 ): List<NewsItem> @GET("api/news/") suspend fun getLatestNews(): List<NewsItem> }

top_n=15是为了让首屏列表超过一屏,便于上滑和产生更多点击数据。主入口先用推荐接口,失败时回退到最新文章接口,保证 App 永远有内容展示。ViewModel 这么写:

class HomeViewModel : ViewModel() { private val api = RetrofitClient.newsApi private val _newsList = MutableLiveData<List<NewsItem>>() val newsList: LiveData<List<NewsItem>> = _newsList fun refresh() { viewModelScope.launch { val userId = DeviceIdManager.getUserId() _newsList.value = try { val recommended = api.getRecommend(userId, 15) if (recommended.isNotEmpty()) recommended else api.getLatestNews() } catch (e: Exception) { api.getLatestNews() } } } }

DeviceIdManager内部用Settings.Secure.ANDROID_ID生成匿名 ID,不需要申请权限。try/catch是给答辩现场兜底的:后端推荐接口挂掉时,App 至少还能展示普通新闻列表,不会白屏卡住。

4.3 新闻列表渲染与点击行为上报

列表用 RecyclerView + ListAdapter。Adapter 点击部分:

override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { val item = items[position] holder.title.text = item.title holder.category.text = item.category holder.itemView.setOnClickListener { val intent = Intent(context, NewsDetailActivity::class.java) intent.putExtra("news_id", item.id) context.startActivity(intent) viewModel.reportBehavior(item.id, "detail", 0) } }

点击时先跳转详情页,再上报behavior_type=detailduration先给 0,等详情页在onPause时补报停留秒数。上报接口定义:

@POST("api/behavior/") suspend fun reportBehavior(@Body behavior: BehaviorBody): Response<Unit>

BehaviorBody的字段名要对应后端模型:user_idnews_idbehavior_typeduration。最容易错的是埋点时间点:详情页onPause上报而不是onStop,否则 Android 在后台上报受限时会丢数据。上报完后端才能有“行为”可用,推荐结果才会因人而异。

4.4 联调时最容易错的三个参数

联调出现白屏或 400 时,先查这三项:

场景接口地址示例常见错误
模拟器访问本机后端http://10.0.2.2:8000/api/localhost指向模拟器自身
真机连接电脑局域网http://192.168.x.x:8000/api/Django 的ALLOWED_HOSTS没加 IP
真机调试明文 HTTPmanifest 允许cleartextAndroid 9 以上默认拒绝明文流量

第一,模拟器访问宿主机必须用10.0.2.2。第二,ALLOWED_HOSTS加上局域网 IP,否则返回Invalid HTTP_HOST header。第三,在AndroidManifest.xml<application>上设置android:usesCleartextTraffic="true",否则视频演示时只能看到一片空白。usesCleartextTraffic只用于本机调试,上线必须换 HTTPS。

这三项检查完,列表能通,点击能上报,推荐接口才能拿到完整的用户行为数据。

5. 文档说明与验收脚本:让演示链路经得起追问

5.1 文档里写清楚启动顺序,比写算法公式更救命

源代码交付时,README 至少要写清四件事:后端依赖怎么装、数据库怎么初始化、Xadmin 后台入口在哪、Android 端如何改接口地址。把这些写成一个可复现的启动脚本:

pip install django xadmin djangorestframework python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000

0.0.0.0让手机能通过局域网 IP 访问后端。启动后先登录 Xadmin 发布 20 篇文章,再打开 Android 端验证。如果答辩现场换电脑,这一套命令能把环境在五分钟内重新搭起来,比当场解依赖问题靠谱得多。

5.2 用两条命令验证推荐是否真的“因人而异”

先在 Xadmin 后台给testA用户制造行为:点击几条科技类新闻;testB用户不点击。然后执行:

curl "http://127.0.0.1:8000/api/recommend/?user_id=testA&top_n=5" curl "http://127.0.0.1:8000/api/recommend/?user_id=testB&top_n=5"

两条命令返回结果不同,说明推荐链路生效。如果返回一样,先查behavior表里有没有记录,再查created_at是否还在 7 天窗口内,最后看用户 ID 有没有被 Android 端重置。

文档清单按发布包结构列成表:

交付物必须包含的内容验收动作
README.md环境版本、依赖安装、启动顺序、接口地址说明按步骤执行不报错
requirements.txt后端依赖pip install -r requirements.txt
接口文档列表、详情、推荐、上报四个接口的参数和返回用 curl 各调一次
Xadmin 数据至少 20 篇带分类的已发布新闻后台能按分类筛选
Android 源码接口地址集中放在一个配置类更换 IP 后十分钟内重新打包

最后在 Android 端清掉应用数据,重新点击三条体育新闻,等 10 秒,再用 curl 拉一次推荐接口,应该看到体育类文章排到前三位。这句验证脚本写进 README,源代码交付的完整度会比大多数只贴项目的仓库高出一截。

本文还有配套的精品资源,点击获取

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

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

立即咨询