做私域运营或者客户服务的朋友应该都有这种感觉:企微这边,加客户、建外部群、发欢迎语、做素材库这些环节,早就能靠自动化跑通七七八八了,但每次卡住的地方往往是最后一公里——手里好几个工作号,几十个外部群,通知要发,消息要回,总不能靠人工一个个切号吧。这篇就是这个系列的最终章,专门聊多账号轮巡推送这件事:从账号池怎么搭,到轮巡调度怎么写,再到消息怎么去重、加密ID怎么处理,最后把实际跑起来踩过的坑一并倒出来。适合正在做企微运营自动化、手上已经有一定账号规模、想把这套协同机制彻底落地的读者。
我要先说一句:轮巡推送不是让你搞一批账号去炸群,而是把一个运营团队手里的真实工作号组织起来,各自负责不同的群、不同的时段,用一套调度逻辑代替人工切号。这中间的差距很大,一个是我帮你把通知工作流理顺,另一个是平台重点打击的对象。下面所有的方案,默认前提都是:账号实名、群是你自己管理的群、推送内容合规。
1. 先把需求拆清楚:外部群为啥需要多账号轮巡
1.1 外部群和内部群,运营逻辑完全不同
很多刚开始做企微自动化的人,一开始都是拿内部群练手,发现脚本根本不会被限制,消息想发多少发多少,于是直接把同一套逻辑搬到外部群,结果没两天就出事。原因在于外部群和内部群在平台眼里是两种完全不同的东西。
内部群成员都在企业通讯录里,群是组织内部的协作空间,消息行为天然属于正常工作流,平台基本不太干预。外部群里则混合着客户、合作伙伴、外部联系人,群管理的边界很模糊,平台对外部群的消息频控严格得多,因为这是广告骚扰、恶意营销的重灾区。
这就带来一个现实问题:你手上有一批真实客户在外部群里,售后通知、活动预告、订单提醒都得发,但你不可能让一个账号从早发到晚。一旦触发频控,轻则消息发不出去,重则账号被限制发言甚至被短期封禁,客户群也跟着断联。
1.2 单账号推送的四个瓶颈
单个工作号直接扛所有外部群的推送任务,我实际测下来会遇到四个绕不开的问题。
第一个是发送频率。企微对外部群的主动消息,虽然没有一个公开的具体数字,但实际操作中,一个号在同一个群里连续发几条后,系统就会提示“操作过于频繁,请稍后再试”。我自己的参考经验是:同一个账号在同一个外部群里,两次主动推送间隔至少要留半小时,单日主动推送总量控制在十几条以内相对安全。但这是个经验值,不同企业、不同账号状态差异很大。
第二个是客户体验。一个账号天天在群里说话,时间久了客户会形成“又来打广告了”的条件反射。如果换不同的人来发,群里氛围会更像正常运营而不是机械推送。
第三个是业务隔离。通知、售后、活动、客服应答,如果全混在一个账号里,哪天某个群投诉了,你辛辛苦苦维护的账号直接受影响,所有群一起遭殃。分账号跑,至少能实现故障隔离。
第四个是账号本身的可信度。一个账号的活跃行为过于集中在“对外推送”上,风险是逐渐累积的。多个账号分担之后,每个账号的活跃曲线更像真人,整体更稳。
1.3 轮巡的本质:把团队协作逻辑写成代码
你仔细想一下,真人运营的时候,团队是怎么处理这些群的?客服小张负责华东群,客服小李负责华北群,活动通知由运营统一写好文案,大家分别在各自负责的群里发一遍。这本来就是多账号协作。
轮巡自动化做的就是这件事:把账号按角色拆分,把任务按群重新分配,用调度逻辑决定“哪个账号在哪个时间点发哪条消息”,再把发送结果记录下来。它是对真实协作流程的编码,而不是凭空造一套轰炸工具。
我一直跟来问自动化方案的人强调一个判断标准:如果你的自动化和真人运营的节奏差不多,只是把重复动作省掉了,那就没问题。如果自动化能实现真人做不到的事,比如一分钟内几十个群一起发,那就要警惕了,因为平台也会这么想。
2. 动手前的地基:账号池该怎么做
2.1 账号角色划分与配比
轮巡推送的第一步不是写代码,而是把账号分成角色。我建议至少分成三类。
主运营号负责日常群内互动、答疑、关键通知,是群里最有存在感的那个。通知号只负责定时推送固定内容,比如日报、活动预告,不参与群聊。客服号独立承接用户私聊和群内艾特,跟营销推送彻底隔离。
配比上,先跑起来的话,3到5个账号足够了,不用一上来搞一堆。我见过有人准备了十几个账号,结果调度复杂度指数上升,一个没协调好,反而触发风控。核心原则是:每个账号的业务边界清晰,推送任务分散,单账号负载低。后续确实跑不开了再加,稳定优先。
2.2 登录态、凭证与设备环境的保存
多账号意味着多个登录态要并存,这一步最容易被忽略。很多自动化框架会缓存会话凭证,如果你把这堆凭证随手放在项目目录里,后面等着你的就是各种诡异的串号、失效、被顶下线问题。
我的做法是单独的配置管理模块,统一管理每个账号的 session 文件路径、缓存目录和需要用的企业凭证。有几个要点:
- 凭证不要硬编码在代码里,用环境变量或者专门的密钥存储,避免代码仓库泄露。
- 每个账号的缓存目录互相独立,不要共用同一个临时目录,避免登录态串混。
- 登录设备要固定,今天这台机器,明天那台机器,后台会看到登录环境频繁变化,这本身就是风险信号。
- 写一个会话健康检查任务,定时探测每个账号的登录态是否有效,失效了立刻标记为不可用,不要等发送时才发现。
2.3 运行环境与网络出口的隔离
这里要特别当心。多个账号如果挤在同一台电脑的同一个目录、同一个进程里跑,后台很容易识别出这些账号的操作行为高度一致,可信度会直线下降。不是说这样一定会出事,而是它让你暴露在风险里,完全没必要。
常见做法是不同业务线的账号分容器、分虚拟机、分目录运行,至少保证会话数据互不相通。如果只有一台机器,最少也要做到每个账号一个独立的运行实例,退出后状态不串场。网络出口的事情同样重要,我这里不建议你去碰那些不正规的网络工具,而是建议从业务流程上区分:比如华东团队负责的账号在华东的办公网络里跑,华北团队的账号在华北的办公网络里跑,让账号行为在地理和网络环境上跟真实业务匹配。这个逻辑听起来简单,但很多团队根本没在意,直到出了风控问题才反应过来。
2.4 三条合规底线先刻在脑子里
正式动手之前,有三条底线我建议你打印出来贴在工位上。
账号必须是真实注册、实名认证的工作号,这是最基本的门槛。凡是来路不明、批量注册的账号,在任何自动化方案下都是雷。群必须是你的团队自己创建或拥有管理权限的群,你要对群内的推送行为和内容负全部责任。推送的内容必须合规,不发诱导分享、不发虚假宣传、不收集客户敏感信息。
这三条不是形式主义,而是整个自动化的地基。地基歪了,后面调度写得再漂亮,也只是在雷区里跳舞。
3. 核心机制:调度、去重、加密ID那点事
3.1 轮巡调度策略,选哪种更稳
轮巡听起来简单,不就是账号轮着来吗,但实际上有几种做法,效果差别很大。
最简单的方案是纯轮询:一个账号列表,来一个任务,顺序选下一个账号。这种方案适合只有两三个账号、一小撮群的场景,但群多了以后问题就来了——同一个群里可能刚轮完一圈又来一遍,两个账号短时间内连续给同一群发消息,既不专业也容易被系统盯上。
我实际在用的是分组排队调度,核心思路是每个群绑定一个主账号,主账号达到当日配额或者处于冷却期时,才自动切换到备用账号。冷却期就是发送后的一段时间内,该账号对同一个群不再发送,我给默认值设的30分钟。配额同理,每个账号每天在所有外部群的推送总量设一个上限,比如15条,到了就停。这个做法有两个好处:一是群里收到消息的频率是可控的,不会忽密忽疏;二是每个账号的负载是均匀的,不会出现某个账号天天满负荷。
还要加一个随机化。开发定时任务的时候,我习惯在固定的调度间隔上随机加几秒到十几秒的抖动,避免每次发送的间隔时间完全一致。这个细节看起来不起眼,但能让行为曲线更接近真人操作,实测对稳定性有正向作用。
3.2 外部群会话用户ID是加密的,到底怎么处理
这个话题是很多人的痛点,每次讲自动化都有人问:接企微直达机器人或者侧边栏能力的时候,拿到的用户ID是一串类似加密的字符串,怎么解析成明文?
结论先说清楚:不要解析,直接存。官方接口体系里,这个加密ID本身就是设计用来做唯一标识的,不是拿来给你还原成手机号、微信号的。平台不会提供解密能力,那些号称能解密的第三方工具,既不可靠,也存在明显的合规风险,别碰。
正确的做法是把它当作不透明的键来用:存数据库,做主键,做去重,做关联记录。如果你确实需要知道这条消息来自哪个客户,在用户首次主动添加或者产生互动的时候,把当时能拿到的身份标识和这个密文ID一并写入映射表,后续通过映射表关联。这个映射表的维护工作要提前设计好,否则后面做用户维度的数据分析时会很痛苦。
我在代码里处理的时候,就是把用户ID原样作为一个字符串key传下去,日志里记录的也是这个ID,不尝试转换。这样既安全,又少了很多没意义的排查工作量。
3.3 消息模板与内容变量
推送内容如果每次都是代码里临时拼的,后面维护起来就是一场灾难。我的建议是消息模板独立管理,用一个JSON文件或者配置表存起来。
模板里预留变量,发送时替换。最常用的是群名称、日期、订单号、用户昵称这几类占位符。比如一条售后回访通知,模板里写的是“尊敬的用户,您的订单{order_id}已进入售后流程,预计{date}处理完成”,发送前程序自动把变量填进去。
模板一定要分组,对应不同业务场景:活动通知、售后提醒、社群日报、新客欢迎。每个模板有自己的版本号。为什么强调版本号?因为你改了一版文案,却不知道哪些群已经发过旧版,后面去重就会出问题。带版本号的模板,配合上去重逻辑,逻辑上才是闭环的。
3.4 去重与幂等,防止重复推送
多账号轮巡最容易踩的坑就是重复推送。同一个内容,主账号发了一遍,因为某种原因切换到了备用账号,备用账号没查历史记录,又发了一遍,客户群里同一时间收到两条一模一样的信息,体验很糟糕,后台也会觉得你的推送行为异常。
解决办法是发送前置一个幂等校验。最简单的幂等键是“群ID + 模板ID + 业务ID”。如果一条消息关联了一个订单号,直接拿订单号做唯一约束,系统里已经存在这个订单号的发送记录,就跳过。如果消息不关联业务ID,就用内容指纹,也就是对原文做一次哈希,然后查发送记录里有没有同一个群、同一个指纹、在同一时间窗口内的记录。
这个步骤必须在所有发送动作之前执行,而且要放在同一个事务流程里,防止并发场景下两个账号同时通过校验。我实际遇到过一次没加锁导致重复推送的事故,原因就是两个调度任务同时跑,各自查了一下发送记录都没查到,然后各自发了一条。后来加了分布式锁才彻底解决。
4. 实操:搭一个能跑起来的轮巡推送器
4.1 主流程先理清
动手写代码之前,先把链路理清楚。我自己的轮巡推送器主流程是这样的。
定时器统一触发整个流程。每次触发时,先拉取当前要执行的任务队列,每个任务包含目标群、模板、业务参数和时间窗口。然后针对每个任务,先做幂等校验,查发送记录,如果发过就跳过。接下来是选号,从账号池里挑一个对目标群有权限、配额没超标、不在冷却期内的账号。
选完号之后,渲染模板,把变量替换成真实内容。最后一步是发送,发送成功之后写日志,更新账号的配额和冷却状态。整个链路按这个顺序跑,每一步有自己独立的函数,日志做得越细越容易排查问题。
4.2 配置与代码骨架
配置我习惯用JSON管理。下面这个结构可以直接作为参考。
{ "accounts": [ { "id": "acc_01", "role": "notice", "group_limit": 3, "daily_limit": 15, "cooldown_min": 30 }, { "id": "acc_02", "role": "notice", "group_limit": 3, "daily_limit": 15, "cooldown_min": 30 } ], "tasks": [ { "group_id": "group_notice_01", "template_id": "tpl_daily_notice", "template_version": "2025-06-v1", "send_windows": ["10:00", "15:00", "20:00"], "payload": { "order_id": "ORDER20250601001" } } ] }主程序里,我把逻辑拆成三个核心类来写。
import hashlib import json import time from datetime import datetime class AccountPool: """账号池:负责账号状态管理、配额和冷却判断""" def __init__(self, accounts_config): self.accounts = accounts_config # 账号ID -> 当日已发送次数 self.sent_count = {} # 账号ID -> 群ID -> 最近发送时间戳 self.last_sent = {} def is_available(self, account_id, group_id, now_ts): # 当日总量是否超标 if self.sent_count.get(account_id, 0) >= self.get_account(account_id)["daily_limit"]: return False # 冷却期判断,同一个群两次推送间隔不小于 cooldown_min 分钟 last_ts = self.last_sent.get(account_id, {}).get(group_id, 0) cooldown = self.get_account(account_id)["cooldown_min"] * 60 if now_ts - last_ts < cooldown: return False return True def get_account(self, account_id): for acc in self.accounts: if acc["id"] == account_id: return acc return None def mark_sent(self, account_id, group_id, now_ts): self.sent_count[account_id] = self.sent_count.get(account_id, 0) + 1 self.last_sent.setdefault(account_id, {})[group_id] = now_ts class TaskQueue: """任务队列:加载当天待推送任务,简单起见直接读配置""" def __init__(self, tasks_config): self.tasks = tasks_config def pending_tasks(self, now_str): tasks = [] for task in self.tasks: for window in task["send_windows"]: if window == now_str: tasks.append(task) return tasks class PushScheduler: """调度器:把任务分配给合适的账号发送""" def __init__(self, accounts_config, tasks_config, send_log): self.account_pool = AccountPool(accounts_config) self.task_queue = TaskQueue(tasks_config) # 发送记录表:group_id -> 集合,存放消息指纹,用于幂等 self.send_log = send_log def _fingerprint(self, task): # 用群ID、模板ID、模板版本、业务参数生成唯一指纹 raw = json.dumps({ "group_id": task["group_id"], "template_id": task["template_id"], "template_version": task["template_version"], "payload": task["payload"] }, sort_keys=True) return hashlib.sha256(raw.encode()).hexdigest() def _already_sent(self, task): fp = self._fingerprint(task) return fp in self.send_log.get(task["group_id"], set()) def _pick_account(self, group_id, now_ts): # 这里可以换成更复杂的选号策略,基础版先按顺序轮巡 for acc in self.account_pool.accounts: if self.account_pool.is_available(acc["id"], group_id, now_ts): return acc return None def run(self, now_str, now_ts): for task in self.task_queue.pending_tasks(now_str): if self._already_sent(task): print(f"skip duplicated task: {task['group_id']}") continue account = self._pick_account(task["group_id"], now_ts) if account is None: print(f"no available account for group: {task['group_id']}") continue # 这里调用真实的发送接口,发送成功后记录结果 # send_message(account["id"], task["group_id"], rendered_content) self.account_pool.mark_sent(account["id"], task["group_id"], now_ts) self.send_log.setdefault(task["group_id"], set()).add(self._fingerprint(task)) print(f"sended via {account['id']}: {task['group_id']}")这个代码骨架每一段都有明确的边界。AccountPool管状态,TaskQueue管任务,PushScheduler管分配。后续要加告警、加统计、加新的选号策略,都只改对应类就行。
4.3 配额与冷却怎么算
配额和冷却的数值,我在前面的配置里写了daily_limit等于15、cooldown_min等于30,这是两个经验参考值,不是平台的规定。实际怎么定,取决于你的业务和账号状态。
新账号不要按满配额跑。我实操时的做法是:新账号前三天只分配日常的两到三成任务,让账号的活跃度慢慢提升,观察没有被限制再逐步增加。老账号的配额也不要顶满,留出余量给人工应答和临时互动,因为群内的自动推送只是一部分行为,还要给真人操作留空间。
冷却期也要分群计算。一个账号给群A发了一条,过两分钟又给群B发一条,这其实是正常的,因为两个群不同。同一个账号同一个群,间隔至少半小时。这个逻辑反映在AccountPool的last_sent结构里,按账号加群两级维度去判断,不能只按账号判断。
4.4 部署、日志与监控
推送器我是建议用系统定时任务或者调度框架来触发。如果用cron,写一行配置,比如每天10:00、15:00、20:00调用一次脚本就可以。如果希望更灵活,可以用Python的APScheduler在进程内做定时调度,配置放数据库,支持动态增删任务。
日志这一块不要省。我每条发送记录至少包含:哪个账号、哪个群、哪条模板、什么时间、发送结果、消息ID。这样出了问题,能还原当时的情况。建议输出结构化日志,比如JSON格式,后面做统计直接查日志表就行。
监控要有一个保险丝。我加了一个规则:同一个账号连续发送失败超过三次,立即停止该账号的推送任务,并且告警到工作群。很多问题早期只是个信号,如果不及时切断,会一连串地失败下去,最后账号被限制掉。
5. 线上实录:常见问题与排查办法
5.1 消息发不出去,先看四个原因
| 现象 | 常见原因 | 排查方向 |
|---|---|---|
| 提示操作过于频繁 | 同一账号短时间内发太多群 | 查账号冷却状态与当日配额 |
| 消息发出后被拦截 | 内容包含链接或营销词 | 检查模板内容,减少链接密度 |
| 部分群发不出去 | 群状态变化,解散或成员被移出 | 确认群的成员状态与账号权限 |
| 登录态失效 | 账号被顶下线或会话过期 | 检查会话健康检查任务的告警 |
链接是最大的不确定因素。实测下来,纯文本内容的发送成功率远高于带链接的内容。如果业务上确实需要放链接,同一时间不要发到太多群,分批小流量测试可能更稳。
5.2 调度器重复推送了,怎么定位
重复推送的场景,我前面提过一个并发导致的问题。排查的时候先看发送记录表,找到重复发出去的几条记录,对比它们的账号ID、任务指纹和发送时间。
如果两个账号都发了,并且指纹一样的,说明幂等校验没生效。大概率是去重查询和发送动作不在同一个事务里,并发情况下两个任务同时查,都没看到对方已经查过,然后各自发送。解决办法是给发送动作加锁,简单粗暴地与群维度加分布式锁,保证同一时刻同一个群只有一个任务在处理。
还有另一种情况,消息内容发了一模一样的,但指纹不同,那就是指纹生成规则里漏了字段,或者payload里有序的无意义字段变化了。这种情况去重很难靠代码兜底,只能靠规范数据格式来避免。
5.3 账号突然被限制发言了
账号被限制发言,多半不是一次操作引起的,而是一个累积过程。常见触发因素包括:新号一上来就高强度推送、多个群短时间内同时收到相同内容、登录设备频繁变化、群内被客户投诉。
真有账号被限制了,先不要动,把推送任务停掉,停止包括其他账号的任务,防止整个池子都受影响。然后去后台看限制原因,通常会有申诉入口,按指引提交账号主体信息和情况说明。等待期间其他账号不要加量,维持正常运营水平就好。
我还想多说一句:不要试图通过解封工具、第三方渠道绕过限制,没有任何合规的路可以走,老老实实申诉才是唯一正确的动作。
5.4 那些容易被忽略的细节
时区问题。如果你的服务器在上海,cron用的是本地时间还是UTC要看清楚,否则每天10点的推送硬生生跑到18点才发。
节假日问题。周六周日和法定节假日,外部群的推送打开率很低,反而客户觉得被打扰的可能性更高。我运营位的策略是:工作日正常推,节假日只发必要的售后提醒,活动类推送停止。
凌晨推送问题。这个不用讨论,任何自动化方案都不应该在凌晨给外部群推消息,也没有任何业务场景需要你这样做。凌晨的推送行为在后台数据里会非常突兀,属于自己送上去的异常信号。
6. 跑起来的最后建议:怎么稳定合规地跑下去
6.1 账号生命周期的管理
账号池不是一个静态配置,它会变化。新账号加入要经过试运行,老账号偶尔也要休息调整。我建议在配置里加状态字段:active、trial、paused。试运行的账号任务量小,状态稳定后再转为active。发现问题直接paused,从选号逻辑里排除掉。
定期检查登录态,这个不能省。会话凭证过期是常态,不要等发送失败才处理。每天跑一次健康检查,把所有账号的登录状态列出来,过期的自动剔除并通知负责人。
6.2 推送内容自查清单
模板内容审核要形成习惯。我自己有一套自查清单:不包含诱导分享、不包含虚假夸大承诺、不包含收集用户隐私的内容、不包含竞品敏感信息。每条模板上线前过一遍这四个维度,推送出去之后才安心。
客户退出机制也要预留好。群里有人明确说不想接收推送,要在发送名单里标记出来,后续任务直接跳过。尊重客户的意愿,既是合规要求,也是运营的基本素养。
6.3 我的经验:半自动比全自动更可靠
最后说一个我个人的心得体会。轮巡推送器解决了多账号协同的问题,但我后来发现,全自动推送到最后,运营效果反而没有“机器加人”的半自动模式好。
机器负责的事情是:定时提醒、内容渲染、发送路由、发送记录、数据统计。人在干什么呢?负责发送前的最后确认、审核文案,以及在客户回复时第一时间介入对话。全自动模式的问题在于,只能按预设模板发,遇到客户的特殊问题,机器回答不了,反而影响体验。半自动模式把机器从重复劳动里解放出来,人才去做真正需要判断力的事情。
我现在跑的生产环境,定时推送仍然全自动,但每个任务发送前会经过一个确认环节,如果有临时情况就人工干预。这样兼顾效率和体验,也让我对每天发出去的内容有把握。
如果你刚开始搭这套系统,不用追求一步到位。先让两三个账号的轮巡跑起来,把去重、冷却、日志这些基础逻辑验证好,再逐步扩到全部账号。自动化这事,稳比快重要得多。