本系统是一个基于 Python 的微信群自动化管理工具,主要用于快递云仓售后服务场景。系统通过 WebSocket 连接微信机器人,自动识别群消息中的快递单号和关键词,将消息智能转发到对应的快递公司售后对接群,并记录处理数据。
- 自动识别消息中的快递单号(支持申通、中通、圆通、韵达、邮政、极兔、顺丰)
- 关键词检测:拦截、催件、改地址等
- 支持单条和多条快递单号混合识别
- 支持空格、逗号、换行等多种分隔符
- 根据快递公司自动转发到对应售后群
- 邮政快递根据物流信息智能匹配网点(吉安、安福县、吉水县)
- 支持一对多转发(同一快递公司多个网点)
- 自动@原始发送者并回复处理状态
- 自动记录处理数据到 Excel 表格
- 分类存储拦截、催件、改地址等不同类型的任务
- 按快递公司和网点分类存储
- WebSocket 长连接,自动重连机制
- 心跳保活(60秒间隔)
- 断线自动重连,指数退避策略
- 自动记录新加入的群组信息
- 群组数据持久化存储
- 定时向指定客户群发送截单提醒
- 支持多个截单时间点配置(15点、16点、17点)
- 提前半小时自动发送提醒通知
- 灵活配置提醒消息模板和提醒时间
- 自动检测并过滤重复接收的消息
- 防止重复处理导致的多次转发和数据重复
- 基于消息指纹的高效去重算法
- 自动清理过期的消息记录(默认5分钟)
- Python 3.7+
- Windows/Linux/MacOS
- 稳定的网络连接
cd /path/to/cloudexpresspython -m venv venv
# Windows
venv\Scripts\activate
# Linux/MacOS
source venv/bin/activatepip install -r requirements.txt依赖包说明:
requests- HTTP 请求库websockets- WebSocket 客户端python-dotenv- 环境变量管理openpyxl- Excel 文件操作filelock- 文件锁,防止并发写入冲突aiohttp- 异步 HTTP 客户端
# 基础信息配置
TOKEN=wx_E2k7qPKTJt8zMpNyLrrX2 # 微信机器人Token
ROBOT_ID=wxid_cnw193tl86kx22 # 机器人微信ID
SERVER_IP=124.223.95.144 # WebSocket服务器IP
SERVER_PORT=5555 # WebSocket服务器端口
# 统一回复模板
RESPONSE_M='已加急联系快递处理了!'
# 触发关键词
KEYWORDS=['拦截', '退回', '拒收', '催件', '催更', '催促', '地址']
# 各快递公司售后群昵称配置
STO=['申通售后群'] # 申通快递
ZTO=['大鹏D中通(吉州分部)售后对接群','大鹏D中通(工业园)售后对接群'] # 中通快递
YDA=['大鹏D韵达(庐陵)售后对接群','大鹏D韵达(吉贤路)售后对接群'] # 韵达快递
EMS=['大鹏D邮政(安福县)售后对接群','大鹏D邮政(吉安)售后对接群','大鹏D邮政(吉水县)售后对接群'] # 邮政快递
YTO=['圆通售后群'] # 圆通快递
JTE=['大鹏D极兔(庐陵)售后对接群'] # 极兔快递
SFE=['顺丰售后群'] # 顺丰快递
# 截单提醒配置
CUTOFF_REMINDER_MESSAGE='亲,今日的截单点({time})将至,麻烦检查一下系统里是否有未审核的订单,谢谢!'
ADVANCE_REMINDER_MINUTES=30 # 提前30分钟发送提醒| 快递公司 | 单号开头 | 配置变量 |
|---|---|---|
| 申通 | 77 | STO |
| 中通 | 73/75/76/78 | ZTO |
| 圆通 | YT/yt | YTO |
| 韵达 | 3/4 | YDA |
| 邮政 | 9 | EMS |
| 极兔 | JT/jt | JTE |
| 顺丰 | SF/sf | SFE |
- 拦截任务:包含"拦截"、"退回"、"拒收"
- 催件任务:包含"催件"、"催更"、"催促"
- 改地址任务:包含"地址"
邮政快递需要通过物流信息查询具体网点:
- 包含"吉安市行业客户大宗揽投部" → 转发到吉安邮政群
- 包含"安福县城东揽收部" → 转发到安福县邮政群
- 包含"吉水县金滩揽收部" → 转发到吉水县邮政群
系统会自动将检测到的群组信息保存到 groupdata/groupdata.json,无需手动配置。
首次运行时,确保目录存在:
mkdir groupdata
mkdir record
mkdir log
mkdir image
mkdir task截单提醒功能用于在指定时间点前自动向客户群发送截单通知。
编辑 task/task.json 文件,配置不同时间点需要提醒的客户群:
{
"15": [
"永新顺为",
"永瑞药业",
"秋叶源",
"顽大夫",
"新桥仓",
"西洋参山茱萸茶",
"裕耀咖啡"
],
"16": [
"米炭",
"牛乐",
"一慕",
"开元堂",
"MW仓"
],
"17": [
"文具用品仓",
"复真工厂",
"本初食品",
"尚腾",
"橙子母婴"
]
}配置说明:
- 键名("15"、"16"、"17")表示截单时间点(24小时制)
- 值为数组,包含需要在该时间点接收提醒的客户群昵称
- 群昵称必须与微信群实际昵称完全一致
- 系统会在截单时间点前30分钟(可在.env中配置)发送提醒
在 .env 文件中配置提醒消息模板:
# 截单提醒消息模板({time}会被替换为实际截单时间)
CUTOFF_REMINDER_MESSAGE='亲,今日的截单点({time})将至,麻烦检查一下系统里是否有未审核的订单,谢谢!'
# 提前提醒时间(分钟)
ADVANCE_REMINDER_MINUTES=30示例效果:
如果配置15点截单,系统会在14:30向所有15点截单的客户群发送:
亲,今日的截单点(15:00)将至,麻烦检查一下系统里是否有未审核的订单,谢谢!
如需添加新的截单时间点,只需在 task.json 中添加新的键值对:
{
"15": ["客户群1", "客户群2"],
"16": ["客户群3"],
"17": ["客户群4"],
"18": ["客户群5", "客户群6"] // 新增18点截单
}如需修改某个时间点的客户群列表,直接编辑对应的数组即可。
python mian.py启动 WebSocket 客户端...
群组数据加载完成
成功连接到 WebSocket 服务器: ws://124.223.95.144:5555/ws
已向服务器注册robotid: wxid_cnw193tl86kx22
连接确认: Connection established
当群内有人发送消息时:
用户消息示例:
"77123456789 拦截"
系统处理流程:
1. 检测到关键词"拦截"
2. 提取快递单号:77123456789
3. 识别为申通快递(以77开头)
4. 回复原始发送者:"@用户 已加急联系快递处理了!"
5. 转发消息到申通售后群:"77123456789 拦截"
6. 记录数据到Excel:record/申通快递_拦截.xlsx
77123456789 拦截
773456789012 快递拦截
77123456789 73987654321 拦截
77123456789,73987654321 拦截
77123456789
73987654321
拦截
77123456789 改地址:广东省深圳市南山区科技园XXX
# 按 Ctrl+C 停止
客户端已手动停止。截单提醒功能会在每天的指定时间点前自动向配置的客户群发送提醒消息。
系统会根据 task/task.json 的配置,在每个截单时间点前的指定时间(默认30分钟)自动发送提醒。
示例场景:
假设配置了以下截单时间:
- 15:00 截单 → 客户群:永新顺为、永瑞药业、秋叶源...
- 16:00 截单 → 客户群:米炭、牛乐、一慕...
- 17:00 截单 → 客户群:文具用品仓、复真工厂、本初食品...
系统运行后:
- 14:30 - 自动向所有15点截单的客户群发送提醒
- 15:30 - 自动向所有16点截单的客户群发送提醒
- 16:30 - 自动向所有17点截单的客户群发送提醒
亲,今日的截单点(15:00)将至,麻烦检查一下系统里是否有未审核的订单,谢谢!
消息中的时间会自动替换为实际的截单时间点。
-
群名称匹配
task.json中配置的群名称必须与groupdata/groupdata.json中的群名称完全一致- 建议先让机器人加入所有客户群,系统会自动记录群名称到
groupdata.json - 然后从
groupdata.json复制准确的群名称到task.json
-
时间配置
- 时间使用24小时制(0-23)
- 提前提醒时间在
.env的ADVANCE_REMINDER_MINUTES中配置 - 建议提前时间设置在15-60分钟之间
-
定时任务启动
- 截单提醒功能需要系统持续运行才能生效
- 建议使用进程守护工具(如 supervisor、systemd、pm2)确保程序持续运行
- 或在服务器上设置为开机自启动
-
消息发送接口
- 截单提醒使用
action/group/sendtaskmess.py中的send_message()函数 - 如需自定义发送逻辑,可修改该文件
- 截单提醒使用
查看已配置的提醒任务:
cat task/task.json查看群组映射关系:
cat groupdata/groupdata.json手动测试发送消息:
可以临时修改 .env 中的 ADVANCE_REMINDER_MINUTES 为更小的值进行测试。
cloudexpress/
├── mian.py # 主程序入口
├── schedule.py # 消息调度和处理逻辑
├── check.py # 快递单号提取工具
├── env_loader.py # 环境变量加载
├── event_maps.py # 事件映射
├── addgroupdata.py # 群组数据添加
├── query_groupinfo.py # 群组信息查询
├── requirements.txt # 依赖包列表
├── .env # 配置文件(需自行配置)
├── action/ # 动作模块
│ └── group/ # 群组相关操作
│ ├── sendrefermess.py # 发送引用消息
│ ├── sendtaskmess.py # 发送定时提醒消息
│ ├── input_excel.py # Excel记录(拦截/催件)
│ ├── input_excel_address.py # Excel记录(改地址)
│ └── getgroupinfo.py # 获取群组信息
├── expressapi/ # 快递API接口
│ ├── __init__.py
│ └── Express_inquiry.py # 快递查询接口
├── cutoff_reminder.py # 截单提醒调度器
├── message_dedup.py # 消息去重工具
├── groupdata/ # 群组数据存储
│ └── groupdata.json # 群组ID映射
├── task/ # 截单提醒任务配置
│ └── task.json # 截单时间和客户群配置
├── record/ # 处理记录存储
│ ├── 申通快递_拦截.xlsx
│ ├── 中通快递_催件.xlsx
│ └── ...
├── log/ # 日志文件
│ └── logger.py
├── image/ # 图片资源
└── venv/ # 虚拟环境(自动生成)
系统会在 record/ 目录下自动创建Excel文件,文件命名规则:
{快递公司}_{任务类型}.xlsx
示例:
申通快递_拦截.xlsx中通快递_催件.xlsx邮政快递_改地址.xlsx
| 字段 | 说明 |
|---|---|
| 快递公司 | 快递公司名称 |
| 运单号 | 快递单号 |
| 操作类型 | 拦截/催件 |
| 来源群昵称 | 消息来源群名称 |
| 来源群ID | 消息来源群ID |
| 发送者昵称 | 发送者微信昵称 |
| 发送者ID | 发送者微信ID |
| 处理时间 | 记录时间戳 |
| 字段 | 说明 |
|---|---|
| 快递公司 | 快递公司名称 |
| 运单号 | 快递单号 |
| 操作类型 | 改地址 |
| 原始消息 | 完整消息内容 |
| 来源群昵称 | 消息来源群名称 |
| 来源群ID | 消息来源群ID |
| 发送者昵称 | 发送者微信昵称 |
| 发送者ID | 发送者微信ID |
| 处理时间 | 记录时间戳 |
问题:程序无法连接到WebSocket服务器
解决方案:
- 检查
.env中的SERVER_IP和SERVER_PORT配置 - 确认服务器是否正常运行
- 检查网络连接和防火墙设置
- 查看是否有VPN或代理影响
问题:检测到关键词但未转发到快递群
解决方案:
- 检查
.env中快递公司群名称配置是否正确 - 确认机器人已加入对应的快递售后群
- 查看
groupdata/groupdata.json中是否包含该群组 - 检查群名称是否与配置完全一致(包括空格、括号等)
问题:无法识别或识别错误的快递单号
解决方案:
- 确认快递单号格式符合规则(见配置说明)
- 检查是否包含特殊字符或空格
- 查看日志输出的提取结果
- 必要时修改
check.py中的提取逻辑
问题:提示Excel文件无法写入
解决方案:
- 关闭正在打开的Excel文件
- 系统使用了文件锁机制,等待几秒后会自动重试
- 检查文件权限设置
问题:邮政快递转发到错误的群或未转发
解决方案:
- 检查物流查询API是否正常
- 查看物流信息中是否包含网点关键词
- 在
schedule.py:176-184查看网点匹配逻辑 - 必要时添加新的网点匹配规则
问题:日志显示频繁的连接/断开
解决方案:
- 检查网络稳定性
- 确认服务器端是否正常
- 调整心跳间隔(默认60秒)
- 查看服务器端日志
问题:到了提醒时间但没有收到截单提醒消息
解决方案:
-
检查群名称配置
- 确认
task/task.json中的群名称与groupdata/groupdata.json中完全一致 - 注意空格、标点符号等细节差异
- 确认
-
检查时间配置
- 确认当前时间已经到达提醒时间(截单时间 - 提前提醒分钟数)
- 检查
.env中ADVANCE_REMINDER_MINUTES配置 - 确认系统时间与服务器时间一致
-
检查程序运行状态
- 确认程序在提醒时间点时正在运行
- 查看程序日志是否有错误信息
- 检查定时任务调度器是否正常启动
-
检查机器人权限
- 确认机器人已加入所有需要发送提醒的客户群
- 确认机器人在群内有发送消息的权限
- 测试手动发送消息到该群是否成功
-
调试建议
- 临时将
ADVANCE_REMINDER_MINUTES改为较小值(如5分钟)进行测试 - 查看
action/group/sendtaskmess.py的返回结果 - 检查API接口是否正常响应
- 临时将
问题:向群里发送快递单号后,系统转发了多次消息,Excel中也保存了多条相同数据
原因分析:
-
WebSocket服务器重复发送消息(最常见)
- 网络不稳定导致消息重发
- 服务器消息确认机制问题
- 程序重启时接收历史消息
-
并发竞态条件(已修复) ⭐
- 两条几乎同时到达的相同消息
- 在第一条消息添加到去重缓存之前,第二条消息也通过了检查
- 导致两条消息都被处理
-
运行了多个程序实例
- 不小心启动了多个
main.py进程 - 检查方法:
tasklist | findstr python(Windows)或ps aux | grep main.py(Linux)
- 不小心启动了多个
解决方案:✅ 已彻底修复
系统已集成线程安全的消息去重功能,会自动过滤重复的消息:
2026-01-16 15:00:00 - INFO - [调试] ========== 收到消息 ==========
2026-01-16 15:00:00 - INFO - [调试] 消息ID: 100001
2026-01-16 15:00:00 - INFO - [调试] 时间戳: 1737012345
2026-01-16 15:00:00 - INFO - [调试] 消息指纹: e351e399a8f2b1c4...
2026-01-16 15:00:00 - WARNING - ⚠️ ⚠️ ⚠️ 检测到重复消息,跳过处理 ⚠️ ⚠️ ⚠️
去重机制说明:
- 基于消息指纹(FromUserName + Content + MsgType)
- 不包含时间戳和消息ID(因为服务器重发时这些字段会变化)
- 线程安全设计:使用互斥锁防止并发竞态条件
- 自动缓存最近1000条消息
- 自动清理60秒前的历史记录
- 对性能影响极小(毫秒级)
技术细节:
# message_dedup.py 核心改进
class MessageDeduplicator:
def __init__(self):
self._lock = threading.Lock() # 线程锁
def is_duplicate(self, message_data):
with self._lock: # 原子操作,防止竞态条件
# 检查 → 添加 → 返回结果(整个过程加锁保护)
if fingerprint in self.message_cache:
return True
self.message_cache[fingerprint] = current_time
return False并发测试结果:
测试场景:10个线程同时收到相同消息
测试结果:只有1个线程通过去重检查
并发安全性测试: [PASS]
准确性测试: [PASS]
如何验证:
- 向群里发送测试消息
- 观察日志:
- 第一次消息:
[调试] ✓ 新消息,准备处理... - 重复消息:
⚠️ ⚠️ ⚠️ 检测到重复消息,跳过处理 ⚠️ ⚠️ ⚠️
- 第一次消息:
- 检查Excel文件,确认只保存了一条数据
- 检查目标群,确认只转发了一次
注意:
- 去重功能在程序运行期间持续有效
- 程序重启后会清空去重缓存
- 如果同一消息间隔超过60秒发送,会被视为新消息
- 如果仍然出现重复,请检查是否运行了多个程序实例
程序使用Python标准logging模块,默认级别为INFO:
INFO- 正常运行信息WARNING- 警告信息ERROR- 错误信息DEBUG- 调试信息(需修改代码启用)
# 连接成功
成功连接到 WebSocket 服务器: ws://...
# 心跳
心跳包已发送
# 消息处理
--- 收到新的消息 ---
检测到关键词,开始处理消息...
处理单号: 77123456789
匹配到快递公司: 申通
申通快递拦截数据保存成功!
# 群组新增
[INFO] 新增群组: 测试群 -> 12345@chatroom
# 截单提醒
[INFO] 截单提醒调度器已启动
[INFO] 准备发送15:00截单提醒,目标群:永新顺为
[INFO] 截单提醒发送成功: 永新顺为 -> 亲,今日的截单点(15:00)将至...
[ERROR] 截单提醒发送失败: 客户群A -> 群组未找到
- 不要将
.env文件提交到版本控制系统 - 定期更换TOKEN和密钥
- 限制服务器访问权限
- 做好数据备份
- 系统使用异步处理,可处理高并发消息
- 多个快递群转发时有1秒延迟,防止发送过快
- Excel写入使用文件锁,避免并发冲突
- 定期清理record目录下的历史记录
- 每个快递单号建议唯一处理,避免重复转发
- 改地址任务每次只处理第一个快递单号
- 邮政快递依赖API查询,需要网络稳定
- 定期检查
groupdata/groupdata.json清理无效群组 - 定期清理
record/目录下的历史Excel文件 - 监控日志文件大小,必要时进行日志切割
- 定期更新依赖包版本
-
在
.env中添加配置:XXX=['XXX快递售后群1', 'XXX快递售后群2']
-
在
env_loader.py中加载配置:XXX = os.getenv('XXX', '[]')
-
在
schedule.py:130-136添加匹配规则:'XX': ('XXX快递', XXX), # XX为单号开头
如需自定义截单提醒的更多功能:
-
修改提醒消息模板
- 编辑
.env中的CUTOFF_REMINDER_MESSAGE - 可以使用
{time}占位符,会被替换为实际截单时间 - 可以添加更多占位符,需要同步修改发送逻辑
- 编辑
-
调整提醒时间
- 修改
.env中的ADVANCE_REMINDER_MINUTES - 单位为分钟,建议15-60分钟之间
- 修改
-
添加更多截单时间点
- 直接在
task/task.json中添加新的时间点和对应客户群
- 直接在
-
实现定时任务调度器
- 可以使用
APScheduler库实现定时任务 - 在主程序中启动定时任务,定期检查是否到达提醒时间
- 调用
action/group/sendtaskmess.py中的接口发送消息
- 可以使用
-
多种提醒方式
- 除了微信群提醒,还可以添加邮件、短信等提醒方式
- 在
action/目录下创建对应的发送接口
示例代码结构(仅供参考):
from apscheduler.schedulers.asyncio import AsyncIOScheduler
import json
from action.group.sendtaskmess import send_message
async def check_and_send_reminders():
"""检查并发送截单提醒"""
task_config = json.load(open('task/task.json'))
# 实现提醒逻辑
pass
# 在主程序中启动调度器
scheduler = AsyncIOScheduler()
scheduler.add_job(check_and_send_reminders, 'cron', minute='*/5') # 每5分钟检查一次
scheduler.start()本系统主要服务于云仓业务场景,具体流程:
- 电商客户在主群发送快递问题(拦截、催件、改地址)
- 机器人自动识别并回复客户
- 将问题转发到对应快递公司的售后对接群
- 快递公司人员在售后群处理问题
- 系统记录所有处理数据供后续统计分析
- 系统根据
task.json配置的截单时间点,在指定时间前自动触发提醒 - 遍历该时间点配置的所有客户群
- 向每个客户群发送截单提醒消息
- 客户收到提醒后检查系统中的未审核订单
- 确保订单在截单时间前完成审核,避免延误发货
如遇到问题,请:
- 查看程序日志输出
- 检查配置文件是否正确
- 参考本文档的常见问题部分
- 联系系统管理员或开发人员
- 基础消息识别和转发功能
- 支持7家主流快递公司
- Excel数据记录功能
- WebSocket稳定连接
- 群组自动管理
- 截单提醒功能(支持多时间点定时提醒)
祝使用愉快!