简介:这是一套面向需要接入微信能力的Python开发者的API交互工具包,基于Python 3.9.10 x64环境,提供社区版与专业版两种功能版本,适合实现微信自动回复、消息推送、数据统计等场景。压缩包共142个文件,整体约40.26MB,文件类型以HTML接口文档为主,配合pyd动态库、DLL组件、Python脚本、exe辅助工具及CSS/JS前端资源,既可用于直接调用,也方便二次开发。目前已有166人学习下载。包内除核心调用库外,还附带requirements.txt依赖清单、readme说明、版本记录及常见问题解决方案,尤其是接口使用文档对理解API调用逻辑、参数含义和错误处理很有帮助;社区版与专业版同时提供,也能满足不同层级的使用需求。整体来看,这套资源覆盖了从环境配置到功能调用、从文档查阅到问题排查的完整链路,适合希望快速上手微信API开发的初中级开发者参考使用。
1. WeChatPYAPI 不是机器人框架:它是把 Python 挂进微信回调的桥
做过微信个人号自动化的人都有同感:网页版接口关停之后,能用官方渠道做的事越来越少,剩下的路基本都指向两个方向——要么用企业微信的 API,要么在本地找一个能拦截微信事件回调的入口。WeChatPYAPI 属于后者,它在 PC 微信内部挂一个 Python 运行时,让消息、好友请求、群变更这类事件直接流进你的 Python 函数。你可以把它理解为“微信事件的本地回调服务器”,而不是一个封装好指令的机器人框架——机器人该有的聊天、发图、踢人功能它都不直接提供,但任何 Python 能写的逻辑,它都能挂上去。适合谁用?想在自己电脑上跑一套消息转发、关键词回复、聊天记录归档脚本的开发者,尤其是那些希望逻辑完全掌握在自己手里、不经过第三方云平台的人。
这个项目本身不带界面、不带数据库,核心就两件事:加载微信进程里的 hook 环境,把你的 Python 函数注册成事件回调。所以本篇文章要讲的不是“怎么用现成的机器人指令”,而是“怎么把回调链路的每个环节拆明白”——从安装、启动、注册回调,到消息对象长什么样、参数怎么调,再到多开和内存泄漏这些坑,最后给出一套能直接跑的 Demo 工程拆解。
2. 安装与第一条回调链路:从 pip 到事件触发
2.1 依赖项不是只有 python,还得看微信版本
WeChatPYAPI 的安装比一般 Python 库多一步:它不是一个纯 Python 包,而是带 C++ 侧 hook 逻辑的扩展模块。安装前先确认三件事:Python 必须是 32 位、PC 微信是 3.x 的某个特定版本、操作系统是 Windows。第一点最容易被忽略,因为现代开发机默认装的都是 64 位 Python,但 hook 模块要注入到微信进程里,微信本身还是 32 位程序,所以宿主 Python 也必须是 32 位,否则 ctypes 访问进程内存时会直接报错。
# 建议用 conda 单独建一个 32 位环境,避免污染日常开发环境 conda create -n wechatbot32 python=3.8 conda activate wechatbot32 # 安装核心库 pip install WeChatPYAPI # 如果网络环境不好,可以先下载 wheel 再本地安装 pip install WeChatPYAPI-xxx-cp38-win32.whl这里有一个常见做法:不用 conda 的用户,建议在安装官方 Python 3.8 时勾选“Add to PATH”,安装完后用python -c "import platform; print(platform.architecture())"确认输出('32bit', 'WindowsPE')。64 位环境后面跑 demo 时,遇到ERROR: Could not attach to process这类提示,大概率不是代码问题,是位数不匹配。还有一点,微信版本不能乱升,项目对 hook 地址做了硬编码,微信用的是修改版还是官方版、版本号是否匹配,直接决定start接口能不能拉起来,这部分先查README里写的版本对照表,别闭着眼睛装最新版。
2.2 初始化回调管理器:事件循环必须由你启动
库安装完之后,第一件要理解的事是:导入包然后挂一个@register装饰器,不会让脚本开始运行。你需要显式地创建一个WxApi实例、传入回调函数的注册表、然后调用run方法让消息循环跑起来。这和大多数异步框架的run_forever思路类似,但区别在于,你写的脚本一旦run,主线程就阻塞在事件循环上了,后面的代码不会再执行。所以业务逻辑要么写在回调函数内部,要么另起线程。
from WeChatPYAPI import WxApi from WeChatPYAPI import MessageTypes class MyHandler: def __init__(self): self.api = WxApi() self.api.register(self.handle_message) # 注册消息回调 self.api.register(self.handle_friend) # 注册好友事件回调 def handle_message(self, msg): # msg 是一个字典,包含 type, data 等字段 if msg["type"] == MessageTypes.TEXT: sender = msg["data"]["from_wxid"] content = msg["data"]["msg"] print(f"[{sender}] -> {content}") return True # 返回 True 表示处理成功 def handle_friend(self, msg): print("好友事件", msg) return True if __name__ == "__main__": h = MyHandler() # 启动回调管理器,阻塞运行 h.api.run()说明一下参数逻辑:register可以多次调用,每次注册一个回调函数,事件类型区分靠msg["type"]字段而不是靠函数名;run()启动内部循环后,微信进程里的所有消息事件都会按你注册的顺序回调。返回值True代表本次事件已处理,如果返回False或抛异常,库会把这次的原始报文缓存到内存里,并在下次触发同一事件类型时重试,这一点开发时特别容易踩到:回调里抛异常不捕获,消息队列会越积越多。
2.3 消息对象拆解:字段不是from_user而是from_wxid
第一次看回调数据的人通常都会愣一下:微信项目里的字段命名和常见聊天机器人 SDK 差异很大,没有user_id、nickname这种直观命名,取而代之的是wxid、to_wxid、from_wxid。比如一条文本消息,data字段里拿到的是wxid1和wxid2这种微信号 ID,而不是可读的用户名。如果你要做的是把消息存进数据库,需要再用联系人接口反查昵称和备注,这一步很多人会忘记。
def handle_message(self, msg): if msg["type"] == MessageTypes.TEXT: data = msg["data"] # 字段清空:from_wxid 是发送者,to_wxid 是自己 sender = data["from_wxid"].strip() receiver = data["to_wxid"].strip() content = data["msg"].strip() # 群聊和单聊都走同一个回调,用 from_wxid 是否带 @chatroom 区分 is_group = "@chatroom" in sender if is_group: # 群聊场景下,实际发言人是 data 里的 wxid1 actual_speaker = data.get("wxid1", sender) # 群名在 data["group_info"]["nickname"] 里 group_name = data.get("group_info", {}).get("nickname", "")这段代码揭示了两个设计决策:一是所有会话类型共用同一个回调入口,区分方式靠wxid里是否包含@chatroom;二是群里有人发言时,from_wxid指向的是群本身,真实发言人放在wxid1里,这是从协议层继承来的习惯。参数get("wxid1", sender)这种写法是防御式的,因为图片、语音这类非文本消息的data字段结构不一样,不确定有没有这个 key。调试中最快的验证方式是print(json.dumps(msg, ensure_ascii=False, indent=2)),把完整报文打出来对照文档看,微信的字段有时会有空字符串、有时缺 key,多打几次比翻源码高效得多。
3. 把裸回调调教成可控机器人:消息路由与状态机实战
3.1 全局消息分发器:先拆包再派发
直接用handle_message处理一切逻辑是能跑,但一旦脚本超过两百行,回调函数就会变成一坨 if-else,任何一个分支抛异常,整个消息循环都受影响。我一般会在回调入口加一层简单的分发器,按消息类型路由到不同的处理函数,每个处理函数只负责单一动作。
class MessageRouter: def __init__(self, api): self.api = api self.handlers = { MessageTypes.TEXT: self.on_text, MessageTypes.IMAGE: self.on_image, MessageTypes.FRIEND_REQUEST: self.on_friend_request, MessageTypes.SYSTEM: self.on_system, } def dispatch(self, msg): handler = self.handlers.get(msg.get("type")) if handler is None: # 未知类型:不能也不打断后续消息,记个日志就好 print(f"[未处理类型] {msg.get('type')}") return True try: return handler(msg.get("data", {})) except Exception as e: print(f"[回调异常] {e}") # 返回 True 表示事件已消费,避免失败重试堆积 return True关键点在于把“业务异常”和“框架异常”分开:捕获异常并返回True,事件被消费掉,日志里能看到错;如果把异常抛出去,回调被库重试,同一个消息会反复触发,配合微信端本身也保留一条未读消息,等于双重播放。还有个细节是dispatch里处理完就立刻return True,不要在业务函数内部睡太长时间,比如做语音识别,如果耗时超过两秒,微信侧会认为消息未处理,可能造成重发。处理耗时任务的标准做法是先返回,再用threading.Thread丢到后台去执行。
3.2 会话状态机:让机器人记住上一轮
纯回调模型有个天然的毛病:它是事件驱动的,不保留对话上下文。用户说一句“查天气”,下一句“北京”如果单独进来,回调里拿到的只是“北京”这两个字,没有和“查天气”产生关联。状态机不复杂,核心就是一个dict,以wxid为 key ,值为当前状态对象,等用户下一次发消息时先查状态再决定走哪个分支。
import re from collections import defaultdict class SessionManager: def __init__(self): self.sessions = defaultdict(dict) self.intent_map = { "查天气": "ASK_CITY", "设置提醒": "ASK_TIME_AND_EVENT", } def step(self, wxid, text): session = self.sessions[wxid] state = session.get("state", "IDLE") if state == "IDLE": # 匹配意图 for keyword, next_state in self.intent_map.items(): if keyword in text: session["state"] = next_state session["pending_intent"] = next_state return "好的,请告诉我城市名(查天气)或时间+事件(提醒)" return "我不太理解,试试说:查天气 / 设置提醒" if state == "ASK_CITY": # 用户上一步说要查天气,现在这个文本就是城市名 city = text.strip() # 演示:把状态清掉,结束会话 session.clear() return f"收到,开始查询 {city} 的天气" if state == "ASK_TIME_AND_EVENT": parts = text.split() if len(parts) >= 2: session.clear() return f"提醒已设置:{parts[0]} {' '.join(parts[1:])}" return "格式不对,用空格分隔时间和事件,比如:18:00 开会"这里体现的是状态机的一个常见做法:不维护复杂的转移矩阵,而是用dict + 状态字符串完成上下文衔接。要注意session.clear()之后defaultdict的行为——再访问同一个wxid会拿到新的空字典,所以下一次 IDLE 分支的匹配是可以正常触发的。内存方面,长期运行时会话积累,建议给每个wxid加一个最后活跃时间,超过 30 分钟没活跃直接del self.sessions[wxid],不然跑一天下来内存里挂几万个死会话。
3.3 联系人信息反查:从 wxid 到可读昵称
前面提到消息体里只有wxid,要做“给 xx 发消息”这种操作,得先把wxid映射成昵称或备注。WeChatPYAPI 提供联系人列表接口,但它是全量拉取,本地需要建索引。一个实用的做法是启动时加载一次联系人全量表,存入内存字典,之后收到消息直接查表。
def build_contact_map(api): contact_dict = {} # 文档里的取联系人函数,返回列表,每个元素是 dict contact_list = api.get_contacts() for c in contact_list: wxid = c.get("wxid") # remark 优先级高于 nickname,备注没填时才用昵称 name = c.get("remark") or c.get("nickname") or wxid contact_dict[wxid] = name return contact_dict def resolve_name(contact_dict, wxid): """把 wxid 转成展示名;查不到时返回原 wxid 而不是报错""" name = contact_dict.get(wxid) if not name: # 群成员可能不在联系人表里,按群关系再查一次或做缓存标记 return wxid.split("@")[0] return name要注意全量联系人接口的返回表里,群聊是一个独立的wxid,群成员不会单独出现在这个列表里,所以群里发言人的昵称需要通过群成员接口单独拉取,这个接口通常是异步的,第一次拉群成员时可能返回空列表,要轮询等待。如果联系人数量很大,比如几千人,每次全量刷新会卡顿,建议只在启动时刷一次,后续用事件回调增量更新,对于大多数个人号场景几百人的量级,一次性全量加载完全够用。
4. 避坑排查:多开、内存泄漏、回调失效的血泪经验
4.1 微信版本一升级,hook 就失效
现象:脚本昨天还跑得好好的,今天打开电脑微信自动升级到新版本,然后run()能启动,但任何消息都不触发回调函数。
原因:WeChatPYAPI 的 hook 点是特定版本里写死的函数地址,微信升级后函数偏移变了,或者钩子被检测到直接失效。这种失效不是崩溃,是静默失败,最迷惑人。
解决:先把微信降级到项目文档指定版本,然后禁止微信自动更新。在微信的配置文件里找到更新设置关闭自动升级,或者直接把安装目录里的Update文件夹设置成只读、删掉更新程序。从那以后我每次做新环境部署都强制加这一条:写完pip install第一步先查版本,第二步关更新,第三部启动脚本验证,三步缺一个都不算环境就绪。
4.2 回调里一抛异常,内存就飙到几个 G
现象:脚本跑两小时后,微信进程内存从 200M 涨到 1.5G,聊天发消息越来越卡,最后直接无响应。
原因:回调函数内json.loads解析失败或者访问了不存在的 key 导致异常,异常被库捕获后进入重试队列,同一事件反复入队。而微信侧的消息又不断进来,新事件继续加入队列,两边叠加等于无限堆积。
解决:给所有回调函数套一层 try-except,并保证任何分支都返回True。更稳妥的做法是在分发器的dispatch里做统一拦截,前面第 3.1 的代码就是这个思路。另外,定位哪条消息引起的问题,可以在捕获异常时把msg的msg_id打出来,用这个 id 去日志里反查原始报文,微信同一消息有唯一 id,排查时比看时间戳高效得多。
4.3 多开两个微信账号,只有一个账号能触发回调
现象:同一台电脑开了两个微信进程,第二个微信登录后,任何消息都没有反应,但第一个账号正常。
原因:WeChatPYAPI 默认 hook 的是微信主进程里的第一个实例,第二个实例的进程内存结构和事件回调通道没有注册成功。这不算 bug,而是这个库本身的设计边界,它在设计上就没有把多实例的隔离做好。
解决:常见做法是只跑一个被 hook 的微信,另一个微信不用自动化能力。如果非要两个账号都有回调,只能起两台 Windows 机器或者用虚拟机隔离,把第二个账号放到虚拟机里的另一个微信 + 另一套 Python 脚本里。群里有人尝试批量修改配置文件来支持多开,那属于绕协议的行为,既不稳定又可能触发风控,不值得试。
4.4 发送消息接口时好时坏,间隔一长必定失败
现象:send_text接口发送消息,连续发三条没问题,间隔十分钟再发就报错,或者报成功但对方没收到。
原因:这个库的发送动作本质上是调用微信内部的发送函数,微信对高频发送有频率控制,但不是简单限流,而是会随机失效。另外,发送成功后回调里没有 ACK 机制,你看到的“成功”只是函数返回了0,实际是否送达是黑匣子。
解决:需要做发送确认。一个可行方案是:发消息后延迟 0.5 到 1 秒,然后通过get_msg类型的事件接口去拉取最近的消息记录,匹配自己刚发的client_msg_id。如果匹配不到,按失败处理,重新发送。这个逻辑写成独立的send_with_check函数,作为所有发送操作入口,别在业务代码里裸调send_text。还有,风控期的账号发消息成功率会骤降,这不是代码能解决的,建议遇到这种情况降低发送频率、多人工聊几句养号。
4.5 Python 进程退出后,微信进入“假死”状态
现象:Ctrl+C结束 Python 脚本后,微信还能打开,但点联系人没反应,收不到消息,重启微信才能恢复。
原因:hook 注入的 DLL 还残留在微信进程里,Python 侧的事件循环断开了,微信内部的回调地址指向已释放的内存,成了野指针,微信 UI 线程一访问就卡住。
解决:不要用Ctrl+C强杀,要调用库提供的stop或unhook接口,让 DLL 从微信进程里安全卸载。如果已经强杀了,重启微信是最快的恢复方式。另外,每次启动脚本时先检查微信进程里是不是还有上一次的残留,用tasklist | findstr WeChat看一眼,如果有多个微信进程并且内存看起来异常,就taskkill /F /IM WeChat.exe之后再启动。
5. 资源落地:Demo 工程怎么跑、参数边界在哪
5.1 下载包里到底有什么
下载后解压,目录结构大概是这样的:
WeChatPYAPI_demo/ ├── requirements.txt ├── config.yaml # 运行参数配置 ├── main.py # 入口,初始化 + 启动 ├── handlers/ │ ├── __init__.py │ ├── text_handler.py # 文本消息处理 │ ├── image_handler.py # 图片消息处理 │ └── system_handler.py # 系统通知处理 ├── services/ │ ├── contact_service.py # 联系人索引 │ ├── session_manager.py # 会话状态机 │ └── log_service.py # 日志落盘 └── tests/ ├── test_router.py └── test_session.py这不是什么庞大的框架工程,几行代码就能看完。值得参考的是它把入口、路由、业务逻辑三层分开的组织方式,main 里不写业务,handlers 里不碰微信 API 细节,services 只做数据管理。新手可以把这套结构原封不动拿来当自己的项目骨架,熟手可以直接把handlers和services替换成自己的业务逻辑。
requirements.txt内容大致是这种风格:
WeChatPYAPI==2.1.4 PyYAML==6.0 requests==2.31.05.2 直接跑的步骤与参数说明
第一件事是改配置文件config.yaml,里面有几个参数直接决定行为:
wechat: auto_login: true save_qr_path: "./qr.png" # 扫码登录二维码保存路径 hook_mode: "inject" # inject=注入到现有微信进程 bot: name: "demo-bot" enable_group: true # 是否处理群消息 group_whitelist: [] # 白名单留空表示所有群都处理 keyword_reply: "你好": "你好呀,我是自动回复机器人"wechat.auto_login为true时要配合save_qr_path使用,二维码会在启动时生成到本地文件,用微信扫码登录。如果希望手动开微信窗口去扫码,就把auto_login改成false并且不填save_qr_path。bot.enable_group是群聊总开关,关掉之后群消息不会进你的回调,这是节流的手段之一,真机上调试时建议先关掉群消息,只跑单聊,减少干扰。
启动命令非常简单:
conda activate wechatbot32 python main.py起来之后如果控制台打印[WeChatPYAPI] 微信客户端注入成功就说明 hook 环境就绪。这时用另一个微信给你自己发条“你好”,如果你在keyword_reply配置了它,会自动回一句。整个过程不需要写任何新代码。
5.3 自定义行为的接入口
Demo 里默认的路由规则是优先匹配config.yaml里的keyword_reply,匹配不到再落到handlers/text_handler.py。如果你想加一个新的关键词回复,直接改 YAML 就行,无需改代码;但如果你想加一个复杂逻辑,比如“收到图片后调用 OCR 识别”,那就需要进入handlers/image_handler.py:
def on_image(data): # data 里会有图片的本地缓存路径 img_path = data.get("img_path") if not img_path: return None # 这里接 OCR 服务 ocr_result = run_ocr(img_path) return f"图片识别结果:{ocr_result}"注意img_path这个字段在部分版本里不存在,而是data["data"]["file_path"],官方文档的示例和实际运行结果偶尔不一致,代码里要对两种 key 都做兼容。整体来看,资源包的价值不在代码量,而在于把事件从回调到路由到业务的完整链路搭出来了,你换业务只需要替换最里层的处理函数。
6. 进阶:把回调消息重放成离线数据集,给机器人做语料分析
最后一个值得掌握的技巧,是把在线回调模式切换成离线分析模式。WeChatPYAPI 支持把微信侧的消息落盘保存成日志文件,之后通过日志回放模拟消息流,不回连微信也能测试你的路由和状态机逻辑。具体实现:把WxApi的回调入口抽象出一个基类,在线回调实现走微信 API,离线回放实现从日志文件逐条读取消息,构造同样结构的msg字典后喂给同一个dispatch。这样业务代码完全不用改,就能在 CI 环境里跑回归测试。
import json def replay_log(log_path, router): with open(log_path, "r", encoding="utf-8") as f: for line in f: row = json.loads(line.strip()) msg = { "type": row["type"], "data": row["data"], } # 这里和在线回调调用同一个方法 router.dispatch(msg)离线回放最直接的价值是:你不需要微信实弹实发就能验证状态机在长对话下的表现。比如用户依次发了“查天气”、“北京”、“查天气”、“上海”,四次消息塞进SessionManager,检查每次返回的状态转移是否符合预期。我之前在一台没有微信的 Linux 机器上跑过一晚上回放测试,抓到过连续三次“查天气”导致会话状态被覆盖的 bug——这种问题在线模式下要手动发上百条消息才撞得出来,离线一小时就复现了。
从那以后我每次改完路由逻辑,都强制走一遍离线回放再上真机,血泪经验告诉我:别拿微信上的真人聊天当测试环境,你永远不会知道哪一条消息会触发风控。如果你的目标是长期维护一个微信自动化项目,把回放脚本放进测试套件里,和写业务逻辑一样重要。希望帮到你。
本文还有配套的精品资源,点击获取