资讯动态

Python用户画像项目实战:标签体系、TF-IDF与K-Means分群

发布时间:2026/10/2 13:17:21 来源:尧图企业网站定制
简介这是一个面向CSDN用户画像构建的人工智能项目源码包适合具备Python基础与机器学习初步知识的开发者研读。项目完整覆盖用户画像的典型流程先对浏览、收藏、评论等行为数据进行清洗与预处理再利用分词和词向量技术将中文内容转化为可计算的特征最后通过聚类与建模生成用户标签和画像。压缩包共十七个文件以九个Python脚本为主体负责数据分割、预处理、词向量训练以及模型训练等模块另有三个XML工程配置文件、两个TXT说明文档整体仅17KB内容紧凑但逻辑清晰。目前已有111人学习可帮助初学者理解如何从海量用户数据中提炼有价值的信息也可为推荐系统、广告投放或产品个性化优化提供参考实现。读者既能通过代码复现完整流程也能将其改造为其他平台的用户画像基础工具。1. 先别急着跑开源代码用户画像项目到底要交付什么每年到人工智能大作业和课程设计的时间点总能在CSDN上看到“人工智能项目实践用户画像源码”的搜索热词。但下载过这类源码的人都知道包号看着完整实际不是缺数据就是依赖版本旧跑通比翻车还难。用户画像方向本身很落地把用户行为、内容、基础属性加工成一组可解释标签再拿标签去支撑推荐、分群和运营。这篇笔记用一套可复现的方案把CSDN用户画像源码里该有的模块、参数和踩坑点讲透适合正在赶大作业、想入门数据挖掘或要搭画像初版的人。2. 用户画像系统的整体设计与标签体系从CSDN源码常见模块说起CSDN上能跑通的用户画像源码基本不是一个大模型而是一套“统计聚合 规则标签”的流水线。原因是画像的价值在于可解释、可运营。你给运营一个“0.73概率是AI爱好者”不如给“Python 0.8、机器学习 0.7、活跃度 高”这样的标签列表后者能直接决定推哪些内容和发什么权益。所以做这个项目第一件事不是选模型而是把系统分层和标签结构定下来。2.1 画像系统的三层结构数据层、特征层、服务层任何一个能落地的画像系统我习惯分成三层每层只干一件事。数据层负责把原始记录变成一张干净的行为宽表通常包括用户注册信息、文章标题、浏览量、点赞量、访问时间。特征层负责从行为宽表里提取关键词和权重比如对标题做分词、算TF-IDF、按互动量折算行为权重。服务层则把特征结果包装成画像标签输出JSON文件或者提供查询接口。CSDN上很多源码缺少的不是最后的模型而是第一层的数据清洗和第二层的特征口径。层级输入主要处理输出数据层注册信息、文章列表、浏览/点赞日志去重、补齐、时间切分行为明细宽表特征层标题文本、浏览量、点赞量分词、TF-IDF、行为折算关键词权重向量服务层特征向量标签规则、聚类分群用户画像JSON/API这里最容易出错的地方是主键。三层结构里用户画像必须统一以user_id作为主键否则后面合并数据时会大量重复或丢失。源码包常见的翻车点就是把用户文章表和浏览行为表分开处理到最后一步才发现两个表里的user_id类型不一致一个字符串一个整数。这个坑在数据层就要解决不要拖到特征层。数据量不大时用 Pandas 就足够。很多人看到 CSV 文件几十万行就想着上 Spark这是没必要的。几十万行在单机内存里完全能跑反而分布式集群的启动和调度时间比计算时间还长。用户画像项目的数据规模在课程设计和多数中小团队场景里根本到不了需要 Spark 的程度。2.2 标签体系怎么设计CSDN用户画像常用的四类标签用户画像源码里代码是一部分标签体系设计才是更核心的。我一般建议至少输出四类标签基础属性、兴趣标签、活跃程度、用户角色。基础属性直接来自注册字段比如注册年份、是否VIP不需要算法。兴趣标签是核心需要从用户发表的文章或阅读内容里算出来。活跃程度用文章数量和总浏览量分段分成高、中、低三档。用户角色可以用规则判断也可以交给聚类分群结果来命名。user_profile { user_id: 101, base: {register_year: 2023, is_vip: 0}, interest: {Python: 0.85, 机器学习: 0.72, 爬虫: 0.65}, activity: high, role: developer, version: 1, updated_at: 2024-06-01 }这里interest字典里的数值不是概率是相对权重表示该标签在这个用户身上的强度。role字段可以由规则生成比如文章数大于 20 且总浏览量超过 5 万标记为 senior否则是 developer。最后两个字段是版本和时间戳用于追溯画像更新历史这一点很多源码都忽略了。标签体系一定要避免“万能标签”。如果一套词表里全是“编程”“技术”“开发”这类大词出来后每个用户都是全栈工程师业务方根本没法用。要把标签切到“Python”“前端”“缓存”“爬虫”这样的技术领域粒度。CSDN用户画像里常见的做法是维护一份技术词表再配合 TF-IDF 过滤通用词才能让标签之间产生差异化。2.3 技术选型为什么用“规则机器学习”而不是纯深度学习现在很多人工智能项目实践都喜欢上 BERT 或者生成式大模型听起来高级但落地常常拉胯。兴趣标签需要大量用户标注数据课程设计很难搞到几千条人工标注好的用户兴趣深度学习模型产出稠密向量解释性差运营同事看到一串浮点数不知道怎么用。而“规则机器学习”的方案可以先用词典和规则快速产出标签再用 TF-IDF 和 K-Means 从数据里发现人工规则之外的聚类既保证能跑通也保留一定的数据挖掘含量。实现方式可解释性冷启动数据要求适合场景纯规则标签很高好低中小项目、快速上线TF-IDF 聚类高一般中内容型平台深度学习低差很高有标注数据和 GPU文本处理用 jieba因为中文技术文章分词效果好且轻量不需要 GPU。关键词抽取用 TF-IDF因为 TF-IDF 能抑制“实战”“经验”这类出现频次高但区分度低的词。用户分群用 K-Means因为画像标签维度不高、聚类快结果容易映射到业务定义。这套组合也是 CSDN 用户画像源码里最常见的配置可以说能跑通的源码底子差不多都是这样。如果后面数据量大了可以把 TF-IDF 换成 Sentence-BERT 做 embedding但服务层输出的标签接口保持不变这样升级模型不需要改下游。技术选型时要记住画像的下游是运营决策不是论文精度优先保证可解释、好维护。3. 用 Python 实现用户画像核心流程文本清洗、关键词抽取与兴趣标签这一章直接给可运行代码。为了不依赖任何爬虫和外部数据我用一份模拟 CSDN 文章列表的伪数据来演示。真实场景里只要你把这些代码里的 DataFrame 换成从数据库或日志读出来的数据后续流程完全一样。3.1 最小数据构造自己造一份CSDN风格的伪用户数据先构造三到五个用户的行为数据字段和 CSDN 文章列表类似用户ID、文章标题、浏览量、点赞量、发布月份。注意这里刻意只放了三个用户方便演示结果差异。import pandas as pd raw_data [ # 模拟CSDN用户发布的文章记录 {user_id: 101, title: Python爬虫入门实战抓取CSDN博客列表, views: 3200, likes: 120, month: 2024-03}, {user_id: 101, title: 机器学习分类模型调参经验分享, views: 2100, likes: 80, month: 2024-05}, {user_id: 101, title: 用Pandas做数据清洗的10个技巧, views: 4500, likes: 200, month: 2024-07}, {user_id: 202, title: Java多线程编程实战线程池与锁, views: 1800, likes: 60, month: 2024-02}, {user_id: 202, title: Spring Boot集成Redis缓存, views: 3100, likes: 140, month: 2024-04}, {user_id: 303, title: Vue3组件通信方式总结, views: 2700, likes: 110, month: 2024-06}, {user_id: 303, title: 前端性能优化从指标到工具, views: 5000, likes: 230, month: 2024-08} ] user_df pd.DataFrame(raw_data) print(user_df)这段代码建立了一张行为宽表每一行代表一篇文章及其互动数据。views和likes是后面计算行为权重的关键不用归一化因为后面会用 log 处理。实际项目中还可能有“文章分类”“阅读时长”但画像初版不需要太多字段多了反而难以定位口径问题。3.2 文本清洗与分词用jieba提取技术关键词CSDN 风格的技术文章标题特点是中英混排、带冒号顿号、经常出现“实战”“入门”“总结”这些通用词。清洗的目标是把标题里的噪声去掉再用 jieba 切词最后过滤掉不参与画像的泛词。import jieba import re # 停用词表去掉标题里不区分技术方向的泛词 stopwords {的, 了, 和, 是, 与, 实战, 经验, 分享, 技巧, 入门, 总结, 用, 做} # 技术词表优先保留这些领域词 tech_words [Python, Java, 爬虫, 机器学习, Pandas, 线程池, Spring Boot, Redis, Vue3, 前端, 性能优化] def clean_and_cut(title: str): # 去掉标点符号只保留中英文、数字和空格 title re.sub(r[^\w\s\u4e00-\u9fa5], , title) words jieba.lcut(title) words [w.strip() for w in words if w.strip() and w not in stopwords and len(w) 1] # 与技术词表匹配匹配到就保留没匹配到但长度大于1的中文词也保留 result [w for w in words if w in tech_words or (len(w) 4 and \u4e00 w[0] \u9fa5)] return result关键参数是len(w) 1过滤掉单字词避免“的”“是”混进标签。再加上一行“只保留长度等于2的中文词”会让结果过于干净所以我在最后一行用了“在技术词表里或满足条件的中文词”来平衡。实际跑的时候你会发现Python和机器学习都能被保留“抓取”因为长度是2且不在词表里会被丢弃这没关系我们只需要能代表兴趣的头部词。3.3 基于TF-IDF和规则打分给用户打标签分词之后不要直接用词频。词频会被“性能优化”这类常用词带偏。常见做法是先对整个文章标题集合做 TF-IDF再把单个文章的 TF-IDF 向量按浏览量折算后叠加到用户级向量上。from sklearn.feature_extraction.text import TfidfVectorizer import numpy as np from collections import defaultdict # 把分词结果拼成空格分隔的文本供 TfidfVectorizer 使用 user_df[clean_title] user_df[title].apply(lambda x: .join(clean_and_cut(x))) vectorizer TfidfVectorizer(min_df1, max_features5000) tfidf_matrix vectorizer.fit_transform(user_df[clean_title]) feature_names vectorizer.get_feature_names_out() # 初始化每个用户的标签向量 user_vectors defaultdict(lambda: np.zeros(len(feature_names))) for index, row in user_df.iterrows(): vec tfidf_matrix[index].toarray().flatten() # 行为权重浏览量取log点赞量系数较小避免点赞刷榜 weight np.log1p(row[views]) 0.1 * np.log1p(row[likes]) user_vectors[row[user_id]] vec * weight for uid, vec in user_vectors.items(): top_indices np.argsort(vec)[-5:][::-1] tags {feature_names[i]: round(float(vec[i]), 3) for i in top_indices if vec[i] 0} print(uid, tags)这里min_df1表示只要出现一次的词也保留适合小样本演示max_features5000是防止真实数据量很大时向量爆炸的上限。weight的计算方式是项目里常见的 tricks浏览量取 log1p 压量纲点赞量乘 0.1 弱化其影响。因为总浏览量常是点赞量的几十倍如果不做系数调整点赞几乎不参与决策。输出结果大概是 101 用户得到“Pandas”“机器学习”“Python”这类标签202 用户得到“Redis”“线程池”“Java”303 用户得到“性能优化”“前端”“Vue3”。如果发现某个用户标签全空先回到分词函数检查停用词表和词表是否覆盖了文章主题。4. 从标签到画像聚类分群与可视化标签算出来以后画像其实已经完成了 80%。剩下两步一是用聚类看用户画像能不能自然分成有意义的群体二是把画像按规范存储供后续推荐系统读取。4.1 用K-Means对用户分群很多用户画像源码喜欢加一步 K-Means目的不是代替标签而是为了验证标签的区分度。如果聚类中心的关键词高度重合说明标签体系设计有问题。这里对用户向量做归一化再用 K-Means 分三簇。from sklearn.cluster import KMeans from sklearn.preprocessing import normalize # 取出用户向量矩阵并归一化消除文章数量差异 X np.array([user_vectors[uid] for uid in user_vectors]) X_norm normalize(X) kmeans KMeans(n_clusters3, random_state42, n_init10) kmeans.fit(X_norm) # 查看每个簇中心对应的关键词 for idx, center in enumerate(kmeans.cluster_centers_): top_indices np.argsort(center)[-3:][::-1] cluster_tags [feature_names[i] for i in top_indices] print(f簇 {idx}: {cluster_tags})n_clusters3是因为演示数据只有三个用户真实场景可以先用轮廓系数扫一遍 K 值再定。random_state42保证每次运行结果一致。n_init10是 KMeans 做多次随机初始化取最优解避免落到局部最优。聚类结果如果和业务常识一致比如 Python 相关聚到一起、Java 相关聚到一起、前端相关聚到一起说明画像特征有效。新用户上线时用同一个vectorizer把新文章转成向量再调用kmeans.predict就能把新用户分到最像的群。这里有一个隐藏条件向量化的字典必须一致。如果新数据里出现训练时没有的词汇TF-IDF 会忽略它所以不用重新训练向量器。4.2 画像标签的存储与更新JSON 版本管理在线系统里画像数据一般存 Redis 或 Elasticsearch本地项目最简单的方式是存 JSON 行文件一行一个用户。关键是 JSON 里必须带版本和时间戳合并新数据时不要直接覆盖老画像。import json import time def make_profile(uid, interest_dict, total_views, article_count): # 活跃度规则总浏览量超过1万或文章数超过10为高活跃 if total_views 10000 or article_count 10: activity high elif total_views 3000: activity medium else: activity low # 角色规则可用于冷启动阶段的粗分 if article_count 0: role newbie elif article_count 20 and total_views 50000: role senior else: role developer profile { user_id: uid, base: {register_year: 2023, is_vip: 0}, interest: {k: round(v, 3) for k, v in sorted(interest_dict.items(), keylambda x: -x[1])[:5]}, activity: activity, role: role, version: 1, updated_at: time.strftime(%Y-%m-%d %H:%M:%S) } return json.dumps(profile, ensure_asciiFalse, indent2)更新的时候先读取旧画像如果新兴趣标签和旧标签差异较大version加一而不是覆盖。这样后续排查问题时可以看到用户从一个兴趣转向另一个兴趣的路径。很多做过用户画像的工程师都有这种血泪经验上线两周后运营说数据不对找不到历史版本只能从头跑数。保持版本号是那粒后悔药。5. 用户画像项目的5个常见坑跑源码翻车前先看这里CSDN 下载的用户画像源码大部分坑不在模型而在工程细节。下面五条都是我在复现别人的源码或者自己翻车后总结出来的现象、原因、解决一条条对应上。5.1 中文分词加载慢且结果乱词典与停用词缺一不可现象第一次运行jieba.lcut要等十秒切出的结果出现“机器学习”被拆成“机器”“学习”或者“Thread Pool”被切成毫无意义的碎片。原因jieba 默认词典覆盖通用语料但对技术领域词覆盖不足同时没有加载停用词表导致“实战”“经验”“分享”这类泛词进入标签。解决维护一个自定义技术词典在代码开头用jieba.add_word(机器学习)或加载外部词典文件同时准备一个针对技术文章场景的停用词表把“入门”“总结”“教程”“分享”全部过滤掉。这两个动作做完分词结果会稳定很多。5.2 标签阈值一刀切导致所有用户都是“全栈”现象每个用户跑出来的标签都有十几个占了四五个技术方向看起来无所不精实际上没法用。原因只按原始词频取关键词没有做 IDF 加权比如“Python”“编程”这类词很多用户都写单独提高词频后挤掉了真正有区分度的“线程池”“性能优化”。解决用 TF-IDF 用户级加权取 TopN而不是全局固定阈值。对每个用户只保留权重最高的 3 到 5 个标签剩余的舍弃。阈值要根据用户标签分布动态看不要拍脑袋定 0.1。更稳妥的方式是看标签权重的拐点从排序后斜率开始变平的位置截断。5.3 未做数据版本控制画像越更新越乱现象今天更新了画像明天发现某类用户标签从“Python”变成了“Java”但找不到变化发生的时间和原因。原因存储层只保存了最新值旧数据被覆盖。每次更新都重新算全量而不是增量合并导致没有历史痕迹。解决画像 JSON 里加version和updated_at字段定期把版本快照归档到单独目录。增量更新时建议用updates_log表记录用户ID、旧字段、新字段、变更时间。这个成本很低但能救命。5.4 Python 环境冲突CSDN源码大多是老版sklearn写法现象照 README 装完依赖运行到from sklearn.decomposition import TruncatedSVD没问题但调用kmeans.predict报参数名错误或者TfidfVectorizer直接不识别。原因很多源码是两三年前写的依赖的是 scikit-learn 0.24 或更老版本新版本对部分 API 做了兼容性调整比如KMeans的n_init从默认 10 改成了 1某些旧参数被废弃。解决项目必须配requirements.txt并且用虚拟环境安装。建议固定scikit-learn1.0,1.5同时锁定jieba0.42.1、pandas1.5。永远不要图省事在全局环境里跑源码我见过太多因为环境版本导致“源码一跑就崩”的项目。5.5 用户没有行为数据时画像全空冷启动处理现象新注册用户没有任何文章和浏览记录画像输出为空字典下游推荐拿到空串直接报错。原因算法侧没有设计冷启动方案。用户行为数据是为老用户准备的新用户在积累数据之前必须用其他信号兜底。解决给空行为用户打默认标签比如新用户来源页面是人工智能频道就给interest加一个“人工智能”低权重标签没有来源信息就给role: newbie、activity: low。代码里在make_profile参数传入article_count0时先判断interest_dict是否为空为空则填入{新手: 0.1}保证输出永远不是空字典。6. 进阶画像效果怎么验证从命中率到业务收益画像不是生成标签就结束必须验证标签到底准不准。常用的离线验证方法是行为回访命中率用画像预测用户接下来一周会关注哪些技术方向再拿真实点击或发文数据回去对比。我先做一个极小规模的样例便于理解指标含义。# 画像预测的每个用户Top3标签 pred pd.DataFrame({ user_id: [101, 202, 303], top3: [[Python, 机器学习, Pandas], [Java, Redis, 线程池], [前端, Vue3, 性能优化]] }) # 一周后用户真实点击的技术类目 actual pd.DataFrame({ user_id: [101, 202, 303], clicked: [[Python, Pandas], [Java, Redis], [前端, 性能优化]] }) merged pred.merge(actual, onuser_id) merged[hit_rate] merged.apply( lambda row: len(set(row[top3]) set(row[clicked])) / len(row[clicked]), axis1 ) print(merged[[user_id, hit_rate]]) print(平均命中率:, merged[hit_rate].mean())这套代码里hit_rate表示画像 Top3 标签覆盖用户真实点击类目的比例。如果平均命中率低于 0.3优先怀疑两类问题一是标签粒度太粗把所有后端技术混在一个标签里二是行为权重设置不对浏览量挤掉了真正代表兴趣的内容。真实项目里建议至少抽样 500 个用户计算命中率并和随机猜标签的基线对比才能证明画像算法有效。我最早做画像系统时只关注标签好不好看把词云做得五彩斑斓结果运营一用才发现全是“编程”“开发”这类大词分不了群。后来改成“标签必须能被回访行为验证”项目才真正落地。用户画像不是一次性交付而是可以不断迭代的数据资产。希望这篇笔记帮你在复现 CSDN 源码之外把用户画像这条路走通。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑