news 2026/9/18 10:26:36

用户画像基础全解析:从ID打通到标签体系落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用户画像基础全解析:从ID打通到标签体系落地

简介:这份《用户画像基础》PDF聚焦互联网行业用户画像的完整知识框架,适合数据产品、数据分析与运营人员系统入门。内容从画像定义与标签体系讲起,覆盖统计类、规则类、机器学习挖掘类三类标签,并延伸到数仓分层、Spark/Hive/HBase/MySQL/Redis/Elasticsearch等基础设施,以及用户画像八大模块与项目开发上线流程。资源共1个文件,类型为PDF,压缩包大小约795KB,便于随时翻阅。目前已有1015人学习下载,可作为搭建用户画像体系、规划标签开发任务和开展精准运营的参考手册,尤其适合需要理解数据如何从仓库走向业务应用的学习者。

1. 用户画像基础是什么:先解决“标签打架”问题

做过三年以上的数仓或推荐工程师,多半见过这种场景:运营要“高价值用户”,数据团队从订单侧给出高消费用户,从访问侧又给出高频活跃用户,两批标签词面相近,人群重合率却不到六成。问题不出在算法,而出在“用户画像基础”没打牢。用户画像基础不是拉一张宽表、堆几十个特征,而是把用户标识、标签口径、计算时效、访问性能串起来的一套规范。本文沿着这个标题,从数据底座、标签体系、批量计算到线上存储,讲清一套可复现的画像基础方案。读者如果正在搭画像平台,或者被业务疑问追问到怀疑人生,本文能给你一张完整的地图。

2. 用户画像基础的数据底座:ID打通与数据分层

在建立任何标签之前,先要回答一个问题:一个用户到底是谁。移动端场景下,同一个用户可能用手机号登录过一次,老版本App里一直以device_id匿名访问,后来微信授权又生成一个新的业务ID。如果只按单一ID聚合,画像宽表里会有一模一样的物理用户出现多行,标签也随之分裂。

2.1 先梳理用户标识:u_id、device_id、cookie

常见的平台内标识有登录用户ID、设备ID、Cookie ID、手机号、第三方OpenID。它们产生的场景不同,可信度也不同。建议先把这些标识列成一张枚举表,再决定映射关系。

标识产生场景更新频率可信度典型问题
userId注册/登录低频未登录用户无此标识
device_idApp首次启动固定不变中高可被清缓存或刷机识别为新人
cookie_idWeb/H5访问会话级浏览器清Cookie即丢失
手机号下单/绑定极低换号后影响连续归因
openid微信/支付宝授权低频同一人在不同开放平台id不同

我的习惯是:在ODS层保留原始标识,不直接修改;在DWD层建一张dim_user_id_mapping映射表,以userId为主键,把device_id、cookie_id都指向这个主键。未登录用户先以device_id作为占位主键,等业务侧触发登录后再合并身份。这样既能上报匿名数据,又能保证登录后画像连续。

2.2 数仓分层与标签宽表设计

画像基础表通常放在DWS层,因为它属于轻度汇总的公共维度数据。数据链路建议是:ODS统一收集业务日志 → DWD清洗成行为明细 → DWS加工用户维汇总层 → ADS面向应用提取标签。宽表不是越宽越好,字段过多会降低查询效率,也会让下游依赖变得脆弱。

下面是一张最小可用的日级画像宽表DDL,覆盖了事实指标和简单标签:

CREATE TABLE dws.user_profile_daily ( user_id STRING COMMENT '统一用户ID', first_seen_date STRING COMMENT '首次出现日期', last_active_date STRING COMMENT '最近活跃日期', order_cnt_30d BIGINT COMMENT '近30天下单次数', order_amt_30d DECIMAL(12,2) COMMENT '近30天下单金额', tag_value_level STRING COMMENT '价值分层标签', tag_channel_prefer STRING COMMENT '渠道偏好标签' ) PARTITIONED BY (dt STRING COMMENT '日期分区') STORED AS PARQUET;

之所以用PARTITIONED BY (dt),是为了按天刷新全量快照,便于下游按分区读取。存储使用Parquet,一方面压缩比高,另一方面按列裁剪能减少查询IO。宽表里的字段分三类:用户身份字段、事实聚合字段、派生标签字段。事实字段尽量保留原始聚合值,派生标签则可以随时由事实字段重算。

2.3 最小可跑的ID-Mapping SQL

下面这个SQL例子,可以把登录日志和设备注册表合并成统一映射。它只处理两类来源,但思路可以扩展:

INSERT OVERWRITE TABLE dwd.dim_user_id_mapping SELECT COALESCE(a.user_id, b.reg_user_id) AS user_id, COALESCE(a.device_id, b.device_id) AS device_id, CURRENT_TIMESTAMP() AS update_time FROM ( SELECT device_id, MAX(login_user_id) AS user_id FROM dwd.app_login_log WHERE dt = '${bizdate}' GROUP BY device_id ) a FULL OUTER JOIN ( SELECT device_id, user_id AS reg_user_id FROM dwd.app_device_register WHERE dt = '${bizdate}' ) b ON a.device_id = b.device_id;

逻辑说明:子查询a从登录日志中取每个设备最近登录过的一个用户ID,子查询b从设备注册表取注册用户ID。FULL OUTER JOIN保证只出现过一次访问行为的设备也能得到一行映射。COALESCE两边都适用,因为JOIN的两侧都保留device_id。实际生产环境比这复杂得多,还要处理一台设备多人使用、多个设备对应一个用户等情况,但核心思想就是先把已知登录行为作为可信证据,再逐步用规则合并。

3. 用户画像基础的标签体系:从维度到权重的设计

ID打通之后,画像基础的核心就是标签体系。很多团队的标签表里几百个字段,但业务方不知道每个字段的准确含义,数据团队自己也不敢改口径。要避免这种局面,得从标签分类、命名、生命周期三个维度先定规矩。

3.1 标签的三种类型:事实、规则、模型

用户画像标签可以从加工程度上分成三类。事实标签直接从行为明细聚合而来,规则标签基于逻辑判断,模型标签则需要训练或打分。三者各有取舍:

类型典型示例更新频率优点缺点
事实标签最近购买时间、累计消费金额日级/小时级准确、可解释相对用户意图滞后
规则标签高价值用户、沉睡用户日级口径灵活、易调整阈值依赖运营经验
模型标签价格敏感度、流失概率周级/月级覆盖广、预测性强需要训练评估和模型维护

我在项目里偏向把事实标签和规则标签优先做,因为它们在早期就能快速产生业务价值。模型标签要等数据积累到一定量级再上,否则样本太少,模型输出的分数会震荡。

3.2 标签命名、口径与生命周期

标签命名是团队协作的基础。推荐格式为:{业务域}_{标签名}_{周期}_{类型}。例如trade_order_amt_30d_fact表示交易域近30天下单金额事实标签,trade_value_level_rule表示交易域价值分层规则标签。这种命名让数据字典可以自动关联,也方便下游按前缀权限管控。

标签口径必须沉淀成文档。我的习惯是维持一张“标签口径表”,字段包含标签名、中文名、定义描述、口径SQL、负责人、更新状态。这样当业务方质疑“为什么用户A是高价值,用户B不是”时,能直接打开文档看到判断阈值和SQL,而不是听口头解释。

生命周期也要区分对待:日更新标签跑T+1任务;月更新标签只做月末调度;一次性标签必须带版本号,避免下次跑数把历史人群刷掉。无论哪种,都要保证万一任务失败时,上一份数据不被立刻覆盖。

3.3 用Python生成标签权重衰减

模型类标签经常需要对用户行为做时间衰减,否则三个月前一次大额购买会永远把用户顶到“高价值”。一个常用的衰减函数是半衰期指数衰减:

import math def decay_weight(days_ago: int, half_life: int = 30) -> float: """按距离今天的天数计算事件权重。 half_life=30 表示30天前的事件权重为0.5,60天前为0.25。""" return 0.5 ** (days_ago / half_life)

使用示例:计算某用户对“3C数码”的加权兴趣分,浏览记0.3分、加购记0.6分、下单记1.0分,再乘以时间衰减系数:

action_score = { "view": 0.3, "cart": 0.6, "order": 1.0, } user_actions = [ (3, "order"), # 3天前下单 (12, "cart"), # 12天前加购 (45, "view"), # 45天前浏览 ] score = sum( decay_weight(days_ago, half_life=30) * action_score[action] for days_ago, action in user_actions ) print(f"全部行为加权后的兴趣分: {score:.3f}")

逻辑说明:decay_weight的底数取0.5,意味着每个半衰期周期权重减半。half_life是核心参数,调小会让衰减过快,适合短期促销活动;调大则保留较长历史记忆,适合用户生命周期价值预测。要注意,这种连续衰减对周期性行为(比如每周固定购物)不友好,那种场景建议先做“周期内归一化”,再叠加周期间衰减。

4. 用户画像基础的计算与存储:批量跑数与线上读取

标签定义完之后,就要考虑怎么算、怎么存。画像基础数据的消费者一般分两类:离线报表、数据开发,他们能接受T+1延迟;线上推荐和实时风控,他们要求毫秒级拿到标签。所以计算和存储要分层设计。

4.1 用Spark SQL从明细加工标签宽表

假设DWD已有订单明细表dwd.fact_order_detail,可以用Spark SQL生成前面那张宽表。下面是一个典型模板:

INSERT OVERWRITE TABLE dws.user_profile_daily PARTITION (dt = '${bizdate}') SELECT t.user_id, MIN(t.order_date) AS first_seen_date, MAX(t.order_date) AS last_active_date, COUNT(IF(t.order_date >= date_sub('${bizdate}', 30), t.order_id, NULL)) AS order_cnt_30d, SUM(IF(t.order_date >= date_sub('${bizdate}', 30), t.amount, 0)) AS order_amt_30d, IF(SUM(IF(t.order_date >= date_sub('${bizdate}', 30), t.amount, 0)) > 5000, 'high', 'normal') AS tag_value_level FROM ( SELECT user_id, order_id, amount, order_date FROM dwd.fact_order_detail WHERE dt >= date_sub('${bizdate}', 90) ) t GROUP BY t.user_id;

逻辑说明:COUNTSUM里的IF不是过滤,而是在用户维度内做条件聚合。放在WHERE里会把没有近30天订单的用户整行滤掉,导致宽表缺失“沉睡用户”类标签。外层查询拿到90天明细,指标只统计近30天,这样既能保留所有有历史行为的用户,又不会让30天外的数据污染指标。

4.2 画像结果落到HBase和Redis

离线宽表适合数据分析师跑SQL,不适合线上服务做高并发读取。常见做法是把画像标签灌入HBase,rowkey采用反转的user_id。

HBase表:profile_daily rowkey:reverse(user_id) -- 示例:10091220 -> 02219001 column family:attr qualifier:order_cnt_30d, tag_value_level, tag_channel_prefer TTL:按实际业务保留30~60天

rowkey反转是HBase常用技巧,让前缀散列在Region上,避免连续userId写爆同一个Region。对毫秒级读取要求更高的标签,可以再同步一份到Redis:

redis-cli SET profile:10091220 '{"order_cnt_30d":3,"tag_value_level":"high"}' EX 86400

这里的EX 86400表示缓存一天,配合离线任务凌晨刷新,白天服务读取Redis时不会命中过期数据。如果标签多且频繁访问,建议改成Redis Hash,一次性HMGET多个字段,减少网络往返。

4.3 调度与参数调优

批处理任务通常要挂在调度平台上。如果用crontab加Spark提交,一个最小化的任务形如:

30 2 * * * spark-submit \ --master yarn \ --deploy-mode client \ --executor-memory 8G \ --executor-cores 4 \ --num-executors 20 \ --conf spark.sql.shuffle.partitions=400 \ profile_daily_job.py --bizdate $(date -d 'yesterday' +%F)

几个关键参数说明:--executor-memory 8G--executor-cores 4决定每个Executor的计算资源,--num-executors 20控制并行度;spark.sql.shuffle.partitions直接影响JOIN和GROUP BY的reduce数量,设置过小会OOM,设置过大会产生大量小文件。小表数据可以先广播,例如在Spark代码里加spark.sql.autoBroadcastJoinThreshold配置,把维表大小阈值调高些。

此外,建议每天对画像任务做“新数据量和空分区”巡检。空分区常常意味着上游日志停送,如果忽略,画像会静默变差。

5. 用户画像基础的验证:覆盖率和准确率怎么算

标签表和指标表不同,指标有业务原始定义,标签则由多层加工产生,一旦口径错了,下游很难察觉。因此在输出给应用方之前,至少要看两个指标:覆盖率和准确率。

5.1 覆盖率:画像覆盖了多少用户

覆盖率的定义不能简单写成COUNT(*) / 总用户数。分母不同会得到完全相反的结论。我一般按业务用途区分分母:用于站内推荐的分母取近30天有访问行为的活跃用户;用于短信营销的分母取近30天有真实手机号的用户;用于全量统计的分母取注册表全部用户。下面这个SQL按价值标签计算覆盖率:

SELECT tag_value_level, COUNT(*) AS tagged_user_cnt, COUNT(*) / MAX(total_ucnt) AS coverage_rate FROM ( SELECT p.user_id, p.tag_value_level, (SELECT COUNT(DISTINCT user_id) FROM dws.user_profile_daily WHERE dt = '${bizdate}') AS total_ucnt FROM dws.user_profile_daily p WHERE dt = '${bizdate}' ) t GROUP BY tag_value_level;

逻辑说明:内层子查询把总用户数和每个用户行绑定在一起,MAX(total_ucnt)GROUP BY后只取到同一个总数,不会因分组被放大。coverage_rate可以比较不同标签之间的填充度,如果某标签覆盖率低于预期,要回查是数据源缺失,还是加工逻辑里的过滤条件太严格。

5.2 准确率:抽检回来了

覆盖率说明标签有没有,准确率说明标签对不对。实际操作中不可能校验全量,通常按用户ID分层随机抽取100~200个样本,由业务方对每个标签做“是/否/存疑”的三分类判断。判断结果回来后,用Python快速算一个准确率:

import random check_result = [] # 模拟抽检结果,实际应来自人工标注 # 每条记录格式: {"user_id": "1001", "pred": "high", "label": "high"} correct = sum(1 for r in check_result if r["pred"] == r["label"]) accuracy = correct / len(check_result) print(f"抽检准确率: {accuracy:.2%} 抽检量: {len(check_result)}")

这里最容易被忽略的是“存疑”样本。如果一个标签在业务方眼里经常“存疑”,说明标签定义对业务无感,要么改定义,要么拆成多个更细的标签。准确率抽检不能替代口径文档,它是口径文档的日常校验手段。

5.3 用A/B测试评估画像效果

标签好不好,最硬的标准是它能否驱动业务指标。常见做法是做一个面向策略的小流量A/B测试:实验组使用画像标签分流,控制组保持原有策略。假设要验证“高价值用户标签”的优惠券转化效果,最后得出四格表,可以用卡方检验判断差异显著性:

from scipy.stats import chi2_contingency # 每行是 [消费人数, 未消费人数] table = [ [experiment_convert, experiment_non_convert], [control_convert, control_non_convert], ] chi2, p_value, _, _ = chi2_contingency(table) print(f"p_value = {p_value:.4f}")

解释:p_value < 0.05说明实验组和对照组差异不是随机波动,画像标签对业务有可度量影响;反之则说明该标签目前没有实际价值,需要调标签侧策略,而不是急着推广。让画像标签直接背业务指标很危险,建议观察至少一个完整的业务周期,避免新功能带来的短期波动。

6. 把用户画像基础沉淀成PDF:从标签文档到可评审的交付物

数据文档比代码更容易过期,但用户画像基础恰恰需要一份跨团队能评审、能归档的静态文档。我习惯把所有标签口径表、ID映射说明、宽表DDL和验证结果放在同一个Markdown文件里,再转换成PDF版本,文件名带上日期和版本号。

生成PDF最省事的方式是用pandoc加xelatex引擎。在装有TeX发行版的Linux或macOS环境中,运行下面命令即可:

pandoc user_profile.md \ --pdf-engine=xelatex \ -V mainfont="Noto Sans CJK SC" \ -V monofont="Noto Sans Mono CJK SC" \ -V geometry:margin=2.5cm \ -o 用户画像基础_v1.0_20250608.pdf

参数说明:--pdf-engine=xelatex负责把Markdown里的中文内容编译成PDF;mainfontmonofont指定中文字体;geometry:margin=2.5cm控制页边距,文档内容多时可以把边距调小,内容少时调大。如果不想安装TeX,也可以先用pandoc生成HTML,再用wkhtmltopdf

wkhtmltopdf --enable-local-file-access --footer-center '[page]' user_profile.html 用户画像基础.pdf

生成PDF后,建议把每一张标签口径表截图放进去,并在文档末尾附上最近一次覆盖率验证结果。这样团队评审时不需要打开数仓,单靠一份PDF就能统一对“用户画像基础”的理解,后续人事变动时也能保证知识和业务口径不流失。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 10:26:05

从PDF到API:古诗词文档清洗与学习系统构建实践

简介&#xff1a;这份PDF汇总了人教版小学语文必背古诗词75首&#xff0c;按汉乐府、唐诗、宋诗等经典篇目编排&#xff0c;覆盖《江南》《静夜思》《望庐山瀑布》《悯农》等常考诗篇&#xff0c;适合小学生、家长及语文教师作为日常诵读与考前复习的便携清单。文件为1个PDF文档…

作者头像 李华
网站建设 2026/9/18 10:22:04

3ds Max 2026零基础实操地图:从安装卡顿到施工图交付

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:21:28

Comsol仿真太赫兹热可调超材料:VO₂与InSb建模全流程

去年我在Comsol里跑通了一个太赫兹超材料模型&#xff0c;材料体系用的是二氧化钒&#xff08;VO₂&#xff09;和锑化铟&#xff08;InSb&#xff09;&#xff0c;核心玩法是“热可调”。当时目标很直白&#xff1a;在0.5~2 THz这个频段&#xff0c;用温度把结构的透射响应从“…

作者头像 李华
网站建设 2026/9/18 10:15:19

SLF4J与SpringBoot日志系统深度解析

1. SLF4J在SpringBoot中的核心价值作为Java生态中最主流的日志门面框架&#xff0c;SLF4J(Simple Logging Facade for Java)在SpringBoot项目中扮演着关键角色。不同于直接使用Log4j或Logback等具体日志实现&#xff0c;SLF4J通过门面模式提供统一的日志API&#xff0c;这种设计…

作者头像 李华