ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

新闻推荐系统全栈实战:从Selenium爬虫到Hadoop与大模型混合推荐

2026/9/8 1:12:17 拓冰建站 浏览量
新闻推荐系统全栈实战:从Selenium爬虫到Hadoop与大模型混合推荐 如果你最近在做大数据方向的课程设计或者毕设大概率刷到过“新闻推荐系统”这个题目。热点新闻平台、爬虫、可视化、Hadoop、推荐算法这几个词叠在一起确实很唬人尤其是再加上“大模型”之后几乎是答辩现场的“王炸”配置。但说句实话我见过太多号称“新闻推荐系统”的Demo打开之后其实是同一个模子后台管理员手动填新闻前台按发布时间排一排点进去显示一个浏览量数字然后管这个叫“推荐算法”。这顶多算内容管理系统。这篇文章要聊的是我实际做过的一套完整项目用Selenium爬虫从新闻网站采集热点内容数据清洗后落入Hadoop HDFS通过Hive做数仓建模和离线特征计算推荐侧用“热度召回 协同过滤/内容相似度召回 大模型精排”的混合推荐算法后端用Django提供推荐接口前端用Vue搭建新闻平台和可视化大屏。整条链路从数据源到用户点击行为回流是一个能真正跑起来、能验收、也敢在答辩时演示的闭环。适合谁来参考两类人一类是正在做大数据课设、毕设或者找项目练手的同学另一类是第一次把Hadoop、爬虫、推荐算法这几个东西拼在一起想知道“它们到底怎么协作”的开发者。我会把技术选型理由、数据流链路、关键代码逻辑、踩过的坑都写清楚尽量让你能照着搭出属于自己的版本。1. 先搞清楚你做的到底是“热度榜”还是“推荐系统”1.1 常见的“伪推荐系统”长什么样我拆过不少所谓的新闻推荐项目表面上有登录、有新闻列表、有收藏、有点击量但仔细一看推荐逻辑无非三种全站用户看到同一个列表按浏览量倒序排按栏目分类把最近几天的新闻按时间倒序排号称用了协同过滤但用户行为数据是写死的假数据模型根本没跑起来。这类系统的问题不是“算法不够高级”而是整个推荐闭环是断的。用户打开首页看到的是编辑置顶的内容系统不知道他喜欢科技还是体育用户点完一条新闻也没有任何机制让这个行为影响下一次刷新。说白了推荐系统最重要的“个性化”“动态更新”“反馈闭环”一个都没做到那就是一个带壳的热度榜。1.2 真正的新闻推荐系统要有三个硬指标我这里说的“硬指标”不是指的准确率数值而是在项目设计层面必须成立的条件个性化不同用户打开首页推荐列表要不一样。哪怕只是个“兴趣标签”维度的差异也不能所有人一模一样。动态更新新新闻进入系统后应该能进入候选池用户点击、收藏、停留时长等行为应该能影响下一轮推荐结果。反馈闭环用户行为必须被采集、落库、回流到算法训练里。没有这一环你的系统就只是一次性推荐不是会成长的推荐系统。新闻场景还有一个特殊性时新性极强。三天前的新闻即使曾经爆火今天也不该再出现在首页。所以新闻推荐不只是“用户喜欢什么”还要掂量“这条新闻现在还值不值得推”。这就是为什么最终项目采用混合算法而不是单一模型——热度负责时效和冷启动兴趣匹配负责个性化大模型负责内容理解。1.3 本项目全链路定位与数据流向先给你整体的数据流这段链路建议直接画在答辩PPT的第一页Selenium爬虫定时抓取新闻网站列表页和详情页清洗后的结构化数据写入本地JSON Lines文件再上传到Hadoop HDFSHive按日期分区建表通过Hive SQL完成基础统计和特征提取推荐算法读取Hive统计结果算出热度分、相似度分、大模型精评分把每个用户/匿名用户的推荐列表写入MySQL和RedisDjango后端从缓存或数据库取推荐结果对外提供APIVue前端调用API渲染新闻流和可视化大屏用户点击、收藏行为上报到Django存入行为表定时导出参与下一轮模型更新。这个链路的好处是每层都职责清晰爬虫只负责采集Hadoop只负责存储和批量计算算法层只算分Django只做接口和服务Vue只做展示。任何一层出问题都能独立排查不会出现“改个推荐算法导致页面打不开”的尴尬情况。2. 技术栈选型复盘Hadoop、Django、Vue、Selenium怎么分工2.1 存储与计算选型为什么是Hadoop不是MySQL很多人的疑问是新闻数据就这么点几万条而已有必要上Hadoop吗实话讲如果只是演示MySQL完全够用。但如果你做的是“大数据项目”Hadoop不是可选项而是核心展示点。新闻爬虫跑一个月原始HTML加结构化数据能到几千万条文件数量非常大HDFS在处理海量小文件时可以把它们合并成更大的数据块Hive能直接在分布式存储上做批量统计。更重要的是Hadoop生态的“存储计算分离”思路和MySQL完全不一样你把数据扔到HDFS上然后用Hive/MR/Spark去算算完结果再导回业务库这样既不会让业务数据库被批量统计拖垮也能体现大数据处理能力。当然纯单机MySQL也能做新闻推荐但那就不叫大数据项目了。我的建议是数据量级不一定真要到PB级别但处理链条一定要“像大数据那么回事”。2.2 爬虫框架选型静态请求和Selenium之间怎么选这个选择其实很现实。你随便打开一个新闻网站按F12看网络请求会发现很多新闻列表的数据不是直接写在HTML源码里的而是页面加载后用JavaScript异步请求接口渲染出来的。这时候用requests直接请求页面URL拿到的HTML里只有空壳根本解析不到新闻条目。处理办法有两个抓接口找到页面背后的Ajax接口模拟请求返回JSON直接解析。优点是速度快缺点是接口参数可能带签名、加密字段逆向成本高。用Selenium直接驱动真实浏览器打开页面等JS执行完再取渲染后的完整HTML。优点是省去接口逆向缺点是速度慢、更吃资源。本项目用Selenium核心原因是“稳定优先”。爬虫需要长时间无人值守运行如果一个接口签名变了就断排查成本很高而Selenium只要目标站不强制登录、不弹验证码基本不会因为前端重构而崩溃。虽然慢一点但爬虫频率本身就不用太高每半小时跑一批完全够用。2.3 后端和前端选型Django负责接口Vue负责呈现Django的优势在Python项目里非常明显自带ORM、Admin后台、用户认证加上Django REST Framework三四天就能把API骨架搭完。这个项目里Django的核心职责不是做推荐计算——推荐是离线算好的它更像是“推荐结果的前端服务层”从Redis读缓存、从MySQL读行为数据、给Vue提供标准的JSON接口。Vue这边我选它主要是为了可视化表达。新闻推荐系统的页面无非三大块新闻信息流、新闻详情页、数据可视化大屏。Vue的组件化适合把这三块拆开维护ECharts是Vue生态里最顺手的可视化库热点趋势图、栏目分布饼图、用户画像词云都靠它。如果你愿意折腾还能加一个Vue Router管理的多页面结构让“推荐流”和“可视化大屏”作为两个独立路由存在。2.4 系统整体数据从哪来、到哪去我画一下最终跑通的架构分工文字版采集层Selenium Chrome WebDriver抓列表页和详情页存储层原始HTML和结构化JSON存入HDFSHive负责离线数仓计算层离线跑热度统计、协同过滤、TF-IDF相似度、大模型批量打分业务层Django MySQL Redis对外提供推荐流、热度榜、行为上报接口展示层Vue ECharts新闻平台 可视化大屏调度层Crontab/APScheduler串联以上所有定时任务。这里有一点要提醒不要上来就写业务代码先把这条数据流在文档里画清楚。我当初因为没画清楚就动手结果Django后端写了快一半才发现不知道推荐结果放在哪、行为数据往哪存返工成本非常高。3. 爬虫模块实战用Selenium把热点新闻变成可用数据3.1 爬虫整体结构列表页抓链接详情页抓正文新闻爬虫最忌讳一页一页直接抓全量内容效率低而且容易触发反爬。推荐用“列表页 详情页”双层结构打开新闻列表页比如科技频道、财经频道等待页面渲染完成滚动到底触发懒加载让更多新闻链接出现提取当前页所有新闻的URL存入待抓取队列遍历URL逐个打开详情页抓取标题、正文、来源、发布时间、栏目、阅读数等字段抓完的URL写入已完成表避免下次重复。这样做的好处是列表页抓得勤、详情页抓得少能有效控制请求量。比如列表页每30分钟跑一次详情页只在出现新链接时才去抓大部分时间系统是空闲的。3.2 Selenium关键配置无头、等待、滚动加载Selenium的核心代码其实不多但配置细节决定成败。我直接在项目里用的配置如下from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headless) options.add_argument(--no-sandbox) options.add_argument(--disable-gpu) options.add_argument(--disable-blink-featuresAutomationControlled) options.add_argument(--user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) ...) driver webdriver.Chrome(optionsoptions) driver.get(https://news.example.com/tech)这里面最容易被忽略的是--disable-blink-featuresAutomationControlled。不加它很多网站可以通过navigator.webdriver属性识别出这是自动化浏览器直接拒绝后续请求。加上之后能降低一部分被检测概率。懒加载处理靠滚动import time from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait WebDriverWait(driver, 10) wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, div.news-list))) for _ in range(5): driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) time.sleep(1.5)每滚动一次页面会继续加载新内容。等你拿到足够多的新闻链接后再停止滚动而不是一口气滚到底减少无意义请求。3.3 反爬不是玄学降低存在感比绕验证码更重要很多同学一遇到反爬就想着怎么破解验证码、怎么伪造指纹这些当然有用但对新闻爬虫来说第一要务是“别让目标站感觉到你是个异常程序”。我总结下来最重要的几条随机请求间隔每次请求之间随机等2到5秒而不是固定3秒。固定间隔在日志里看起来非常机器化。User-Agent轮换维护一个UA池每次请求随机取一个至少覆盖Chrome、Edge、Safari。控制整体量新闻站不是电商不需要每秒抓几十条。列表页半小时一次、详情页每分钟不超过10条基本不会被封。遇到验证码不硬刚如果出现滑块验证码或图片验证码程序直接跳到下一个URL把当前URL记入失败队列下次再试。宁可漏掉几条不要卡死整个任务。我实际运行过程中遇到过滑块验证码也遇到过IP被临时限制。滑块验证的通用处理思路是通过OpenCV识别缺口位置、用actions拖拽但成功率不稳定还不如放过它过段时间再补抓。别在一个验证码上耗一下午。3.4 清洗与入库从HTML到干净的JSON LinesHTML拿到手之后用BeautifulSoup或者lxml解析。重点解析字段包括news_id用URL的MD5生成作为主键去重title新闻标题content正文文本去掉script、style、广告节点source来源名比如“XX网”“YY时报”publish_time发布时间统一转成标准时间格式category栏目/分类views阅读数、评论数等热度字段有的站有没有就填0。清洗完成后每行一条JSON输出方便后续入HDFS和Hive。文件名带上批次时间比如news_20250120_1830.jsonl。这里有个经验原始HTML也尽量留一份压缩后存起来虽然前期用不到但如果你想重跑清洗逻辑不需要重新爬一遍。4. 大数据流转Hadoop存储和Hive数仓建模4.1 伪分布式环境与格式化那个著名的坑Hadoop的第一道坎就是环境搭建。单机训练/毕设用伪分布式模式足够真正上集群可以用Ambari之类工具部署但成本和复杂度高不少。伪分布式需要注意的点core-site.xml配置fs.defaultFS为hdfs://localhost:9000hdfs-site.xml配置副本数为1并指定NameNode和DataNode的目录路径改好配置后第一次启动之前要先执行hdfs namenode -format。格式化失败是最常见的坑之一。原因是之前运行过程中NameNode目录已经存在或者目录里有残留数据。解决方案很简单把dfs.namenode.name.dir和dfs.datanode.data.dir指向的目录清空再重新格式化。另外如果出现Incompatible clusterIDs之类的报错说明DataNode和NameNode的clusterID不一致删除DataNode目录里的current文件夹重新启动即可。Hadoop启动后用jps确认进程应该有NameNodeDataNodeSecondaryNameNodeResourceManagerNodeManager这几个关键进程。少一个都说明启动有问题。4.2 新闻原始数据如何进入HDFS数据进入HDFS最直接的方式就是命令行上传hdfs dfs -mkdir -p /warehouse/news/raw hdfs dfs -put ./news_*.jsonl /warehouse/news/raw/如果数据量大可以先合并文件再上传避免大量小文件挤占NameNode内存。比如每天抓完数据后用shell脚本把当天所有jsonl合并成一个news_20250120.jsonl再上传这样Hive查询效率也会高很多。也可以写一个Python脚本直接用hdfs库或调用subprocess执行hdfs dfs -put把这个步骤纳入整体调度。不需要为了“看起来高级”引入Flume或者Kafka——在这个项目里定时批量上传已经足够加消息队列反而徒增复杂度。4.3 Hive建表按日期分区的新闻明细表Hive里建一个外部表按日期分区。外部表的好处是数据文件删了表结构还在不会误删原始数据。CREATE EXTERNAL TABLE IF NOT EXISTS default.news_dim ( news_id STRING, title STRING, content STRING, source STRING, publish_time STRING, category STRING, views BIGINT, comments INT, url STRING ) PARTITIONED BY (dt STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t STORED AS TEXTFILE LOCATION /warehouse/news/warehouse;上传到HDFS后手动增加分区hive -e ALTER TABLE news_dim ADD PARTITION(dt2025-01-20) LOCATION /warehouse/news/warehouse/dt2025-01-20;分区的好处是后续按天统计时只需要扫描对应分区的数据不需要全表扫描。这个特性在新闻这种按天增量明显的场景里非常实用。4.4 从Hive到算法离线特征怎么返给推荐层Hive在项目里的产出主要有三类新闻热度统计表每天每篇文章的浏览量、评论量、发布时间等新闻栏目分布表各栏目内容数量、平均阅读数用于可视化大屏用户行为聚合表从行为日志里统计每个用户的点击偏好、阅读时长。这些离线统计结果不适合直接让前端查因为Hive查询延迟高。标准做法是把统计结果导出到MySQL或Redis用hive -e INSERT OVERWRITE ...或者写一个Python脚本读取Hive结果然后写回业务库。推荐流接口直接读MySQL/Redis秒开可视化大屏的汇总数据则每小时刷新一次完全够用。调度上我直接用Crontab跑一个总脚本先跑爬虫再跑清洗上传HDFS执行Hive ETL最后触发推荐算法任务。整个过程串行执行日志写到固定目录方便第二天排查哪里断了。5. 混合推荐算法拆解热度、协同过滤、内容相似、大模型精排5.1 第一层召回带时间衰减的热度公式新闻推荐里热度是第一生产力。新用户没有行为记录时你没法做个性化只能靠“大家都在看什么”来兜底。但单纯按浏览量排会有一个问题老新闻霸榜。所以热度公式一定要带时间衰减。我这里用的公式是hot_score (a * views b * comments c * collects) / power((now - publish_time).hours 2, 1.5)分子是“质量分”浏览量、评论数、收藏数按权重累加分母是“时间衰减”新闻发布越久热度越低。常数2是为了避免刚发布时除数为0指数1.5控制衰减速度。这个公式不复杂但它在冷启动阶段的表现直接决定了整个推荐系统的下限。权重a、b、c可以按平台调内容站评论权重大一些资讯站浏览量权重大一些。5.2 第二层召回协同过滤 TF-IDF内容相似度热度只解决“大家都在看”解决不了“你喜欢什么”。第二步要做的是个性化召回。基于用户的协同过滤UserCF核心逻辑是找到与当前用户兴趣相似的一群人把他们看过的新闻推荐给当前用户。用户之间的相似度可以用共同看过的新闻数量除以并集新闻数量也可以根据行为加权。新闻场景有个明显问题多数用户读过的东西很少交互矩阵极其稀疏纯UserCF容易抓瞎。基于内容的召回对每篇新闻做分词、TF-IDF向量化计算新闻之间的余弦相似度。用户读完一篇“Hadoop入门”的新闻系统就可以找向量最接近的其他技术文章推荐过去。这个方案不依赖用户间行为冷启动优势明显但缺点是会给用户推同质化内容容易形成“信息茧房”。所以项目里不是选二者之一而是把两个召回结果合并UserCF给Top N内容相似给Top N再加上热度Top N三个来源并集形成候选集送到下一层精排。5.3 第三层精排大模型做语义匹配和推荐理由候选集可能有几百条用户只能看到二三十条怎么排序这层我引入了大模型。大模型在这里不是做在线实时推理那延迟太高了。我在项目里用本地部署的开源对话模型7B/13B级别均可离线批量给候选新闻打分。具体流程是从用户画像中提取兴趣标签和最近读过的5条新闻标题把候选新闻的标题、摘要、分类拼接成提示词让模型输出0到10的匹配分批量跑完后将结果写入Redis在线推荐时直接读取分数。提示词大致是这样用户近期关注机器学习、房价走势、新能源汽车 候选新闻标题某品牌发布新款电动车续航超过700公里 请根据用户兴趣输出这篇新闻对该用户的匹配程度0到10分只输出数字。模型输出可能是“8”解析后归一化作为精排分。除了打分也可以让模型生成一句“推荐理由”像“因为你对新能源车持续关注这条消息值得一看”这个细节在演示时非常加分。大模型还有两个很实用的点一是给新闻打语义标签比如“科技/财经/体育”这种粗分类很容易分但“ChatGPT”“AIGC”“大模型”这种语义标签需要模型理解能力二是用文本相似度判断两条标题不同、内容相同的新闻是否属于同一事件做去重。5.4 混合权重、冷启动和实时反馈修正最终分数用加权混合final_score 0.20 * hot_score 0.30 * user_cf_score 0.25 * content_score 0.25 * llm_match_score这个权重不一定通用。你可以先跑一个月数据用离线测试集做网格搜索选CTR预估最准的一组权重。冷启动阶段用户没有任何行为时user_cf和content_score都没法算权重直接切到热度和当日大事件上等用户产生行为后再逐步切换到个性化权重。还要留一个“实时反馈修正”通道用户点击一条科技新闻那么他候选列表里同类内容的权重立即上调5%不喜欢/点踩的内容权重下调。这个可以在Django接口层临时塞一个bias参数实现不用重新跑整个离线流程。6. Django后端接口与Vue可视化联动6.1 推荐API怎么设计feed、hot、behavior三条主线后端接口不需要设计得非常复杂三条主线就够用推荐流GET /api/recommend/feed?user_idxxxpage2返回当前用户当前页的新闻列表每项包含标题、摘要、来源、发布时间、推荐理由热度榜GET /api/news/hot?categorytech返回某个栏目下按热度分排序的新闻可供“热点新闻平台”页使用行为上报POST /api/user/behavior接收user_id、news_id、behavior_typeview/collect/comment、duration、timestamp写入行为表。用户体系这里可以做得轻一些注册登录用Django自带User模型匿名用户生成一个session_id作为临时用户行为照样上报。匿名用户第一次打开首页时接口返回热度榜内容并提示“登录后获得个性化推荐”。6.2 推荐结果是怎么从Hadoop流向Django的流程是这样的离线算法任务跑完后把每个活跃用户的推荐列表写进MySQL的一张recommend_result表字段包括user_id、news_id、score、reason、expire_at同时把热度榜和当日推荐理由写入Redis。Django接口收到请求后优先查Redis缓存缓存过期或没有该用户数据时再查MySQL。Redis的key可以设计成recommend:user:{user_id}:{page}过期时间设为30分钟。这样避免每次刷新都打MySQL也给了推荐结果“定时更新”的窗口——最多每30分钟刷新一次推荐用户体感是“推荐居然在跟着我看的东西变”。6.3 Vue页面结构新闻流、详情页、可视化大屏Vue这边我分了三个主要模块新闻流页面进入首页自动请求推荐接口下拉触底加载下一页。每条新闻卡片可以展示标题、摘要、来源、推荐理由。点“不感兴趣”按钮前端把这个行为上报后端下次刷新该条新闻会被过滤掉。新闻详情页展示正文同时给出“相关推荐”栏目调用基于内容相似度计算的接口推荐3-5篇相关新闻。可视化大屏如果项目需要演示建议单独路由做一个/dashboard页面用ECharts展示五块内容——当日新闻数量趋势、栏目热度分布、新闻来源占比、用户阅读偏好词云、推荐点击率折线。数据来源是后台统计接口每小时更新一次。6.4 联调阶段最常踩的跨域和字段坑前后端分离项目最常见的坑是跨域。Django里直接装django-cors-headers在settings.py里配置INSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, ]Vue开发服务器默认端口5173Django默认8000两个端口不一致不配跨域请求必挂。这个坑几乎每个第一次做前后端分离的人都会踩。字段命名也要统一。数据库里用news_id接口里就不要一会儿newsId一会儿id推荐理由字段统一叫reason前端组件写死。建议接口返回前先打印一份JSON检查结构别等到页面渲染报错再回来翻后端代码。7. 实测表现、效果评估和几个真实翻车场景7.1 离线指标和在线指标怎么定推荐系统不能靠“看起来推荐得挺准”来验收。我实际操作时用了两类指标离线指标用历史行为数据切训练集和测试集看召回率、准确率、覆盖率。覆盖率特别重要如果推荐结果永远来自那几百篇热度高的新闻说明个性化没生效。我会统计“推荐列表里非Top200热门新闻的占比”目标是超过30%。在线指标记录用户曝光量、点击量、收藏量、平均停留时长。核心就是CTR点击率公式是点击次数 / 曝光次数。跑混合推荐之后CTR应该明显高于纯热度榜。我当时对比了两周数据混合推荐CTR提升了大概15%-20%。如果只是课程设计不需要做这么细但答辩时能说出“离线覆盖率从20%提到35%在线CTR比热度榜提升17%”这种数据说服力比“用了协同过滤算法”强太多。7.2 三个让我半夜改代码的翻车场景翻车场景一爬虫跑了一小时IP被限制。症状是页面开始弹验证码所有请求进入死循环。解决办法是加代理池 降低抓取频率。代理池可以用免费代理列表但质量不稳定更稳的方案是拉长抓取间隔让单IP的整体请求量控制在每天几千次以内新闻站基本不会有意见。翻车场景二冷启动用户打开首页是空的。我最初把推荐接口设计成“先取UserCF结果取不到就返回空列表”结果新用户注册进来首页一片空白。后来加了两条保底逻辑第一任何用户都能拿到热度榜Top50第二如果用户有过一次点击立即用内容相似度补30条候选。诡异的是这个bug在开发环境一直没暴露因为开发环境我账号里已经攒了很多行为数据。所以测试新用户流程一定要从零开始。翻车场景三大模型在线打分太慢接口超时。一开始我天真地在推荐接口里直接调大模型推理一条候选就要几百毫秒几十条候选算下来接口奔着10秒去了。后来改成离线批量打分把所有用户候选集一次性拼批喂给模型结果写入Redis接口从10秒优化到200毫秒。这个优化也解释了为什么项目架构里“大模型”一定要放在离线任务里不能放在请求链路上。7.3 这套系统还能往哪些方向扩展如果你做完之后还有余力有几个方向性价比很高实时热度加一个WebSocket推送把用户正在浏览事件的实时热度变化推到大屏上视觉冲击力很强。更细的用户画像除了栏目偏好还可以用大模型抽取用户阅读内容的语义关键词做成词云展示。混合推荐权重自动调优写一个简单脚本每天根据前一天的CTR表现自动微调几个混合权重让系统看起来“会自我进化”。新闻内容去重同一事件不同网站的报道很多用大模型判断“是否为同一事件”把相似新闻聚成一簇推荐时只推最优质的一篇。整套系统跑通之后我最大的感受是推荐系统真正难的不是算法而是数据闭环。新闻推荐尤其见真章——用户点了一下你下一次刷新必须给出反应如果做不到这条再花哨的算法都只是演示动画。所以不管你是为了答辩还是为了入门大数据动手做之前一定先把数据流画清楚这比急着写代码重要得多。后面扩展的时候优先考虑实时流处理和更完整的用户画像那才是这套系统“从能跑变成好用”的下一级台阶。