ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

体育场馆预约小程序推荐系统实战:协同过滤与多端技术栈解析

体育场馆预约小程序推荐系统实战:协同过滤与多端技术栈解析 项目是给我之前的同事做的练手加试金项目核心一句话把一个体育场馆预订小程序从列表点选升级成千人千面的推荐平台。标题里那串技术栈看着有点杂——协同过滤算法、微信小程序、PHP、Node.js、vue加uniapp——其实各有各的用途不是炫技是当时面对真实约束的取舍结果。这篇文章想聊的不是单纯晒代码而是把整个项目从需求到上线过程中那些最容易被教程跳过又最折磨人的决策点写清楚。如果你正准备做类似的小程序项目或者想给自己的平台加一套推荐逻辑这篇应该能帮你少走不少弯路。1. 为什么体育场馆预约反而需要推荐引擎1.1 场馆平台最大的问题不是获客是选择困难先还原一下用户视角。正常人打开一个体育场馆小程序大概率是想打羽毛球或者周末想活动一下但打开列表后面对的是几十家场馆有气膜馆、社区馆、商业综合体里的馆价格从几十到三百一小时都有有的评分高但距离远有的就在楼下但时段冷门。传统做法是让用户自己去筛按价格排序、按距离排序、按评分排序。听起来没问题但实际留存数据很残酷用户翻三屏没找到合适的直接退出了下次想运动时也懒得再打开。这个场景和电商很像电商靠推荐提升转化率场馆预订也一样。但场馆领域有个特殊性用户的真实意图往往不是我要买东西而是我今晚想动一动却不知道去哪。这时候协同过滤的价值就出来了——它不硬猜用户画像而是依赖行为数据相似的人喜欢去哪用户以前约过的场馆和它差不多的场馆。本质上就是让平台的老用户的屁股帮新用户做选择。1.2 协同过滤在体育场馆场景的适用性判断我第一次搭这套系统时也犹豫过协同过滤是十几年前的老算法现在还合适吗深度学习是不是更先进后来想明白了这个场景里协同过滤反而比深度学习更务实第一用户行为数据量级有限一个区域几十万活跃用户算很不错了喂给神经网络容易过拟合第二场馆的物理属性位置、价格、运动类型本身就高度结构化用户的选择具备明显的群体规律性第三推荐结果需要解释性产品侧总要回答用户为什么给我推这个协同过滤的答案非常直接——和你有相同运动习惯的人去过。算法选型上我采用了经典的双路线基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF。后面会详细展开这里先给结论场馆预约更偏ItemCF因为用户偏好往往先附着在某个场馆上然后顺着场馆属性扩散。UserCF只作为行为数据丰富用户的一路补充信号。注意这不是绝对的和电商强调UserCF的快速兴趣转移不同场馆的复购周期长、低频反而ItemCF的稳定性和可解释性更好。2. 技术栈分工复盘PHP、Node.js、uniapp是怎么拧在一起的2.1 主业务用PHP推荐服务独立跑Node.js的逻辑很多人看到PHP加Node.js第一反应是疯了一个项目两种后端语言。其实这恰恰是我认为值得分享的点不要追求单一技术栈要追求每个模块的技术选型匹配它的任务属性。主业务用的是PHP我用的ThinkPHP 8负责什么用户注册登录、场馆管理、订单预约、支付回调、后台管理。这些是典型CRUD加事务型业务PHP的生态成熟、开发效率高、部署简单一台云服务器装个Nginx加PHP-FPM就能跑运维成本极低。对于这种重业务、轻计算、高可靠性要求的模块PHP是利润最高的选择。推荐服务则独立用Node.js写。为什么协同过滤的计算核心是矩阵运算和TopN排序在等待数据库IO的过程中需要并发处理Node.js的异步非阻塞模型很适合做这个BFF层。更重要的一点推荐逻辑会频繁调整改相似度权重、换衰减函数、加冷启动规则把它隔离成独立服务主业务的稳定性完全不受影响——就算推荐服务挂了用户还能正常预订场馆只是首页变成热门列表。这种降级友好的架构在业务系统里比全家桶一套逻辑要稳妥得多。两个服务之间怎么通信我在最开始就约定好使用HTTP JSON交互理由很实际语言无关、排查问题方便curl一眼就能看到返回、不需要引入额外消息队列基础设施。PHP侧在预约成功、浏览场馆的时候异步上报行为数据给Node推荐服务Node服务在PHP请求推荐接口时返回结果。高峰期行为量大的时候PHP只是写入一个本地的行为表通过定时任务批量同步给Node避免同步调用阻塞主流程。2.2 uniapp加Vue3写微信小程序值不值前端选择了uniapp加Vue3这个组合在圈子里争议不少。有人说uniapp性能不如原生微信小程序有人吐槽它封装层的坑。我个人的真实体验对于体育场馆这类中低频工具型小程序优势明显大于劣势。核心收益是一套代码同时出微信小程序、H5和App。实际运营中场馆老板要扫码看数据用户从公众号H5入口进管理员用App前期根本没人力维护三个端。uniapp把它们统一了。而且Vue3的composition API写起来比原生小程序那一套Page({})体验舒服太多组件化开发在项目后期改版时节省了大量时间。当然坑也有后面第六章细说。这里只强调一条经验值如果目标就是微信小程序且确定不会出App那原生开发如果有多端可能性uniapp是当前综合成本最低的选择。我这个项目从一开始就确定要走微信公众号加小程序双入口所以选uniapp是完全成立的。另外提一下vue在里面的角色。vue不只是uniapp的语法基础控制台管理端也直接用Vue3全家桶写了一个PC管理界面用于场馆方上架、排期、看预约数据。也就是说前端技术栈是uniapp用户端编译为目标 Vue3管理后台跑在浏览器代码有部分复用但职责边界清晰。2.3 服务间接口约定与数据流向定接口约定时我做了三件聪明事回头总结很值得说第一行为上报采用只增不改模式。用户浏览、收藏、下单、取消全部以追加事件方式记录不做业务字段的复杂更新。这样行为数据是一张不断增长的流水表推荐系统的输入永远可追溯。修改业务数据比如取消订单不影响原始行为记录。第二统一用户匿名标识。在小程序端用户未登录也可以浏览场馆此时用uuid生成一个游客标识行为上报同样记录。登录之后通过设备标识和微信unionid把游客行为合并到正式用户。这个细节如果不做新用户的前五次推荐永远是瞎猜因为系统看不到他注册前爱看什么。第三推荐服务只输出ID列表加原因标签。PHP拿到的推荐响应是标准JSON包含venue_id、score、reason热门、相似用户去过、同类场馆PHP只做透传渲染层面的解释由小程序端根据reason字段决定。这样做的好处是推荐规则的修改完全隔离在Node服务里前端和PHP主业务根本不用感知算法换了。整体数据流一句话总结小程序行为数据 - PHP异步落库 - Node定时拉取行为流水 - 离线计算相似度矩阵和推荐列表 - 写入缓存 - 小程序请求推荐时PHP从Node缓存服务取结果。3. 协同过滤算法落地的完整实现细节3.1 用隐式反馈构建伪评分矩阵场馆预约场景最大的算法前置问题是用户不会打分。去电商平台买完东西偶尔会评星级但打完一场球绝不会有人给场馆打五颗星。所以不能用显式评分矩阵必须从行为日志中构造隐式反馈。我用了行为加权方案核心是打开详情页、收藏、下单支付、取消订单四个行为分别对应1分、3分、5分、-2分。为什么这么设计浏览是弱信号但量极大适合用来做召回候选收藏和下单是强意图权重高取消订单要作为负反馈尤其是一周内多次取消同一类场馆的用户说明推荐方向偏了。加时间衰减也很关键我用的公式是score raw_weight * exp(-0.02 * days_since_event)90天前的浏览权重衰减到大约原来的16%避免推荐永远停留在用户半年前的兴趣上。最终的用户-物品矩阵是一个稀疏矩阵行是user_id列是venue_id值是累计加权评分。这个矩阵存起来有多大 10万用户乘以5000个场馆理论上5亿个格子但实际有值的可能就两三百万条所以用稀疏结构存。Node端我用的是自己实现的稀疏Map而不是二维数组具体原因和实现后面讲。这里有个容易被忽略的坑我先点出来不要用预约次数直接当评分。预约为0不是没兴趣可能是路远或时间不合适反之取消过两次的场馆一定代表某种抵触。次数只能当辅助特征加权组合才是有效的。3.2 UserCF实现用户相似度计算与TopK召回UserCF的思路是人以群分。实现上有三步第一步把用户-物品评分矩阵按用户归一化消除用户行为量级差异。有人天天逛小程序有人一个月就预约一次不归一化的话高频用户会主导一切普通用户和谁都不像。第二步计算用户间相似度。我用的余弦相似度公式是cos_sim dot(A, B) / (norm(A) * norm(B))。为什么不是皮尔逊因为稀疏矩阵下两个用户都只是各自只在十几项上有值皮尔逊需要计算均值结果噪声很大余弦在稀疏场景下表现更稳。这一步的核心优化是不要遍历所有用户对先用一个倒排索引——按场馆建立对这个场馆有过行为的用户列表这样只有共享过场馆的用户对才会被计算复杂度从O(N²)降到O(有效对)。第三步对目标用户取相似度最高的K个邻居我取K30对这30人评分过的场馆按相似度加权求和排除目标用户已经预约过的得到推荐得分取Top20作为UserCF结果。核心代码用Node实现大概是这个骨架// 计算用户相似度基于倒排索引优化 function buildUserSimilarity(events) { const venueUsers {}; for (const ev of events) { (venueUsers[ev.venue_id] || []).push(ev.user_id); } const userSim {}; for (const userIds of Object.values(venueUsers)) { for (let i 0; i userIds.length; i) { for (let j i 1; j userIds.length; j) { const a userIds[i], b userIds[j]; userSim[a] || {}; userSim[a][b] (userSim[a][b] || 0) 1; } } } // 然后对每个用户以共同场馆数除以向量模长得到余弦相似度 // 省略归一化细节 }这个倒排索引技巧是性能命脉没了它用户量过万之后计算时间会肉眼可见地飙升。3.3 ItemCF实现用场馆相似来做顺藤摸瓜ItemCF的逻辑反过来了与其找相似的人不如找相似的场馆。比如一个用户最近预约过A羽毛球馆系统就去算哪些场馆和A经常被同一批人预约把这些相似的场馆推给他。这个思路对场馆场景特别合用因为它的行为链条简单预约羽毛球馆的人大概率也预约过隔壁的乒乓球馆或者同价位的气膜篮球馆。计算物品相似度一样走倒排思路不过倒排的维度换成用户对每个用户建立他约过场馆的列表两两场馆之间出现同一用户的次数作为共现次数。然后用similarity 共现次数 / sqrt(场馆A总行为次数 * 场馆B总行为次数)做归一化即余弦相似度。ItemCF在线预测时拿到用户最近的N条正反馈行为取出每个物品的TopM相似物品按照行为和物品相似度乘积累加得分。需要注意过滤掉用户已预约和明确取消过的场这个是防推荐翻车的最基本保险。这里我踩过一个很直观的坑开始做ItemCF时相似度计算只看共现结果推荐出了羽毛球场旁的同品牌按摩店。原因是有大量企业团建订单会把几个完全无关的门类打包预约。后来加了门类约束跨运动大类羽毛球、篮球、游泳等的相似度不参与推荐除非相似度超过一个很高阈值。这才让推荐结果回到体育场馆的正轨。3.4 混合推荐策略按用户成熟度动态调权单独一种算法有明显边界UserCF对行为少的新用户完全失效ItemCF对行为少的用户只能靠仅有的几次点击猜测效果也一般。我采用了一个简单的分段混合策略按用户有效行为量分成三档用户行为区间推荐策略理由0 - 2 条热门兜底 城市偏好冷启动期推大热门和用户所在区域的高分场馆3 - 15 条ItemCF为主权重占比60%热门兜底40%行为不足以找相似用户但已有偏好锚点15 条以上UserCF 40% ItemCF 60%行为丰富相似用户有价值但ItemCF解释性更优实际实现时我是把三种来源的候选集丢进一个得分池乘上权重后归一化排序取Top20。这种分段比生产环境常用的实时加权融合更简单可靠——因为体育场馆平台的数据量根本不足以支撑实时调权重反而固定分段容易调试、容易解释给运营看。第二个原因是工程层面离线算好每个用户的推荐Top20存缓存小程序请求时直接读缓存响应时间稳定在50毫秒以内。如果做成实时计算每次请求都要跑一遍协同过滤算子流量一上来CPU就吃不消了。推荐系统在中小规模项目里先把计算前移离线、把结果缓存在线是最稳妥的架构方式。4. PHP主业务的数据表设计与微信生态对接4.1 核心表结构设计思路PHP侧是主业务系统表设计直接决定了推荐系统能否方便地取数。我用了六张核心表这里拆开讲一下设计动机第一张是venue场馆表除了常规的名称、封面、价格、营业时间、地址经纬度特意加了sport_type运动类型和tags标签JSON这两个字段是推荐系统做约束的重要依据比如前面说的跨运动大类不参与相似推荐就是靠sport_type实现的。第二张是user用户表除了微信身份信息额外加了address_region字段。这个字段很管用因为体育场馆是极强的地域性消费跨城推荐没有任何意义推荐打分时可以直接过滤。第三张是user_behavior行为流水表字段是event_id自增、user_id、venue_id、behavior_type、event_time、scenescene记录是首页曝光点击还是详情页浏览还是搜索结果点击帮助区分用户是被动看到还是主动寻找。这张表只做追加不做修改是推荐系统的原料库。第四张是order订单表关联场馆和场地时间片给推荐系统做预约成功这种最强正反馈的信号来源。第五张是recommend_cache缓存表字段是user_id、rec_listJSON数组、expire_time、create_timeNode推荐服务算完结果后写入这里PHP取推荐结果时直接查这张表逻辑最简单。第六张是item_sim_cache物品相似度缓存表键是venue_id值是Top50相似场馆列表加相似度也是JSON存储。这张表只由Node服务更新PHP只读透传。一个针对性设计所有表都带create_time索引因为推荐系统每次离线计算都要从流水表拉某时间点以后的新行为数据没有这个索引全表扫描会把PHP服务器的IO打满。4.2 关键接口定义与权限控制接口设计遵循薄接口原则每个接口只做一件事返回结构统一为{code, msg, data}。这里列出几个核心接口POST /api/auth/login微信登录用小程序端传来的code换openid和session_key再返回自定义token。POST /api/auth/phone获取微信手机号前端通过open-typegetPhoneNumber拿到code后端调微信接口换取手机号完成绑定。GET /api/venue/list场馆列表支持按区域、运动类型、价格区间过滤。GET /api/venue/detail?venue_idxxx场馆详情返回设施、图片、可预约时段。GET /api/recommend/list推荐列表直接读缓存表无缓存时降级返回热门列表。POST /api/behavior/report行为上报接收浏览、收藏、取消预定等操作。POST /api/order/create创建预约订单事务性操作涉及支付回调状态流转。权限控制上小程序端全部走toke鉴权token用JWT签发有效期七天刷新操作放在拦器里统一做。这里有个微妙问题行为上报接口一定不能因为鉴权失败就丢弃行为游客状态也要允许上报。我的方案是行为上报接口是半开放鉴权——有token解析身份没token则用游客uuid记录保证了新用户的早期行为不丢。4.3 微信小程序登录与手机号获取的落地细节微信登录这个事官方文档写得清楚但实操总是出问题。我梳理一下我这边验证过的完整链路。登录部分小程序端uni.login()拿到code传给后端/api/auth/login后端用code换openid和session_key。注意这里有个隐藏要求code只能用一次且有效期五分钟。如果前端网络抖动可能会重复发送同一个code后台必须处理重复code报错的情况不能因此直接返回登录失败应该引导重新uni.login刷新code。手机号获取是很多人容易卡住的点。流程是页面放一个button open-typegetPhoneNumber用户点击后会触发回调拿到一个code。这时候后端拿着code调微信接口https://api.weixin.qq.com/wxa/business/getuserphonenumber注意这个接口需要access_token不是session_key。我当时在这里被绕晕过一次——登录和手机号获取用的凭证完全不同一个是session_key一个是access_token。PHP实现时用curl请求appid和secret配置在独立配置文件里不允许出现在uniapp前端源码中。还有一个坑手机号快速填写的code在几分钟内是有效的且一个手机号在一个小程序30天内最多获取10次真实手机号所以拿到的手机号一定要自己存好别重复去换。开发阶段测试时经常把额度用完这时候可以用微信开发者工具的模拟手机号功能但真机上必须走真实链路。5. uniapp前端与微信小程序适配的实战记录5.1 自定义导航栏的高度适配方案小程序首页为了视觉效果放弃了原生导航栏改用自定义导航栏。这在小程序里是很常见的操作但高度适配是第一个坑。微信小程序的顶部由三部分组成状态栏显示时间电量那个区域、导航栏标题和胶囊按钮所在区域、页面内容。原生导航栏自带高度适配自定义后就需要自己算。我的适配代码是这样的const systemInfo uni.getSystemInfoSync(); const menuButtonInfo uni.getMenuButtonBoundingClientRect(); // 状态栏高度 const statusBarHeight systemInfo.statusBarHeight; // 导航栏高度 (胶囊按钮顶部 - 状态栏高度) * 2 胶囊按钮高度 const navBarHeight (menuButtonInfo.top - statusBarHeight) * 2 menuButtonInfo.height;为什么这么算因为胶囊按钮在导航栏中是垂直居中的menuButtonInfo.top - statusBarHeight是胶囊顶部到状态栏底部的距离由于垂直居中这段距离的两倍加上胶囊高度就是导航栏的完整高度。这个公式在几乎所有国产手机上都是准的但在iPhone上需要额外注意底部安全区的问题不过导航栏这块公式可以直接用。适配时要处理另一个细节不同机型胶囊按钮的位置不同所以不能让navBarHeight写死必须在onLoad时动态计算。我封装了一个useNavBar的组合式函数Vue3在多个页面里复用返回statusBarHeight和navBarHeight两个值。首页的搜索框、推荐Tab、天气组件都塞进这个自定义导航栏的容器里视觉上融合成一整块体验比原生好很多。5.2 source size 2612kb超限与分包方案热词里出现过一个我特别熟悉的问题source size 2612kb exceed max limit 2mb。这是微信小程序主包大小超限的经典报错。我当时第一次打包主包就冲到1.8MB加了一些图表库之后直接爆了2MB限制。微信小程序的规则是主包加分包的总大小上限是20MB但主包本身不能超过2MB。也就是说必须把非首屏用到的页面拆进分包。我的拆法是这样主包只保留首页、推荐页、登录页、场馆详情页因为用户在首页大概率会直接点进第一个推荐的馆。订单列表、个人中心、设置、支付结果页放进packageOrder分包。场馆管理后台的页面景点方用放进packageAdmin分包。独立静态资源图片、图标全部走CDN不在本地存。这样做完之后主包降到1.3MB左右。但要注意一个配合问题分包里的页面不能相互引用主包以外的组件否则会报找不到组件。我踩了一次把个人中心的优惠券组件放进了分包但订单列表也引用了它导致构建报错。解决方式是公共组件要么放主包要么单独抽到分包再互相引用项目里我是把公共组件全部留在主包因为主包体积还有余量。压缩图片是另一个大头。uniapp打包时会把本地图片base64内联到js里一张几MB的图就足以击穿2MB限制。我开发后期统一把用户头像、场馆实拍图全部外链到CDN本地只保留几个必须的预设图标体积瞬间降下来。5.3 场馆推荐卡片的交互设计与请求优化推荐页的交互设计是这个小程序体验好坏的关键。我做了三件事第一推荐理由可视化。每张场馆卡片上除了场馆名、图片、价格还会显示一行小字比如和你常去的XX羽毛球馆相似或本区热门场馆。这行字就是从推荐服务返回的reason字段渲染的。加了这行字之后用户点击率提高了不少核心原因是协同过滤的黑盒感被降低了用户会想哦原来是因为我打过羽毛球才推这个。第二滚动加载与缓存策略。推荐页用滚动到底部自动加载下一页每页20条。但这里有个体验陷阱用户回退再进来时如果重新请求又会加载一遍数据可能变。我的做法是推荐列表在进入页面时先读本地storage缓存同时异步请求新的推荐列表用新结果替换旧的。这样用户看到的是秒开的旧数据两三百毫秒后自动刷新成最新推荐感知上很流畅。第三行为上报的节流与批量。浏览行为不能每滑动一张卡片就上报一次否则服务端压力大、电量消耗也大。我做了节流用户停留超过3秒才计算为一次有效浏览并且同一场馆2分钟内不重复上报。同时把上报请求合并成数组在页面onHide或onUnload时统一POST一次减少网络请求次数。这个优化对小程序性能的影响很直接因为微信的wx.request并发限制是10个行为上报不节流会和主接口抢通道导致首页加载变慢。6. 冷启动策略与线上踩坑排查实践6.1 新用户冷启动没有行为数据怎么推冷启动是我花了最多时间调试的部分因为没有历史行为的用户在前三天体验不好转身就走了。我的方案分三层第一层地理位置兜底。用户进入小程序时通过微信授权拿定位按省市区过滤场馆。体育场馆是LBS属性极重的品类一个广州用户看到五个上海热门馆再好的推荐也没有意义。定位失败的场景我默认取IP归属城市。第二层热度兜底。热度分是近7天订单量×0.6 收藏量×0.3 评分×0.1。代码里定期算一次。热度兜底不是单纯按销量排而是给新场馆一个爬坡期新上架场馆在第一个月内热度乘以1.5的系数避免新馆永远推不出去。第三层内容属性试探推荐。如果用户首次点击了某个羽毛球馆但没有产生后续行为我会把同运动类型、同区域、价格区间相近的场馆直接推给他。这实际上是一个基于内容特征的冷启动ItemCF在这个阶段比协同过滤更可靠。冷启动的推荐里我会同时掺杂一定比例的热门和一定比例的个性化候选比例大约7比3。目的不是纯粹个性化而是尽可能多地观察用户点击行为让系统快速积累可用于协同过滤的种子数据。6.2 相似度内存爆炸与计算时长的排查第一次上线离线计算的作业时Node服务直接内存溢出崩溃了。排查过程是这样的日志报heap out of memory我先以为是场地数量太多后来发现是相似度Map存了全量的用户对相似度而用户对数随用户量呈平方级增长——1万个用户执行到一半就有几千万个键值对必然爆炸。解决分三步第一候选集收缩——计算用户相似度时只保留同区域、同运动偏好的用户对其他的一律不参与计算。这不只是省内存还提升了推荐质量因为跨区域的用户相似度本来就没有实际意义。第二只存TopK不存全量——相似度矩阵不再存所有用户对的分数每个用户只保留相似度最高的30个邻居物品相似度同理每个场馆只保留Top50相似场馆。第三大计算切分批次跑——离线任务按用户ID范围分片每片5万个用户内存峰值降下来了。另一个时间性能问题是行为流水表在数据量大了之后全量拉取一次要几十秒然后和存量矩阵合并时还有大量IO。我的改法是做增量更新每天只拉取前一日的新行为把新增行为叠加到已有支持数上而不是每次全量重建矩阵。增量模式跑一次控制在10秒以内完全够用。6.3 问题排除的完整链路为什么推荐结果老是不变我在这里必须分享一个真实的线上bug排查过程因为这类问题在推荐系统里太典型了上线两周后后台监测数据显示推荐点击率在逐步下降但推荐列表本身看起来一直没变。排查链路我按顺序走第一步查推荐服务的最后计算时间。发现离线任务每天凌晨两点正常执行但写入的recommend_cache表里大量用户的推荐列表和前一天比分完全一致。问题初步定位在相似度没有更新。第二步拆解公式。怀疑是新增行为没有进入评分矩阵——查增量拉取脚本发现按时间过滤时时间条件用的是事件时间但行为表event_time和库表create_time存在时区问题PHP写入的是北京时间而Node定时任务按UTC时间过滤导致晚8点到凌晨2点的行为永远拉不进来。这个bug很隐蔽结果就是每天只更新了头一天白天的少数行为大部分晚间新增行为没进去推荐自然越来越固化。第三步修复后重新验证。我把时间过滤统一改为数据库自增ID增量记录上次处理到的最大event_id只处理id更大的彻底避开时区问题。这次修复后推荐更新恢复正常点击率回到预期水平。这个bug给我的教训很深分布式服务的时间坐标一定要选一个绝对单调的东西跨语言跨时区时绝对不要用本地时间字符串作为增量标记。用自增ID天然单调不回溯一劳永逸。6.4 运营视角的推荐效果验证方法算法上线不等于结束还得让运营看得见效果。我加了一个简单的A/B效果观测同一版本中随机把10%的用户分配到无推荐对照组他们看到的是纯热门排序90%看到推荐结果。对比指标是卡片点击率和预约转化率周期跑两周出报告。实际数据给了一个很有意思的结果推荐组点击率比对照组高27%但预约转化率只高11%。说明协同过滤拉动了用户浏览意愿但预约还受价格、时段、距离等硬条件制约推荐做不了无米之炊。最后一条运营经验节假日期间的推荐策略必须人工干预。寒暑假和周末的场馆供需关系会和平时完全不同我在节假日启动了一个临时加权模板把草场羽毛球场和篮球场在晚间时段的权重调高同时降低白天时段的派发这个规则不进协同过滤直接在最后打分后叠加。运营可以在后台选择是否启用简单粗暴但非常有效。写在最后的实操体会项目从开发到上线前后三个月我最大的体会是不要把推荐系统当算法项目做要当**数据工程加产品设计**来做。真正耗时间的不是余弦相似度代码本身而是把用户行为数据做得干净、把推荐结果解释得清楚、把异常流量和冷启动问题处理得让用户无感。如果你正准备给自己项目加推荐功能我的建议是先从一张行为流水表加一个热门兜底开始看到有真实行为数据了再逐步把ItemCF和UserCF叠上去每一步都保持可回退就不会被算法绑架。写字板里最乱的时代已经过去现在这个版本也还在继续迭代后面计划把微信搜索端和抖音小程序的入口也接过来用同一套uniapp代码去构建。到时又是一大堆适配的坑要踩但踩完回头一看这个过程本身才是这个项目最大的收获。
返回列表