微信自动回复工具(带PyQt界面)
最近帮朋友处理微信客服消息,重复回复太费时间,干脆写了个带图形界面的自动回复工具。不用记命令,填个Excel路径点按钮就能跑,还能实时看日志,日常用着挺顺手。下面把完整代码和用法贴出来,有需要的可以直接拿去改。
先说说要准备的东西
- 环境:Python 3.8+(版本太高可能和pywin32不兼容)
要装的库:直接复制下面的命令到cmd里跑
pip install pywin32 pandas pyqt5 openpyxl
Excel回复表:建个Excel文件,第一列叫“关键词”,第二列叫“回复内容”,比如这样:
关键词 回复内容 你好 您好~有什么可以帮您? 下班时间 我们18点下班,急事可留言~ 价格 具体报价请发需求给我哦~ 
没有的朋友们不要急,我提供了默认数据文件下载,方便测试完整代码
import sys import time import win32gui import win32api import win32con import pandas as pd from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QLabel, QLineEdit, QPushButton, QTextEdit, QVBoxLayout, QHBoxLayout, QFileDialog) from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtGui import QFont, QPalette, QColor # 消息监听线程:单独开线程是为了不让界面卡住 class ReplyWorker(QThread): # 用来给界面发日志的信号 log_send = pyqtSignal(str) # 用来告诉界面线程是否正常启动 thread_status = pyqtSignal(bool) def __init__(self, excel_path): super().__init__() self.excel_path = excel_path self.reply_rules = {} # 存关键词和回复的对应关系 self.wechat_handle = 0 # 微信窗口的句柄 self.running = False # 控制线程运行的开关 def load_excel_rules(self): """加载Excel里的关键词和回复""" try: # 用openpyxl读xlsx格式,避免编码问题 df = pd.read_excel(self.excel_path, engine='openpyxl') # 转成字典,查起来快 self.reply_rules = dict(zip(df['关键词'], df['回复内容'])) self.log_send.emit(f"回复表加载成功!共{len(self.reply_rules)}条规则") return True except Exception as e: # 捕获各种错误:文件没找到、格式不对、列名错 err_msg = f"加载Excel失败:{str(e)}" self.log_send.emit(err_msg) return False def find_wechat(self): """找到微信窗口,返回是否找到""" # 微信PC端的窗口名一般是“微信”,类名留空不限制 self.wechat_handle = win32gui.FindWindow(None, "微信") if self.wechat_handle == 0: self.log_send.emit("没找到微信窗口!请先打开微信并登录") return False # 把微信窗口提到最前面 win32gui.SetForegroundWindow(self.wechat_handle) self.log_send.emit(f"找到微信啦!窗口句柄:{self.wechat_handle}") return True def find_child(self, parent_handle, class_name=None): """找窗口里的子控件(比如输入框、发送按钮)""" child_handles = [] # 递归遍历所有子控件 def _enum_child(hwnd, extra): if class_name is None or win32gui.GetClassName(hwnd) == class_name: extra.append(hwnd) return True win32gui.EnumChildWindows(parent_handle, _enum_child, child_handles) # 返回第一个找到的控件(一般够用) return child_handles[0] if child_handles else 0 def get_last_msg(self, chat_panel): """获取聊天面板里最后一条消息""" # 微信聊天面板的文本就是所有消息,按换行分割取最后一行 all_msg = win32gui.GetWindowText(chat_panel) if all_msg: return all_msg.split("\n")[-1].strip() return "" def send_reply(self, input_box, send_btn, reply_content): """模拟输入并发送回复""" # 先清空输入框:选中所有文本再删除 win32gui.SendMessage(input_box, win32con.EM_SETSEL, 0, -1) win32gui.SendMessage(input_box, win32con.WM_CLEAR, 0, 0) # 逐个字符输入(太快会乱码,加个小延迟) for char in reply_content: win32gui.SendMessage(input_box, win32con.WM_CHAR, ord(char), 0) time.sleep(0.02) # 点击发送按钮 win32gui.SendMessage(send_btn, win32con.BM_CLICK, 0, 0) def run(self): """线程主逻辑:启动后一直监听""" self.running = True # 先加载规则和找微信,有一个失败就退出 if not self.load_excel_rules() or not self.find_wechat(): self.thread_status.emit(False) self.running = False return self.thread_status.emit(True) # 定位微信的核心控件(不同版本可能要改class_name,用Spy++看) session_list = self.find_child(self.wechat_handle, "ContactPanel") # 会话列表 input_box = self.find_child(self.wechat_handle, "Edit") # 输入框 send_btn = self.find_child(self.wechat_handle, "Button") # 发送按钮 chat_panel = self.find_child(self.wechat_handle, "ChatPanel") # 聊天面板 # 检查控件是否都找到 if not all([session_list, input_box, send_btn, chat_panel]): self.log_send.emit("没找到微信的核心控件!可能版本不兼容") self.running = False return self.log_send.emit("所有控件已定位,开始监听消息...") # 循环监听未读消息 while self.running: # 遍历所有会话找带“未读”的 sessions = [] win32gui.EnumChildWindows(session_list, lambda hwnd, extra: extra.append(hwnd), sessions) for session in sessions: session_text = win32gui.GetWindowText(session) if "未读" in session_text: # 点击未读会话,切换到聊天界面 win32gui.SendMessage(session, win32con.BM_CLICK, 0, 0) time.sleep(0.5) # 等消息加载出来 # 提取联系人(去掉“未读”字样) contact = session_text.replace("未读", "").strip() # 提取最后一条消息 last_msg = self.get_last_msg(chat_panel) if not last_msg: continue self.log_send.emit(f"\n收到[{contact}]的消息:{last_msg}") # 匹配关键词找回复 reply = "抱歉呀,我暂时没理解你的意思~" for keyword, content in self.reply_rules.items(): if keyword in last_msg: reply = content break self.log_send.emit(f"准备回复:{reply}") # 发送回复 self.send_reply(input_box, send_btn, reply) self.log_send.emit("回复发送成功!") # 每2秒查一次,别占太多CPU time.sleep(2) def stop(self): """停止线程""" self.running = False self.log_send.emit("\n监听已停止") # 主界面窗口 class ReplyWindow(QMainWindow): def __init__(self): super().__init__() self.worker = None # 监听线程对象 self.init_ui() # 初始化界面 def init_ui(self): """画界面:布局、按钮、输入框这些""" # 窗口基本设置 self.setWindowTitle("微信自动回复工具 v1.0 - 作者:寒烟似雪 2025/12/29发布在字节曜www.ziyeyao.com博客") self.setFixedSize(1200, 1000) # 固定大小,不允许拉伸 self.setStyleSheet("background-color: #f5f5f5;") # 中心部件(主窗口必须有个中心部件才能放内容) central_widget = QWidget() self.setCentralWidget(central_widget) # 主布局(垂直排列:路径输入→按钮→日志) main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(15) # 控件之间的间距 main_layout.setContentsMargins(25, 25, 25, 25) # 边距 # 1. Excel路径选择区域(水平排列:标签→输入框→选择按钮) path_layout = QHBoxLayout() # 路径标签 path_label = QLabel("回复表路径:") path_label.setFont(QFont("微软雅黑", 10)) # 路径输入框 self.path_edit = QLineEdit() self.path_edit.setFont(QFont("微软雅黑", 9)) self.path_edit.setPlaceholderText("点击右侧按钮选择Excel文件") self.path_edit.setStyleSheet(""" QLineEdit { padding: 6px; border: 1px solid #ddd; border-radius: 4px; background-color: white; } """) # 路径选择按钮 self.select_btn = QPushButton("选择文件") self.select_btn.setFont(QFont("微软雅黑", 9)) self.select_btn.setStyleSheet(""" QPushButton { padding: 6px 12px; border: none; border-radius: 4px; background-color: #4285f4; color: white; } QPushButton:hover { background-color: #3367d6; } """) self.select_btn.clicked.connect(self.select_excel) # 绑定选择文件事件 # 把三个控件加到水平布局里 path_layout.addWidget(path_label) path_layout.addWidget(self.path_edit, stretch=1) # 输入框占满剩余空间 path_layout.addWidget(self.select_btn, stretch=0) main_layout.addLayout(path_layout) # 2. 启动/停止按钮区域(水平排列) btn_layout = QHBoxLayout() # 启动按钮 self.start_btn = QPushButton("启动监听") self.start_btn.setFont(QFont("微软雅黑", 10)) self.start_btn.setStyleSheet(""" QPushButton { padding: 8px 0; border: none; border-radius: 4px; background-color: #34a853; color: white; } QPushButton:disabled { background-color: #a0d9a0; } QPushButton:hover:enabled { background-color: #2d8d46; } """) self.start_btn.clicked.connect(self.start_listen) # 绑定启动事件 # 停止按钮 self.stop_btn = QPushButton("停止监听") self.stop_btn.setFont(QFont("微软雅黑", 10)) self.stop_btn.setStyleSheet(""" QPushButton { padding: 8px 0; border: none; border-radius: 4px; background-color: #ea4335; color: white; } QPushButton:disabled { background-color: #e8a098; } QPushButton:hover:enabled { background-color: #d33526; } """) self.stop_btn.clicked.connect(self.stop_listen) # 绑定停止事件 self.stop_btn.setEnabled(False) # 初始状态:停止按钮禁用 # 两个按钮平分宽度 btn_layout.addWidget(self.start_btn, stretch=1) btn_layout.addSpacing(10) # 按钮之间的间距 btn_layout.addWidget(self.stop_btn, stretch=1) main_layout.addLayout(btn_layout) # 3. 日志显示区域 # 日志标签 log_label = QLabel("运行日志:") log_label.setFont(QFont("微软雅黑", 10)) main_layout.addWidget(log_label) # 日志文本框(只读) self.log_edit = QTextEdit() self.log_edit.setFont(QFont("Consolas", 9)) self.log_edit.setReadOnly(True) self.log_edit.setStyleSheet(""" QTextEdit { padding: 8px; border: 1px solid #ddd; border-radius: 4px; background-color: white; color: #333; } """) main_layout.addWidget(self.log_edit, stretch=1) # 日志框占满剩余空间 def select_excel(self): """选择Excel文件,把路径填到输入框""" # 打开文件选择对话框,只显示Excel文件 file_path, _ = QFileDialog.getOpenFileName( self, "选择回复表", "", "Excel Files (*.xlsx; *.xls)" ) if file_path: self.path_edit.setText(file_path) def start_listen(self): """启动监听线程""" # 先检查路径是否填了 excel_path = self.path_edit.text().strip() if not excel_path: self.add_log("请先选择Excel回复表!") return # 检查线程是否已经在跑了 if self.worker and self.worker.isRunning(): self.add_log(" 监听已经在运行啦,不用重复启动") return # 创建线程对象,绑定信号 self.worker = ReplyWorker(excel_path) self.worker.log_send.connect(self.add_log) # 接收日志信号 self.worker.thread_status.connect(self.set_btn_status) # 接收线程状态信号 self.worker.finished.connect(self.thread_finished) # 线程结束时的信号 # 启动线程 self.worker.start() # 暂时禁用启动按钮 self.start_btn.setEnabled(False) self.add_log("正在初始化监听...") def stop_listen(self): """停止监听线程""" if self.worker and self.worker.isRunning(): self.worker.stop() # 禁用停止按钮,启用启动按钮 self.stop_btn.setEnabled(False) self.start_btn.setEnabled(True) else: self.add_log(" 监听还没启动呢,不用停止") def add_log(self, msg): """往日志框里加内容,自动滚到最下面""" # 加个时间戳,方便看什么时候发生的 time_str = time.strftime("[%H:%M:%S]", time.localtime()) self.log_edit.append(f"{time_str} {msg}") # 自动滚动到最后一行 self.log_edit.moveCursor(self.log_edit.textCursor().End) def set_btn_status(self, is_running): """根据线程状态设置按钮是否可用""" self.start_btn.setEnabled(not is_running) self.stop_btn.setEnabled(is_running) def thread_finished(self): """线程结束时的处理""" self.set_btn_status(False) self.add_log("监听线程已结束") def closeEvent(self, event): """窗口关闭时,确保线程也停了""" if self.worker and self.worker.isRunning(): self.worker.stop() self.worker.wait() # 等线程彻底结束 event.accept() def add_log(self, msg): """往日志里加内容(单独写个方法,方便调用)""" time_str = time.strftime("[%H:%M:%S]", time.localtime()) self.log_edit.append(f"{time_str} {msg}") self.log_edit.ensureCursorVisible() # 自动滚屏 # 程序入口 if __name__ == "__main__": app = QApplication(sys.argv) window = ReplyWindow() window.show() sys.exit(app.exec_())
怎么用?
- 先建好转发的Excel表(列名必须是“关键词”和“回复内容”)
- 运行代码,会弹出一个窗口
- 点击“选择文件”,找到你建的Excel表
- 点击“启动监听”,然后打开微信(必须是PC端,登录状态)
- 有人发消息含关键词,就会自动回复了,日志里能看到过程
- 不想用了就点“停止监听”,或者直接关窗口

注意事项
- 微信版本兼容问题
这个工具是靠pywin32识别微信窗口控件来实现的,不同版本的微信,控件的类名(比如ContactPanel、ChatPanel)可能不一样。如果运行时提示“没找到核心控件”,可以用Spy++(VS自带工具)查看你电脑上微信的控件类名,然后修改代码里find_child方法传入的class_name参数。 - 必须打开微信PC端
工具没法模拟微信登录,运行前一定要手动打开微信PC端并登录账号,而且最好不要最小化微信窗口,否则可能识别不到控件。 - Excel格式要求
Excel文件里必须有两列,列名严格对应“关键词”和“回复内容”,建议保存为.xlsx格式,避免编码问题。如果提示“加载Excel失败”,检查一下文件路径有没有中文或特殊字符,或者是不是被其他软件占用了。 - 避免重复回复
工具默认每2秒扫描一次未读消息,如果你担心同一条消息被重复回复,可以在代码里加一个“已处理消息列表”,把已经回复过的消息内容存进去,下次扫描时先判断是否在列表里。 - 权限问题
运行程序时,如果遇到“权限不足”的提示,右键点击Python.exe,选择“以管理员身份运行”,或者给当前用户授予窗口控制的权限。
常见问题解决
- 问题1:启动后提示“没找到微信窗口”
解决:确认微信PC端已经打开,并且窗口标题是“微信”(不是其他自定义标题);如果微信在任务栏隐藏,先点击显示出来。 - 问题2:能找到微信,但提示“没找到核心控件”
解决:用Spy++查看微信的会话列表、输入框、发送按钮的类名,替换代码里对应的class_name;比如有些版本的微信输入框类名是RichEdit20W。 - 问题3:回复内容发送乱码
解决:在send_reply方法里,延长字符输入的延迟时间,把time.sleep(0.02)改成time.sleep(0.05);同时确保Excel文件的编码是UTF-8。 - 问题4:线程启动后,界面卡死
解决:检查是不是把监听逻辑写在了主线程里——这个工具的监听代码在ReplyWorker线程里,和界面线程分离,不会卡界面;如果还是卡,大概率是控件识别耗时太长,可以减少扫描频率,把time.sleep(2)改成time.sleep(5)。
结语
这便是我制作的微信自动回复工具啦,有bug可以在评论区留言。有动手能力的朋友们可以尝试添加以下功能:
- 添加黑白名单:可以在Excel里加一列“是否启用”,或者单独建一个黑白名单文件,指定哪些联系人需要自动回复,哪些不需要。
- 支持多关键词匹配:现在是匹配到第一个关键词就回复,可以改成支持多个关键词同时匹配,比如“价格”和“优惠”同时出现时,回复特定内容。
- 定时启停:添加一个时间选择控件,设置每天的监听时间段,比如只在9:00-18:00运行,其他时间自动停止。
- 回复记录导出:把收到的消息和发送的回复记录到本地文件(比如CSV),方便后续查看和统计。