ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

新闻推荐客户端实战:Django后端、Xadmin与Android闭环构建

新闻推荐客户端实战:Django后端、Xadmin与Android闭环构建 简介这份资源是一套完整的本科安卓毕业设计项目主题为基于Android的新闻推荐客户端配套基于DjangoXadmin实现的Web后台管理端。资源包含全部源代码和文档说明适合计算机相关专业在校生用于毕设参考、课程设计或项目初期演示也适合具备一定基础的学习者在此基础上二次开发。压缩包共475个文件大小约3.04MB主要涵盖Python后端文件、Android前端JS与HTML页面、CSS样式表及Django框架相关配置文件前后端代码结构清晰便于对照学习。目前已有95人浏览学习。代码经过测试运行成功作者表示可远程教学下载后如有运行问题可私聊咨询。除源码外还附有项目说明文档能帮助读者理解系统架构与实现思路。1. 选题拆解毕设标题里的“新闻推荐”到底在考什么新闻推荐客户端听起来是算法题大部分毕设却挂在工程上。题目真正要交付的不是一个模型而是一条能演示的闭环DjangoXadmin 负责内容管理和后台发布Android 端负责展示和点击推荐逻辑把用户行为变成新的排序。答辩评委最关心三个点推荐数据从哪来、接口怎么设计、换一个用户结果会不会变。只在本地写死推荐列表或只做一个能刷新闻的 App都会在追问中露馅。后续按后端、推荐、客户端、文档交付的顺序把这条链路走通适合要独立完成 Android Django 闭环的本科生也适合想快速搭内容分发 Demo 的工程师。先放下算法执念从内容骨架开始搭。2. DjangoXadmin 后端先建内容骨架再谈推荐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 djangorestframeworkstartproject生成外层目录和同名配置包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.pyrecommender.py是新增文件专门放推荐算法。这样目录本身就是文档之后写说明时可以直接说清楚每个文件的职责。2.2 定义新闻与行为的 Model新闻模型要兼顾列表展示和推荐特征标题、分类、发布时间是必填项封面地址留空也能跑通# news/models.py from django.db import models class News(models.Model): title models.CharField(max_length200, verbose_name标题) summary models.TextField(blankTrue, verbose_name摘要) content models.TextField(verbose_name正文) category models.CharField(max_length50, db_indexTrue, verbose_name分类) source models.CharField(max_length100, blankTrue, verbose_name来源) cover_url models.URLField(blankTrue, verbose_name封面地址) is_published models.BooleanField(defaultFalse, db_indexTrue, verbose_name是否发布) published_at models.DateTimeField(auto_now_addTrue, verbose_name发布时间) def __str__(self): return self.titledb_indexTrue给分类和时间加索引推荐接口按分类过滤、按发布时间倒序时不会慢。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_length64, db_indexTrue) news models.ForeignKey(news.News, on_deletemodels.CASCADE) behavior_type models.CharField(max_length16) duration models.IntegerField(default0) created_at models.DateTimeField(auto_now_addTrue)外键用字符串引用避免循环 import。created_at用来筛选推荐窗口duration是推荐评分里最直接的打分源。如果不想做用户系统设备 ID 是毕设最合理的方案能区分用户又不用处理登录和 token 流程。后台字段整理成表方便写文档时直接复用字段类型用途news_id外键关联新闻文章user_idCharField匿名设备标识不依赖用户表behavior_typeCharField点击、阅读、点赞、分享durationIntegerField详情页停留秒数推荐权重主值created_atDateTimeField取最近 N 天的行为数据2.3 接入 Xadmin 并配置列表可操作在news/下新建adminx.pyXadmin 会自动扫描这个文件名# 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.py的INSTALLED_APPS中追加xadmin及其依赖模块并注册news和behavior。不同版本依赖项不同安装后如果报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_publishedTrue).order_by(-published_at) serializer_class NewsSerializerReadOnlyModelViewSet自动提供 list 和 retrieve 两个动作列表和详情都有了。is_publishedTrue写在 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, basenamenews) 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, days7): now timezone.now() records ( Behavior.objects.filter( Q(user_iduser_id) Q(created_at__gtenow - timedelta(daysdays)) ) .values(news__category) .annotate(scoreSum(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_n10, days7): profile build_user_profile(user_id, days) if not profile: return News.objects.filter(is_publishedTrue).order_by(-published_at)[:top_n] now timezone.now() scored [] for item in News.objects.filter(is_publishedTrue): 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(keylambda x: x[0], reverseTrue) result [item for _, item in scored[:top_n]] if len(result) top_n: result.extend( News.objects.filter(is_publishedTrue) .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 首屏建议 15recency_days30新闻新鲜度衰减跨度新闻站建议 37content_weight0.8内容匹配分权重用户行为越多越可以提高到 0.9recency_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}, status400) news_list recommend_for_user(user_id, top_n) viewed_ids Behavior.objects.filter( user_iduser_id, behavior_typedetail ).values_list(news_id, flatTrue) news_list [n for n in news_list if n.id not in viewed_ids] return Response(NewsSerializer(news_list, manyTrue).data)top_n用查询参数传是为了演示时一条命令能看不同条数不用改代码。过滤viewed_ids是去重逻辑已经读过详情页的新闻不再推否则评委点开几篇后推荐永远不变。user_id保持字符串处理不要转成 intAndroid 端传的设备 ID 可能是 UUID 或一长串随机字符。测试步骤可以这样走在 Xadmin 后台只发布科技和体育两类新闻用test001在 App 里点击 5 篇科技文章再调用推荐接口换test002不点击返回的应该是最新文章而不是科技类。这个对比能直接验证“推荐是针对用户的”这一核心结论也是论文里最值得截图的部分。4. Android 客户端把推荐结果变成可点击的新闻流4.1 用 Android Studio 建立 MVVM 目录和依赖Android 端选 MVVM不要把请求写在 Activity 里。先建三个包data放网络和本地数据ui放页面viewmodel放状态。Gradle 至少需要 Retrofit、Gson、RecyclerView、Lifecycleimplementation 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.4logging-interceptor会在 Logcat 里打印请求和响应联调时直接确认有没有访问到api/recommend。协程和 ViewModel 配合页面销毁后请求不会继续跑避免内存泄漏。RetrofitClient 的baseUrl写成http://10.0.2.2:8000/再根据真机环境改成电脑局域网 IP。4.2 Retrofit 接口定义和推荐回退逻辑新建NewsApi.ktinterface NewsApi { GET(api/recommend/) suspend fun getRecommend( Query(user_id) userId: String, Query(top_n) topN: Int 10 ): ListNewsItem GET(api/news/) suspend fun getLatestNews(): ListNewsItem }top_n15是为了让首屏列表超过一屏便于上滑和产生更多点击数据。主入口先用推荐接口失败时回退到最新文章接口保证 App 永远有内容展示。ViewModel 这么写class HomeViewModel : ViewModel() { private val api RetrofitClient.newsApi private val _newsList MutableLiveDataListNewsItem() val newsList: LiveDataListNewsItem _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_typedetail。duration先给 0等详情页在onPause时补报停留秒数。上报接口定义POST(api/behavior/) suspend fun reportBehavior(Body behavior: BehaviorBody): ResponseUnitBehaviorBody的字段名要对应后端模型user_id、news_id、behavior_type、duration。最容易错的是埋点时间点详情页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:usesCleartextTraffictrue否则视频演示时只能看到一片空白。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:80000.0.0.0让手机能通过局域网 IP 访问后端。启动后先登录 Xadmin 发布 20 篇文章再打开 Android 端验证。如果答辩现场换电脑这一套命令能把环境在五分钟内重新搭起来比当场解依赖问题靠谱得多。5.2 用两条命令验证推荐是否真的“因人而异”先在 Xadmin 后台给testA用户制造行为点击几条科技类新闻testB用户不点击。然后执行curl http://127.0.0.1:8000/api/recommend/?user_idtestAtop_n5 curl http://127.0.0.1:8000/api/recommend/?user_idtestBtop_n5两条命令返回结果不同说明推荐链路生效。如果返回一样先查behavior表里有没有记录再查created_at是否还在 7 天窗口内最后看用户 ID 有没有被 Android 端重置。文档清单按发布包结构列成表交付物必须包含的内容验收动作README.md环境版本、依赖安装、启动顺序、接口地址说明按步骤执行不报错requirements.txt后端依赖pip install -r requirements.txt接口文档列表、详情、推荐、上报四个接口的参数和返回用 curl 各调一次Xadmin 数据至少 20 篇带分类的已发布新闻后台能按分类筛选Android 源码接口地址集中放在一个配置类更换 IP 后十分钟内重新打包最后在 Android 端清掉应用数据重新点击三条体育新闻等 10 秒再用 curl 拉一次推荐接口应该看到体育类文章排到前三位。这句验证脚本写进 README源代码交付的完整度会比大多数只贴项目的仓库高出一截。本文还有配套的精品资源点击获取
返回列表