摘要
教育机构私域池中积累了大量的"沉默用户"------试听后未转化、加好友后未互动、领资料后未激活。如何低成本唤醒这些用户,是提升整体ROI的关键。本文提出基于企业微信API与数据中台对接的用户分层与自动化召回方案。通过RFM模型对用户进行分层,针对不同层级的沉默用户设计差异化的召回策略,利用脚本实现定时扫描、条件判断与批量触达,将沉睡用户激活率提升30%以上。
一、问题背景:沉默用户是"沉睡的金矿"还是"永远的成本"?
从技术运营视角看,私域用户池是一个典型的数据资产,但大多数机构的资产利用率极低:
-
沉默用户占比高:教育行业的典型数据是,试听用户中最终只有5%-10%转化为付费学员,剩余90%以上的用户成为"沉默用户"------他们躺在好友列表里,既不删除也不互动。这些用户占用了员工的好友额度,却没有产生价值。
-
缺乏分层运营:很多机构对所有沉默用户"一视同仁",定期群发同样的广告话术。结果是用户越来越反感,删除率上升,转化率却毫无起色。
-
召回成本高:人工筛选沉默用户并逐一私聊,耗时耗力且效果难以衡量。运营人员更愿意跟进新流入的活跃用户,导致沉默用户被"遗忘"。
-
官方API无用户分层能力:企业微信API虽然支持标签,但无法自动计算RFM(最近互动时间、互动频率、互动价值)等指标,也无法根据用户活跃度自动触发召回流程。
二、技术方案:构建"RFM分层+自动化召回"的沉默用户激活引擎
核心思路是将沉默用户按照价值-沉睡度矩阵分层,针对不同层级设计差异化的召回策略。
🏗️ 方案架构
用户行为数据(消息/互动/点击/购买) → RFM计算引擎 → 分层标签同步 → 召回策略执行
-
数据采集:持续收集用户与企微的互动数据,包括:
-
最后一次消息时间(私聊/群聊)
-
互动频率(周活跃天数)
-
互动价值(是否点击链接、是否咨询、是否购买)
-
-
RFM计算:定时任务(每日凌晨)扫描所有用户,计算RFM分值,将用户划分为:
-
高价值活跃用户:近期有互动,有购买记录
-
潜力用户:近期有互动,无购买记录
-
轻度沉睡:30天内无互动,但有过购买或深度互动
-
深度沉睡:90天内无互动
-
濒临流失:180天内无互动
-
-
标签同步:将分层结果通过企微API写入用户标签,供销售查看。
-
召回策略:根据分层自动触发不同的召回SOP:
-
轻度沉睡:发送行业干货+限时福利
-
深度沉睡:发送问卷调研+大额优惠券
-
濒临流失:发送"告别信"+最后机会
-
⚙️ 技术选型对比
|-----------|------------------|------------|-----------|-------------|
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
| 自研RFM引擎 | 定时任务扫描数据库计算 | 灵活可控,可定制模型 | 开发成本高 | 有数据团队的中大型机构 |
| CDP用户数据平台 | 集成第三方CDP,自动计算 | 功能强大,开箱即用 | 成本高,数据需迁移 | 预算充足的机构 |
| 企微标签手动分层 | 运营手动打标签 | 简单直接 | 无法规模化,滞后 | 小规模机构 |
| 企销宝RFM模块 | 基于iPad协议自动分析互动行为 | 无需开发,自动分层 | 需合规使用 | 追求快速落地的机构 |
本文采用自研RFM引擎+企微API的方案,平衡成本与灵活性。
三、实现步骤:从数据采集到自动化召回的全流程
步骤1:环境准备
-
账号:企业微信已认证主体、MySQL/ClickHouse(存储用户行为数据)、定时任务调度器(Cron/Celery Beat)。
-
工具:Python数据分析库(Pandas/NumPy)、Redis(缓存计算结果)。
-
配置要求:需要能够访问企微API获取用户列表和聊天记录(需开通会话存档功能)。
步骤2:功能配置 ------ RFM模型设计与计算
核心逻辑:将用户行为量化为三个维度,加权计算得出用户价值分。
配置步骤:
-
定义RFM指标:
|-----------|----------|------------------------------------------|------|
| 维度 | 指标 | 计算方式 | 分值范围 |
| R(最近互动时间) | 距离今天的天数 | 1-30天=5分,31-60天=3分,61-90天=1分,>90天=0分 | 0-5 |
| F(互动频率) | 近30天互动天数 | >15天=5分,10-14天=4分,5-9天=3分,1-4天=2分,0天=0分 | 0-5 |
| M(互动价值) | 是否购买/咨询 | 购买过=5分,咨询过但未买=3分,仅点击链接=1分,无价值互动=0分 | 0-5 | -
RFM计算脚本:
python
# rfm_calculator.py - 每日凌晨执行 import pandas as pd import pymysql from datetime import datetime, timedelta def calculate_rfm(): """计算所有用户的RFM分值""" # 1. 从数据库获取用户互动数据 conn = pymysql.connect(host='localhost', user='root', password='xxx', database='wecom') # 获取所有外部联系人 users = pd.read_sql("SELECT external_userid, create_time FROM users", conn) # 获取近180天的互动记录 interactions = pd.read_sql(""" SELECT external_userid, MAX(interact_time) as last_interact, COUNT(DISTINCT DATE(interact_time)) as active_days, MAX(CASE WHEN action_type = 'purchase' THEN 1 ELSE 0 END) as has_purchased, MAX(CASE WHEN action_type = 'consult' THEN 1 ELSE 0 END) as has_consulted, MAX(CASE WHEN action_type = 'click' THEN 1 ELSE 0 END) as has_clicked FROM user_actions WHERE interact_time >= DATE_SUB(NOW(), INTERVAL 180 DAY) GROUP BY external_userid """, conn) conn.close() # 2. 合并数据并计算RFM now = datetime.now() result = [] for _, user in users.iterrows(): external_userid = user['external_userid'] # 查找该用户的互动记录 user_interactions = interactions[interactions['external_userid'] == external_userid] if len(user_interactions) == 0: # 无任何互动 r_score = 0 f_score = 0 m_score = 0 level = '深度沉睡' else: row = user_interactions.iloc[0] # 计算R值 last_interact = row['last_interact'] days_since = (now - last_interact).days if days_since <= 30: r_score = 5 elif days_since <= 60: r_score = 3 elif days_since <= 90: r_score = 1 else: r_score = 0 # 计算F值 active_days = row['active_days'] if active_days >= 15: f_score = 5 elif active_days >= 10: f_score = 4 elif active_days >= 5: f_score = 3 elif active_days >= 1: f_score = 2 else: f_score = 0 # 计算M值 if row['has_purchased'] == 1: m_score = 5 elif row['has_consulted'] == 1: m_score = 3 elif row['has_clicked'] == 1: m_score = 1 else: m_score = 0 # 根据RFM总分和分布确定层级 total_score = r_score + f_score + m_score if total_score >= 12 and r_score >= 3: level = '高价值活跃' elif total_score >= 8 and r_score >= 3: level = '潜力用户' elif total_score >= 5 or r_score >= 3: level = '轻度沉睡' elif total_score >= 2 or r_score >= 1: level = '深度沉睡' else: level = '濒临流失' result.append({ 'external_userid': external_userid, 'r_score': r_score, 'f_score': f_score, 'm_score': m_score, 'total_score': r_score + f_score + m_score, 'level': level, 'calc_date': now.date() }) # 3. 保存计算结果 df_result = pd.DataFrame(result) df_result.to_sql('user_rfm', conn, if_exists='replace', index=False) return df_result if __name__ == '__main__': calculate_rfm() -
同步分层标签到企微:
python
# sync_tags.py - 将分层结果同步到企微标签
def sync_rfm_tags():
"""将RFM分层结果同步为企微标签"""
# 获取最新RFM计算结果
conn = pymysql.connect(...)
df = pd.read_sql("SELECT external_userid, level FROM user_rfm WHERE calc_date = CURDATE()", conn)
conn.close()
# 获取企微access_token
token = get_access_token()
# 为每个层级创建或获取标签ID
level_tags = {}
for level in df['level'].unique():
tag_id = get_or_create_tag(token, "用户分层", level)
level_tags[level] = tag_id
# 批量打标签(注意企微API限制每次最多100个用户)
batch_size = 100
for i in range(0, len(df), batch_size):
batch = df.iloc[i:i+batch_size]
# 按层级分组,每组调用一次打标接口
for level, group in batch.groupby('level'):
external_userids = group['external_userid'].tolist()
# 调用企微API打标签
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch_tag?access_token={token}"
payload = {
"tag_id": [level_tags[level]],
"external_userid_list": external_userids
}
requests.post(url, json=payload)
time.sleep(0.5) # 避免触发频率限制
步骤3:代码实现 ------ 分层召回策略
python
# recall_strategy.py - 分层召回策略执行
from datetime import datetime, timedelta
import random
class RecallEngine:
"""沉默用户召回引擎"""
def __init__(self):
self.executor = ActionExecutor()
def run_recall(self):
"""执行召回任务(每日执行)"""
# 获取不同层级的沉默用户
conn = pymysql.connect(...)
# 轻度沉睡:30天内无互动,但有过购买或深度互动
mild_sleepers = pd.read_sql("""
SELECT u.external_userid, u.level, r.last_interact, r.has_purchased
FROM user_rfm r
JOIN users u ON r.external_userid = u.external_userid
WHERE r.level = '轻度沉睡'
AND DATE(u.last_recall) < DATE_SUB(CURDATE(), INTERVAL 30 DAY)
LIMIT 500
""", conn)
# 深度沉睡:90天内无互动
deep_sleepers = pd.read_sql("""
SELECT u.external_userid, u.level
FROM user_rfm r
JOIN users u ON r.external_userid = u.external_userid
WHERE r.level = '深度沉睡'
AND DATE(u.last_recall) < DATE_SUB(CURDATE(), INTERVAL 60 DAY)
LIMIT 300
""", conn)
# 濒临流失:180天内无互动
losing_sleepers = pd.read_sql("""
SELECT u.external_userid, u.level
FROM user_rfm r
JOIN users u ON r.external_userid = u.external_userid
WHERE r.level = '濒临流失'
AND DATE(u.last_recall) < DATE_SUB(CURDATE(), INTERVAL 90 DAY)
LIMIT 200
""", conn)
conn.close()
# 执行召回
self._recall_mild(mild_sleepers)
self._recall_deep(deep_sleepers)
self._recall_losing(losing_sleepers)
def _recall_mild(self, users):
"""轻度沉睡召回策略:发送行业干货+限时福利"""
for _, user in users.iterrows():
# 根据用户历史偏好选择内容
if user.get('has_purchased'):
# 曾购买过:推送进阶课程
content = f"【专属福利】好久不见!您之前学习的课程现在出了进阶版,限时8折优惠。点击查看:https://yourdomain.com/advanced"
else:
# 未购买:推送行业报告
content = f"【干货分享】我们整理了《2026教育行业趋势报告》,内含10个成功案例,回复"报告"免费领取!"
# 发送消息
self.executor.send_message({
'user_id': user['external_userid'],
'action': {'content': content}
})
# 更新召回记录
self._update_recall_record(user['external_userid'], 'mild')
time.sleep(1) # 控制发送频率
def _recall_deep(self, users):
"""深度沉睡召回策略:发送问卷调研+大额优惠券"""
for _, user in users.iterrows():
# 先发问卷
content = f"【调研有礼】为了给您提供更好的服务,邀请您填写1分钟问卷,填写后领取100元课程优惠券!点击填写:https://yourdomain.com/survey"
self.executor.send_message({
'user_id': user['external_userid'],
'action': {'content': content}
})
# 3天后若用户填写了问卷,自动发放优惠券(需监听问卷填写事件)
# 这部分通过SOP规则实现
self._update_recall_record(user['external_userid'], 'deep')
time.sleep(1)
def _recall_losing(self, users):
"""濒临流失召回策略:发送"告别信"+最后机会"""
for _, user in users.iterrows():
# 情感化召回
content = f"【最后提醒】我们注意到您很久没来了,如果您不再需要我们的服务,请忽略这条消息。如果还想继续学习,回复"1"领取专属回归礼包(内含200元优惠券+精品课程)"
self.executor.send_message({
'user_id': user['external_userid'],
'action': {'content': content}
})
self._update_recall_record(user['external_userid'], 'losing')
time.sleep(1)
def _update_recall_record(self, external_userid, recall_type):
"""更新召回记录"""
conn = pymysql.connect(...)
cursor = conn.cursor()
cursor.execute("""
UPDATE users
SET last_recall = NOW(), recall_count = recall_count + 1, last_recall_type = %s
WHERE external_userid = %s
""", (recall_type, external_userid))
conn.commit()
conn.close()
运行效果说明:
-
每日凌晨,系统自动计算所有用户的RFM分值,并同步分层标签。
-
轻度沉睡用户收到行业干货,若回复"报告"则自动推送资料并打上"已响应"标签。
-
深度沉睡用户收到问卷链接,填写后自动发放优惠券。
-
濒临流失用户收到情感化召回消息,回复"1"后触发优惠券发放。
-
所有召回记录存入数据库,可统计各层级的响应率和转化率。
四、最佳实践与踩坑经验
-
⚠️ 召回频率控制:过度召回会加速用户流失。建议:
-
同一用户每30天最多进入1次召回流程
-
用户对召回无响应(如未回复、未点击)超过3次,自动标记为"无效用户",停止召回
-
用户明确表示"不要再发了"(如回复"退订"),自动加入黑名单
-
-
性能优化:RFM计算可能涉及全量扫描,当用户量达到10万+时,直接查询数据库可能很慢。建议:
-
使用ClickHouse或Elasticsearch存储行为数据
-
计算结果缓存到Redis,供快速查询
-
采用增量计算:只重新计算有新增行为的用户
-
-
💡 踩坑经验:关于会话存档:要获取用户最后一次互动时间,最准确的方式是通过企微的会话存档功能。但该功能需要额外付费且配置复杂。替代方案是记录通过API发送消息的日志和用户点击行为的日志,虽然无法覆盖用户主动发起的会话,但已足够用于RFM计算。
-
召回内容个性化:召回消息的打开率与内容相关度成正比。建议结合用户的历史行为(如浏览过的课程、下载过的资料)进行个性化推荐。例如,用户曾下载过"Python入门"资料,召回时可推送"Python进阶课程"的信息。
-
A/B测试:对同一层级用户,可设计多种召回方案进行A/B测试,持续优化话术和优惠力度。例如,轻度沉睡用户分别测试"干货内容"vs"直接优惠券",看哪种转化率更高。
五、工具推荐:企销宝如何增强用户分层与召回能力
尽管自研RFM引擎能够实现基础的用户分层,但在实际运营中存在两个痛点:
-
数据维度有限:仅靠API记录的消息发送和点击日志,无法全面反映用户活跃度(如用户是否阅读了朋友圈、是否在群内潜水)。
-
缺乏自动化触达工具:召回消息需要个性化内容,且需要监控用户响应,自研开发量大。
企销宝通过iPad协议和RPA能力,提供了更全面的解决方案:
-
技术优势:企销宝能够模拟人工浏览朋友圈,抓取用户的点赞、评论行为,补充RFM计算的数据维度。同时支持自动化朋友圈互动,对沉默用户的朋友圈进行点赞评论,实现"软触达"。
-
智能召回SOP:企销宝内置沉默用户召回模板,可设置多轮召回策略:
-
第一轮:朋友圈点赞互动(低成本触达)
-
第二轮:私聊发送干货(中度触达)
-
第三轮:发送优惠券(强刺激)
-
第四轮:人工介入(若用户有响应)
全程自动化执行,根据用户响应动态调整策略。
-
-
适合场景:适用于大规模私域池(10万+用户)的精细化运营。通过企销宝的自动化分层召回,将沉默用户激活成本降低80%,同时避免人工操作的繁琐和遗漏。
下一篇预告:我们将深入探讨如何利用企微API与数据可视化工具,构建私域运营的数据监控与ROI分析体系,解决"做了没效果"的难题。敬请期待!