这篇文章记录我从零搭一个微信群聊机器人的完整过程。它不是那种"装个库就能跑"的玩具——目前它在真实的群里稳定跑了几个月,带 60 多个功能、五级管理员体系、金币经济系统、婚姻/宝宝/闺蜜/门派这些社交玩法,还有一个能远程配置的 Web 管理后台。
整套东西的技术栈非常朴素:WeChatFerry(Hook) + Python(业务) + Flask(后台) + SQLite/MySQL(存储)。没有高深的东西,难的全在细节和坑上——微信版本、消息解析、并发、穿透部署。这些我都会写在下面。
先说风险:这类机器人基于微信 PC 客户端的 Hook,属于微信官方明确不支持的行为,有封号风险。请务必使用不重要的小号,不要用于商业营销、 spam 或任何违规用途。本文只作技术学习交流。
一、它最终长什么样
先给个直观印象,机器人进群后,群成员可以直接发指令(不需要 @):
签到— 每日打卡,累计连续天数,发金币和积分查币/金币排行/富豪榜— 一整套虚拟经济系统求婚 @某人/我的婚姻/领养宝宝— 社交养成玩法点歌 歌名/猜歌/成语接龙— 群内互动游戏人品/黄历/天气 郑州/历史上的今天— 趣味查询群空间— 机器人回一条带签名的链接,点开就是本群的管理面板@机器人 讲个笑话— 接 AI 模型,支持上下文记忆
管理员则有另一套 # 指令:开关任意功能、改话术模板、拉黑成员、发钻石兑换码、任命各级管理员……
二、架构:为什么要分三层

这是我最后稳定下来的结构。分层的收益非常直接:
| 层 | 职责 | 变化频率 |
|---|---|---|
| Hook 层 | 和微信 PC 客户端打交道,把消息取出来、把内容发回去 | 微信一升级就得换版本 |
| 业务层 | 纯 Python,只认"文本进、文本出",完全不知道微信存在 | 每天都在加功能 |
| 管理层 | Flask 后台 + 云端面板,负责配置、开关、话术、统计 | 改文案不用重启 |
这么切之后,我加一个新功能完全不用碰微信相关的任何代码——业务层里写一个函数、挂到分发链上就完事。而微信协议一旦变动,我也只需要换 WeChatFerry 版本,业务逻辑一行不改。
三、环境准备(最容易翻车的一步)
3.1 微信版本是硬门槛
WeChatFerry 是通过内存偏移 Hook 微信进程的,微信版本必须严格匹配。差一个小版本,注入直接失败。我用的是 3.9.12.x。
- 卸载电脑上的现有微信
- 到
github.com/tom-snow/wechat-windows-versions/releases下载 3.9.12.x 历史版本安装包 - 安装后登录微信小号
- 立刻关闭自动更新(管理员身份运行 cmd):
reg add "HKCU\Software\Tencent\WeChat" /v "AutoUpdate" /t REG_DWORD /d 0 /f血泪教训:微信半夜偷偷自动更新一次,第二天机器人就哑了,现象是启动报"无法连接微信"。这一步千万别省。
3.2 Python 与依赖
Python 3.9 以上,推荐 3.11。依赖少得可怜:
# requirements.txt
wcferry=39.0
PyYAML=6.0
flask=3.0
openai=1.0
requests=2.31
APScheduler=3.10
pydantic=2.0# 进项目目录装依赖,慢就用清华源
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple3.3 常见失败速查
| `` | 现象 | 原因 | |
|---|---|---|---|
| 启动报"无法连接微信" | 微信没登录 / 版本不对 / 没以普通权限启动 | ||
| 注入成功但收不到消息 | 没开 enable_receiving_msg(),或群在黑名单 | ||
| 能收不能发 | 机器人被群禁言,或发送频率过高被限制 | ||
| 跑两天突然失效 | 微信自动更新了,重装指定版本 |
四、项目结构
wechat-bot/
├── main.py # 启动入口:连微信 → 开接收 → 起后台 → 主循环
├── start.bat # 一键启动(含禁更新 + 依赖检查)
├── start-all.bat # 机器人 + frpc 穿透一起起
├── config/
│ ├── settings.yaml # 全局配置(唯一需要你改的文件)
│ ├── frpc.toml # 内网穿透客户端配置
│ └── mysql.yaml # 云端 MySQL 连接信息
├── core/
│ ├── config.py # 配置加载器
│ ├── database.py # 基础数据层(消息、成员、群)
│ ├── database_extended.py# 扩展数据层(金币、婚姻、钻石…)
│ ├── handler.py # 消息分发核心,1200 行,整个项目的中枢
│ ├── feature_manager.py # 功能开关
│ ├── diamond_system.py # 钻石 / 兑换码
│ └── marriage_manager.py # 婚姻系统
├── modules/
│ ├── game_handler.py # 娱乐指令,近 4000 行,最大的一块
│ ├── admin_handler.py # # 开头的管理员指令
│ ├── music_handler.py # 点歌 / 切歌
│ ├── baby_handler.py # 宝宝系统
│ ├── ai_chat.py # AI 对话
│ ├── cloud_sync.py # 本地 → 云端数据同步
│ └── scheduler.py # 定时任务(早报、签到重置、到期检查)
├── web/ # 本地 Flask 后台
├── cloud-admin/ # 部署到服务器上的云端面板
├── static/ # 群卡片封面等素材
└── data/bot.db # SQLite,首次运行自动生成五、配置文件:只改这一个文件
config/settings.yaml 是唯一需要你动手的地方。下面是我脱敏后的版本,逐段说明:
# --- 微信 Hook 层 ---
wechat:
wcf_port: 9999
wechat_version: "3.9.12.51" # 必须与实际安装版本一致
# --- AI 对话 ---
ai:
provider: "OpenAI"
api_key: "sk-你的密钥" # 支持任何 OpenAI 兼容接口
base_url: "https://api.deepseek.com"
model: "deepseek-v4-flash"
max_tokens: 500
system_prompt: "你是一个微信群聊助手,回答简洁友好,每次回复不超过200字。"
bot:
admin_wxids: # 最高权限,全群通用
- "wxid_你的微信ID"
allowed_groups: [] # 留空 = 所有群都生效
blocked_groups: [] # 黑名单,优先级更高
reply_delay_min: 0 # 随机延迟,防刷屏
reply_delay_max: 0
max_replies_per_minute: 20 # 限速
checkin:
enabled: true
keywords: ["签到", "打卡", "签"]
welcome:
enabled: true
template: "欢迎 {nickname} 加入本群!请阅读群规~"
scheduler:
morning_news: { enabled: true, hour: 8, minute: 0 } # 每日早报
checkin_reset: { enabled: true, hour: 0, minute: 5 } # 签到重置
expire_check: { enabled: true, hour: 9, minute: 0 } # 群到期提醒
web:
enabled: true
host: "0.0.0.0"
port: 5000
secret: "改成你自己的随机串" # 群空间链接签名密钥
admin_password: "别用 admin123"
cloud:
enabled: true
server_url: "http://你的服务器"
sync_token: "与云端 config.py 保持一致" # 不一致就同步不了
sync_interval: 300
database:
path: "data/bot.db"安全提醒:如果你要把配置贴到博客、GitHub 或任何公开地方,务必先替换掉api_key、secret、admin_password、sync_token和真实 wxid。我见过太多人把密钥直接传上去,几小时后就收到账单。
六、第一次启动
双击 start.bat,或者命令行 python main.py。成功的样子:
==================================================
微信群聊机器人 v1.0
三层架构: Hook + 业务逻辑 + Web后台
==================================================
[启动] 配置加载完成
[启动] 数据库初始化完成
[启动] 正在连接微信...
[启动] 微信连接成功,机器人 wxid: wxid_xxxxxxxx
[启动] 消息接收已开启
[启动] 本地Web后台已启动: http://0.0.0.0:5000
[SYNC] 云端同步已启动,间隔 300秒
[运行] 机器人已上线,开始监听群消息...看到最后一行就成了。控制台打印的 wxid_xxxxxxxx 就是机器人自己的 ID,如果你要找自己的 wxid,最省事的办法是:把机器人拉进群后 @ 一下自己,或者直接发一条消息,看日志里 sender 字段。
然后把机器人小号拉进群,发个 签到 试试。
七、一条消息是怎么跑完一圈的

这是整个项目最核心的一段。主线程只做一件事——从队列里取消息,然后丢给线程池:
while wcf.is_receiving_msg():
try:
msg = wcf.get_msg()
if msg:
threading.Thread(
target=_safe_handle, args=(wcf, msg), daemon=True
).start()
except queue.Empty:
time.sleep(0.1) # 队列空很正常,别空转烧 CPU为什么必须开线程?点歌要去请求音乐接口,AI 对话可能要等三四秒。如果串行处理,一条慢消息就能堵死后面所有人的消息,群里表现为"机器人卡住了"。
7.1 过滤:不该管的直接丢掉
进 handle_message 第一件事是三道过滤:非群消息直接返回、群不在白名单直接返回、发消息的人被关了小黑屋直接返回(总管理员例外)。
7.2 昵称解析:一个真实的坑
你以为拿群昵称很简单?实际上微信返回的数据经常是脏的——有时会直接给你一个 wxid_xxx 当昵称,导致群里显示"wxid_ab12 签到成功"。我的解法是四级兜底:
- 查本地 members 表,排除掉以
wxid_开头的污染数据 - 调
get_alias_in_chatroom()拿群名片(最准) - 调
get_info_by_wxid()拿微信全局昵称 - 拉一次全群成员列表,顺便批量写回数据库
# 数据库有记录但昵称是 wxid(污染数据),置空以便重新获取
if mem and mem.get("nickname") and not mem["nickname"].startswith("wxid_"):
nickname = mem["nickname"]
else:
nickname = ""7.3 @ 解析:必须读 XML
群里 @ 某人的时候,msg.content 里只有"@昵称"三个字,拿不到 wxid。而业务上我们必须用 wxid(昵称会重复、会改)。真正的 wxid 藏在消息的 XML 里:
m = re.search(r'atuserlist(.*?)/atuserlist', xml, re.DOTALL)
raw = m.group(1).strip()
candidates = re.split(r'[,,\s]+', raw)
result = [w for w in candidates if w.startswith('wxid_')]解析不到时还有个 fallback:拿 content 里 @ 后面的昵称去群成员表里反查。这一层容错很有必要,因为不同微信版本的 XML 结构会有细微差别。
7.4 指令分发:顺序就是优先级
过滤和身份识别做完,才进入分发。顺序绝对不能乱:
# 1. 管理员 # 指令(即使群到期也要能用)
# 2. 到期拦截:群过期后只保留管理指令
# 3. 游戏 / 娱乐指令(60+ 功能)
# 4. 音乐点歌
# 5. 违禁词检查
# 6. 自定义关键词回复
# 7. @机器人 → AI 对话把"管理指令"放在"到期拦截"之前,是为了保证群到期之后管理员依然能进去续费、配置,不至于把自己锁死在门外。这种细节只有真跑起来才会想到。
7.5 群空间链接:别裸奔
机器人回复的群空间链接是带签名的,防止别人改个群 ID 就能进别人的面板:
ts = str(int(time.time()))
raw = f"{bot_wxid}{roomid}{ts}{secret}"
sign = hashlib.sha1(raw.encode()).hexdigest()
url = f"{cloud_url}/grouplogin.php?timestamp={ts}sign={sign}robotid={bot_wxid}qid={roomid}"服务端用同样的算法算一遍比对,同时对 timestamp 做有效期校验。简单、够用。
八、自己加一个功能
这是我改动最频繁的地方,流程固定三步。以新增一个"今日运气"为例:
第 1 步:写处理函数
# modules/game_handler.py
import random
def handle_lucky(wcf, msg, roomid, sender, nickname):
text = msg.content.strip()
if text not in ["运气", "今日运气"]:
return False # 不是我的指令,交还给分发链
luck = random.randint(1, 100)
if luck = 90:
comment = "大吉!今天适合表白、适合辞职、适合买彩票"
elif luck = 60:
comment = "小吉,稳中有进"
elif luck = 30:
comment = "平平淡淡才是真"
else:
comment = "诸事不宜,建议躺平"
wcf.send_text(f"【今日运气】\n{nickname}:{luck} 分\n{comment}", roomid)
return True # 已处理,分发链到此为止第 2 步:挂到分发链上
def handle_game_command(wcf, msg, roomid, sender, nickname):
text = msg.content.strip()
if text in ["签到", "打卡"]:
return handle_checkin(wcf, msg, roomid, sender, nickname)
# ↓ 加在这里,位置决定优先级
if handle_lucky(wcf, msg, roomid, sender, nickname):
return True
...三条必须记住的规则
- 不命中就 return False,命中才 return True。返回 True 会让分发链立即停止,后面的关键词、AI 都不会再触发。
- 顺序即优先级。如果"签到"被前面的模糊匹配吃掉,签到就永远触发不了。加功能时想清楚它该排在哪。
- 不要在分发链里做耗时操作。要请求外部接口就开线程,或者先回一句"查询中",拿到结果再发第二条。
九、Web 管理后台与群空间
本地 Flask 跑在 :5000,能看群列表、消息统计、成员、签到排行,也能改话术模板和功能开关。
更实用的是群空间:群成员在群里发 群空间,机器人回一条签名链接,点开就是本群专属的配置面板——群主可以自己开关 60 多个功能、改欢迎语、看本群数据,不需要来找我。默认密码是 qwe123,第一次进去务必改掉。
十、让外网也能访问:frp 穿透

机器人跑在你自己的 Windows 上,本地 localhost:5000 群友当然打不开。解法是内网穿透,我用的是自建 frp。
服务端(云服务器)
- 下载 frp,配置
frps.toml:bindPort = 7000+ 自定义 token - 启动
./frps -c frps.toml,安全组放行 7000 / 80 / 443 - Nginx 反代到本地 6000 端口,
server_name写你的域名 - DNS 加一条 A 记录指向服务器公网 IP
客户端(你的电脑)
# config/frpc.toml
serverAddr = "你的云服务器公网IP"
serverPort = 7000
auth.token = "你的自定义密钥-和服务端保持一致"
[[proxies]]
name = "wechat-bot-web"
type = "http"
localIP = "127.0.0.1"
localPort = 5000
customDomains = ["bot.example.com"]把 frpc.exe 放进项目根目录,双击 start-all.bat 会同时拉起穿透和机器人。
没有云服务器也没关系,可以用 cpolar、natapp 这类现成的穿透服务,免费版会给你一个随机子域名,够自己用了。
十一、权限体系

五级权限里,只有"最高权限"是写在 yaml 配置文件里的,其余都存在数据库、可以在群里任命:
@某人 指令777 # 设为至尊管理(仅至尊 / 最高权限可用)
@某人 指令666 # 设为超级管理
@某人 指令555 # 设为普通管理(超级管理以上可用)
@某人 指令444 # 取消管理设计上有一个刻意的安排:最高权限不受到期限制。这样即使所有群都过期了、数据库出问题了,我依然能用 yaml 里的 ID 进去救场。
十二、指令速查
完整指令 100 多条,群里发 菜单 就能看到全部。这里列最常用的:
| --- | --- |
| 基础 | 菜单 签到 我的名片 群信息 群空间 |
| 经济 | 我的金币 查币 转账 @某人 100 送花 @某人 金币排行 |
| 社交 | 求婚 @某人 我的婚姻 领养宝宝 结闺蜜 @某人 抓奴隶 @某人 |
| 游戏 | 抽签 抽盲盒 打劫 @某人 猜数字 猜成语 成语接龙 |
| 趣味 | 人品 运势 黄历 情话 天气 城市 历史上的今天 |
| 音乐 | 点歌 歌名 下一首 换乐库 |
| 钻石 | 钻石 兑换 兑换码 购买体力 |
| 管理 | #功能列表 #开启 功能名 #关闭 功能名 #我的权限 #管理列表 |
## 十三、踩过的坑(建议收藏)
1. **微信自动更新** — 头号杀手。装好就禁,一劳永逸。
2. **用主号** — 封号是真会发生的,用注册很久的小号,别绑银行卡。
3. **昵称污染** — 微信偶尔返回 wxid 当昵称,必须过滤,否则群里全是"wxid_xxx 签到成功"。
4. **@ 解析** — 只能从 `atuserlist` 里拿 wxid,从 content 里解析昵称是不可靠的。
5. **耗时操作串行** — 点歌和 AI 必须开线程,否则整条消息队列堵塞。
6. **群到期把管理员也锁外面** — 管理指令一定要排在到期拦截之前。
7. **云同步 token 不一致** — 本地 yaml 和云端 config.py 的 `sync_token` 必须一字不差,不同步时先查这个。
8. **回复太快** — 用 `reply_delay` 和每分钟条数限制,刷屏既影响体验也容易被微信限制。
9. **多群并发写 SQLite** — 人多了会 `database is locked`,要么加写队列,要么迁 MySQL。
10. **群空间默认密码** — 一定要改,那个面板能改本群所有配置。
## 十四、还能怎么扩展
- **接更多 AI 能力** — 目前 `ai_chat.py` 用的是 OpenAI 兼容格式,换 base_url 就能切到任意模型;加个图片理解也只是几行。
- **把数据搬到 MySQL** — 项目里已经有 `migrate_to_mysql.py`,多群大规模跑的时候必须迁。
- **开机自启** — Windows 任务计划程序里挂 `start-all.bat`,配合云服务器就能 7×24 在线。
- **加 HTTPS** — 群空间链接是发给所有群友的,上 Let's Encrypt 免费证书,别让密码明文跑在 HTTP 上。
## 最后
整个项目最难的不是写代码,是跟微信版本、消息脏数据、并发这些琐事死磕。但一旦跑通,能玩的空间非常大——它本质上就是一个"群消息进、任意服务出"的框架,你可以往里塞任何东西:接自己的业务系统、接数据库查询、接定时任务提醒,甚至当运维告警通道用。
如果你也准备搭一个,我的建议是:**先别急着加功能,先把"环境跑通 + 消息能收能发"这一步做扎实**,剩下的都是往上堆积木而已。