微信个人号API实战:私域营销机器人怎么搭任务队列和频控?
营销机器人和客服机器人的最大区别是"主动发消息"——客服是被动等用户来问,营销要主动推活动、推新品、推福利。主动发就有两个必须解决的问题:任务排队和发送频控。做不好要么被限流、要么打扰用户、要么账号被风控。
一、任务队列:三种类型分池管理
营销任务不是一类——单发(给某个用户发一条)、群发(给一批用户发同一条)、定时(未来某个时间发)。三种放同一个队列会乱——单发被群发挤后、定时任务被阻塞。
做法是三池管理:单发任务池按高优先级、群发任务池限并发、定时任务池按触发时间排序。每个池独立速率控制,互不影响。群发任务内部按批次切分,每批之间留间隔,避免一次性几千条冲出去触发限流。
二、动态频控:别用固定间隔
固定 3 秒间隔太僵硬——凌晨没人用微信可以发快一点,白天高峰要降速;老用户可以发频繁点,新注册的发慢一点。动态频控按两个维度调时间间隔:时间段(凌晨 1 秒、高峰 5 秒)、用户新老度(老用户 2 秒、新用户 8 秒)。
频控数据本身也要收集——每条发送记录带 timestamp、目标用户、间隔时长,定期分析频控策略是否合理。
三、灰度放量:先给小批量看效果
第一次跑新活动的群发,别一上来就发几万人。做法是先给 1% 的样本用户发,等 30 分钟看数据——打开率、回复率、投诉率。数据正常再逐步放量到 10%、30%、100%。
灰度期间的频控和正常发送共用一个出口——只是任务队列里标记"灰度"的任务优先度低一些,正常发送不会因为灰度被阻塞。
四、去重与黑名单:别给同一个人重复发
同一活动可能多个运营各自发了一次,或者任务重跑了一次。做法是每条发送任务带唯一 task_id,发送前查用户是否收过这个 task_id 的内容,收过就跳过。
黑名单里的用户(回复过"不要再发"的、投诉过的)永远不进任何营销队列。黑名单维护在一张独立的表,入队前先查。
任务队列与频控对照
| 组件 | 做什么 | 关键点 | 常见坑 |
|---|---|---|---|
| 三池管理 | 单发/群发/定时分池 | 各池独立速率 | 互相阻塞 |
| 动态频控 | 按时间+新老度调间隔 | 间隔实时计算 | 固定间隔僵硬 |
| 灰度放量 | 小样本→逐步放量 | 数据观察窗口 | 一发到底踩坑 |
| 去重黑名单 | task_id去重+黑名单过滤 | 入队前查 | 重复发骚扰 |
营销队列与动态频控实现
import time, random, threading
from collections import deque, defaultdict
class MarketingDispatcher:
def __init__(self, api, store):
self.api = api
self.store = store
# 三池
self.pools = {
"single": deque(),
"batch": deque(),
"scheduled": deque(),
}
self.sent_log = set() # task_id + user_id 去重
self.blacklist = set()
threading.Thread(target=self.run_loop, daemon=True).start()
def add_single(self, user_id, content, task_id):
self.pools["single"].append({
"type": "single", "user_id": user_id,
"content": content, "task_id": task_id, "priority": 2})
def add_batch(self, user_ids, content, task_id, batch_size=50):
# 群发切批
chunks = [user_ids[i:i+batch_size]
for i in range(0, len(user_ids), batch_size)]
for i, chunk in enumerate(chunks):
self.pools["batch"].append({
"type": "batch", "users": chunk,
"content": content, "task_id": f"{task_id}_b{i}",
"priority": 1})
def add_scheduled(self, user_id, content, task_id, fire_at):
self.pools["scheduled"].append({
"type": "scheduled", "user_id": user_id,
"content": content, "task_id": task_id,
"fire_at": fire_at, "priority": 3})
def _dynamic_interval(self, user_id):
# 按时间段+用户新老度算间隔
now = time.localtime()
hour = now.tm_hour
base = 1.0 if 2 <= hour <= 6 else (5.0 if 10 <= hour <= 14 else 3.0)
days = self.store.user_age_days(user_id)
if days < 7: base *= 2 # 新用户更慢
elif days > 90: base *= 0.7 # 老用户可以快些
return base + random.uniform(-0.5, 0.5)
def run_loop(self):
while True:
task = self._pick_next()
if not task:
time.sleep(1); continue
# 灰度任务降优先,先让正常任务跑
if task.get("gray") and random.random() > 0.3:
self.pools[task["type"]].append(task)
time.sleep(0.5); continue
if task["type"] == "single":
self._send(task)
elif task["type"] == "batch":
for uid in task["users"]:
self._send({**task, "type": "single",
"user_id": uid})
# 定时任务由 pick_next 触发
time.sleep(self._dynamic_interval(task.get("user_id", "")))
def _send(self, task):
uid = task["user_id"]
dedup = f"{task['task_id']}:{uid}"
if dedup in self.sent_log: return
if uid in self.blacklist: return
try:
self.api.post("/send", {
"wxid": uid, "content": task["content"][:500]})
self.sent_log.add(dedup)
except Exception as e:
self.store.incr_fail(task["task_id"], uid, str(e))
def _pick_next(self):
# 定时池:找触发时间到了的
now = time.time()
due = None
for t in list(self.pools["scheduled"]):
if t["fire_at"] <= now: due = t; break
if due:
self.pools["scheduled"].remove(due)
return due
# 单发>群发,按优先级
if self.pools["single"]: return self.pools["single"].popleft()
if self.pools["batch"]: return self.pools["batch"].popleft()
return None
落地建议
营销任务按单发/群发/定时三池管理,各池独立速率互不影响,群发切批分批发。频控别用固定间隔,按时间段和用户新老度动态计算,凌晨可以快、高峰要慢、新用户慢、老用户快。第一次跑活动先给 1% 用户灰度,看数据正常再逐步放量。发送前做两件事:task_id 去重、查黑名单,别给同一个人重复发。微信侧的消息接口由Eyun这类个人微信API平台 提供,队列和频控在自建服务实现,具体字段以平台开发文档为准。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)