Python 自动化微信消息发送:uiautomation
与 uiautomation2
实战指南
在日常工作和生活中,我们可能需要重复地给某些微信联系人或群组发送相同的消息,例如每日问候、定时提醒、信息通知等。手动操作不仅效率低下,还容易出错。幸运的是,借助 Python 的自动化库,我们可以轻松实现微信消息的自动发送。
本文将介绍两个强大的 UI 自动化库:uiautomation
(针对 Windows 平台) 和 uiautomation2
(针对 Android 平台),并通过实例代码演示如何使用它们来自动化微信PC版和安卓版的消息发送。
本文目标
- 了解
uiautomation
库及其在 Windows 微信自动化中的应用。 - 了解
uiautomation2
库及其在 Android 微信自动化中的应用。 - 掌握通过代码定位微信UI元素并执行操作的基本方法。
- 学习编写简单的 Python 脚本实现微信消息自动发送。
准备工作
通用准备:
- Python 环境: 确保你已安装 Python 3.x。
- 微信客户端:
- 对于
uiautomation
:安装最新版的微信 PC 客户端。 - 对于
uiautomation2
:在 Android 手机或模拟器上安装最新版的微信 App。
- 对于
1. uiautomation
(Windows PC 版微信)
- 安装库:
pip install uiautomation
- 辅助工具 (可选但推荐):
- Inspect.exe: Windows SDK 自带的工具,用于查看UI元素的属性。
- UI Spy (UISpy.exe): 类似的UI元素查看工具。
automation.py -h
查看uiautomation
自带的元素查看器用法。 (如python -m uiautomation -t 3
)
2. uiautomation2
(Android 版微信)
- 安装库:
pip install uiautomation2 weditor
weditor
是一个基于 Web 的 UI 查看器,非常方便。 - Android 环境:
- ADB (Android Debug Bridge): 确保 ADB 已安装并配置到系统环境变量。你可以从 Android SDK Platform Tools 下载。
- 手机/模拟器开启开发者选项和 USB 调试。
- 初始化
uiautomation2
:
在手机上安装atx-agent
(通常uiautomation2
会尝试自动安装)。连接手机到电脑,并运行:
确保手机已通过 USB 连接并授权调试。python -m uiautomator2 init
一、使用 uiautomation
自动发送 PC 版微信消息
uiautomation
是一个基于 Windows UI Automation API 的 Python 模块,可以方便地自动化 Windows 桌面应用程序。
核心思路:
- 定位到微信主窗口。
- 在搜索框中输入联系人名称。
- 点击搜索结果中的联系人,进入聊天界面。
- 定位到消息输入框,输入消息。
- 点击发送按钮。
示例代码:
import uiautomation as auto
import time
import pyperclip # 用于处理中文输入
def send_wechat_message_pc(contact_name: str, message: str):
"""
使用 uiautomation 给 PC 版微信发送消息。
确保微信已登录并在前台。
"""
print(f"准备向PC微信联系人 '{contact_name}' 发送消息: '{message}'")
# 1. 获取微信主窗口
# 通常微信窗口的 ClassName 是 'WeChatMainWndForPC'
# 你可以使用 Inspect.exe 或 automation.py -t 3 来确认
wechat_window = auto.WindowControl(ClassName='WeChatMainWndForPC')
if not wechat_window.Exists(timeout=5):
print("错误:未找到微信主窗口。请确保微信已登录并打开。")
return
# 将窗口置顶,方便操作 (可选)
wechat_window.SetTopmost(True)
time.sleep(0.5)
# 2. 定位搜索框并点击,然后输入联系人
# 搜索框通常 AutomationId 是 'searchEdit' 或 Name 是 '搜索'
# 使用 auto.EditControl(searchDepth=10, Name='搜索') 或其他属性定位
# 技巧:先点击一下左侧的“聊天”列表,确保焦点在聊天列表区域
try:
chat_list_btn = wechat_window.ButtonControl(Name='聊天')
if chat_list_btn.Exists(0,0):
chat_list_btn.Click(simulateMove=False)
time.sleep(0.2)
except Exception:
pass # 可能已经在聊天界面
search_box = wechat_window.EditControl(Name='搜索') # PC微信更新后,搜索框可能在主窗口的子控件中
if not search_box.Exists(2,1):
print("错误:未找到搜索框。")
wechat_window.SetTopmost(False)
return
search_box.Click(simulateMove=False)
time.sleep(0.5)
# search_box.SetValue(contact_name) # SetValue 对于某些应用输入中文可能存在问题
pyperclip.copy(contact_name)
auto.SendKeys('{Ctrl}v') # 使用Ctrl+V粘贴,兼容中文
time.sleep(1) # 等待搜索结果
# 3. 点击搜索结果中的联系人
# 搜索结果通常在 "会话" Pane 下的 ListItemControl
# 注意:这里需要根据实际情况调整,可能需要更精确的定位
# 可以使用 contact_name 来定位 ListItemControl
contact_item = wechat_window.ListItemControl(Name=contact_name, searchDepth=15) # 增加搜索深度
if not contact_item.Exists(2,1):
print(f"错误:未在搜索结果中找到联系人 '{contact_name}'。请检查名称是否准确或手动打开聊天窗口。")
# 尝试按回车键,因为第一个搜索结果往往是目标
auto.SendKeys('{Enter}')
time.sleep(1)
# 再次检查当前聊天对象是否正确 (可选,较复杂)
else:
contact_item.Click(simulateMove=False)
time.sleep(1) # 等待聊天窗口加载
# 4. 定位消息输入框并输入消息
# 输入框通常是 EditControl,Name 可能是 "输入" 或为空,需要用其他属性辅助
# 通常在当前聊天窗口的最后一个 EditControl
input_box = wechat_window.EditControl(searchDepth=20) # 通常是最后一个EditControl,可以加更精确的条件
if not input_box.Exists(2,1): # 有时候输入框没有Name,只能通过其他方式定位,如ClassName='RichEditComponent'
# 尝试一种更通用的定位方式:寻找当前窗口中最后一个EditControl
edits = wechat_window.GetChildren()
target_edit = None
for child in edits:
if child.ControlTypeName == 'EditControl': # 迭代查找
# 通常输入框在比较靠下的位置
# print(f"Found Edit: {child.Name}, {child.BoundingRectangle}")
target_edit = child # 简单取最后一个(可能不准,需要调试)
if target_edit:
input_box = target_edit
else:
print("错误:未找到消息输入框。")
wechat_window.SetTopmost(False)
return
input_box.Click(simulateMove=False)
time.sleep(0.5)
# input_box.SetValue(message)
pyperclip.copy(message)
auto.SendKeys('{Ctrl}v')
time.sleep(0.5)
# 5. 点击发送按钮
# 发送按钮通常是 ButtonControl,Name 是 "发送"
send_button = wechat_window.ButtonControl(Name='发送')
if not send_button.Exists(2,1):
print("错误:未找到发送按钮。尝试直接按回车发送。")
auto.SendKeys('{Enter}') # 如果找不到发送按钮,尝试直接回车
else:
send_button.Click(simulateMove=False)
print(f"消息已尝试发送给 '{contact_name}'。")
wechat_window.SetTopmost(False) # 取消置顶
if __name__ == '__main__':
# 确保微信PC版已登录并打开
# 首次运行前,最好手动操作一遍,观察微信的响应,并用Inspect.exe等工具确定控件名称
target_contact = "文件传输助手" # 替换为你要发送的联系人名称,确保名称在微信中完全一致
message_to_send = "你好,这是一条来自Python的自动消息!(PC)"
# 等待几秒,给你切换到微信的时间
print("请在5秒内确保微信窗口是激活状态...")
time.sleep(5)
send_wechat_message_pc(target_contact, message_to_send)
使用 uiautomation
的注意事项:
- 控件属性变化: 微信版本更新可能会导致控件的
Name
,AutomationId
,ClassName
等属性发生变化,脚本可能需要随之调整。 - 定位精度:
searchDepth
参数可以帮助在复杂的UI结构中找到元素。有时需要组合多个属性来唯一定位一个控件。 - 中文输入:
SetValue()
方法直接输入中文可能存在问题,推荐使用pyperclip
配合SendKeys('{Ctrl}v')
进行粘贴。 - 延时: 在点击、输入等操作后加入适当的
time.sleep()
,等待UI响应,避免操作过快导致失败。 - 窗口状态: 确保微信窗口是激活的,或者在代码中先激活它。
二、使用 uiautomation2
自动发送 Android 版微信消息
uiautomation2
是一个强大的 Android UI 自动化框架,它通过 Python 连接到 Android 设备(真实手机或模拟器)上运行的 uiautomator2 server
(通常是 atx-agent
),进而控制设备上的应用。
核心思路:
- 连接到 Android 设备。
- 启动微信 App (如果未启动)。
- 点击微信顶部的搜索按钮。
- 在搜索框中输入联系人名称,并点击搜索结果。
- 在聊天界面,定位输入框并输入消息。
- 点击发送按钮。
辅助工具:weditor
启动 weditor
非常简单,连接手机后,在命令行运行:
python -m weditor
它会在浏览器中打开一个页面,可以实时查看手机屏幕,并获取UI元素的 resourceId
, text
, className
, description
等属性,极大方便了元素定位。
示例代码:
import u2 as uiautomator2
import time
def send_wechat_message_android(device_serial: str, contact_name: str, message: str):
"""
使用 uiautomation2 给 Android 版微信发送消息。
确保手机已通过 adb 连接,并且微信已登录。
"""
print(f"准备向Android微信联系人 '{contact_name}' 发送消息: '{message}'")
try:
# 1. 连接设备
if device_serial:
d = uiautomator2.connect(device_serial) # 连接指定设备
else:
d = uiautomator2.connect() # 自动连接当前连接的第一个设备
print(f"已连接到设备: {d.device_info}")
# 可选:解锁屏幕 (如果屏幕锁定了)
# d.unlock() # 需要 ATX 应用支持
# d.screen_on()
# 2. 启动微信 (如果应用已在后台,会切换到前台;如果未运行,会启动)
# 微信的包名通常是 com.tencent.mm
d.app_start("com.tencent.mm", stop=True) # stop=True 表示如果已运行,先停止再启动,确保是全新状态
print("微信已启动,等待几秒加载...")
time.sleep(5) # 等待微信完全加载
# 3. 点击微信主界面的搜索按钮
# 通过 weditor 找到搜索按钮的 resourceId 或 description
# 示例 resourceId: "com.tencent.mm:id/f8y" (此ID会变,请用weditor确认)
# 或者通过 description: "搜索"
if d(description="搜索", className="android.widget.RelativeLayout").exists(timeout=10):
d(description="搜索", className="android.widget.RelativeLayout").click()
elif d(resourceId="com.tencent.mm:id/lq").exists(timeout=5): # 另一种可能的搜索图标ID
d(resourceId="com.tencent.mm:id/lq").click()
else:
print("错误:未找到微信主界面的搜索按钮。")
return
time.sleep(1)
# 4. 在搜索框输入联系人名称
# 搜索输入框的 resourceId: "com.tencent.mm:id/bhn" (此ID会变)
# 或者通过 text: "搜索"
search_input_field = d(text="搜索", className="android.widget.EditText")
if not search_input_field.exists(timeout=5):
# 如果上面那个找不到,可能是上一个点击后直接进入的搜索页面,其ID不同
search_input_field = d(resourceId="com.tencent.mm:id/khf") # 假设这是搜索页面的输入框
if not search_input_field.exists(timeout=3):
print("错误:未找到搜索输入框。")
return
search_input_field.set_text(contact_name)
time.sleep(2) # 等待搜索结果出现
# 点击搜索结果中的联系人
# 通常联系人显示为 TextView,其 text 属性为 contact_name
# 注意:如果搜索结果有多个同名,这里会点击第一个匹配的
# 可能需要更复杂的逻辑来区分,比如通过父容器的某些特征
contact_item = d(text=contact_name, className="android.widget.TextView")
# 有时候联系人结果在一个更复杂的列表中,可以尝试更具体的路径
# 例如:d(resourceId="com.tencent.mm:id/cat").child(text=contact_name)
if contact_item.exists(timeout=5):
contact_item.click()
else:
print(f"错误:未找到联系人 '{contact_name}' 的搜索结果。")
# 尝试直接点击第一个搜索结果项(如果结构固定)
# d(resourceId="com.tencent.mm:id/gbh").click() # 假设这是第一个结果的固定ID
return
time.sleep(2) # 等待进入聊天界面
# 5. 在聊天界面输入消息
# 输入框的 resourceId: "com.tencent.mm:id/b2t" (此ID会变)
chat_input_field = d(resourceId="com.tencent.mm:id/auj", className="android.widget.EditText") # 这是一个常见的聊天输入框ID
if not chat_input_field.exists(timeout=5):
print("错误:未找到聊天界面的消息输入框。")
return
chat_input_field.set_text(message)
time.sleep(1)
# 6. 点击发送按钮
# 发送按钮的 resourceId: "com.tencent.mm:id/ay5" (此ID会变)
# 或者通过 text: "发送"
send_button = d(text="发送", className="android.widget.Button")
if not send_button.exists(timeout=5):
print("错误:未找到发送按钮。")
return
send_button.click()
print(f"消息已尝试发送给 '{contact_name}'。")
time.sleep(1)
# 可选:返回主屏幕或关闭微信
# d.press("back") # 按一次返回
# d.press("home") # 按Home键
# d.app_stop("com.tencent.mm") # 关闭微信
except Exception as e:
print(f"发生错误: {e}")
finally:
if 'd' in locals() and d.running():
print("操作完成,可以考虑关闭应用或断开连接。")
# d.app_stop("com.tencent.mm") # 停止微信
# uiautomator2.disconnect() # 断开所有连接,但通常不需要显式调用
if __name__ == '__main__':
# 确保手机通过USB连接并已授权USB调试
# 使用 adb devices 查看你的设备序列号,如果只有一个设备,可以填 None
# my_device_serial = "emulator-5554" # 替换为你的设备序列号,或 None
my_device_serial = None # 自动选择
target_contact_android = "文件传输助手" # 替换为你要发送的联系人名称
message_to_send_android = "你好,这是一条来自Python uiautomator2的自动消息!(Android)"
send_wechat_message_android(my_device_serial, target_contact_android, message_to_send_android)
使用 uiautomation2
的注意事项:
- 元素ID变化: Android 应用(包括微信)更新后,UI元素的
resourceId
极易发生变化。text
、description
(content-desc) 和className
相对稳定一些,但也不是绝对的。强烈建议使用weditor
仔细检查并选择最可靠的定位符组合。 - 多设备: 如果连接了多台设备,需要在
uiautomator2.connect("设备序列号")
中指定。 - 屏幕状态: 确保屏幕是点亮的,并且设备已解锁。代码中可以加入
d.screen_on()
和d.unlock()
(需要特定服务支持,如ATX)。 - 隐式等待与显式等待:
uiautomator2
的很多操作都有隐式等待 (如d(...).exists(timeout=...)
)。但对于UI加载、动画等,仍需使用time.sleep()
进行显式等待。 - 权限问题: 确保
atx-agent
在手机上有足够的权限执行操作。 - 网络状况: 自动化操作依赖于App的正常响应,网络不佳可能导致超时或失败。
三、选择哪个库?uiautomation
vs uiautomator2
-
uiautomation
:- 平台: 仅限 Windows。
- 对象: PC 版微信客户端。
- 优点: 对于 Windows 桌面应用自动化较为直接,不需要手机或模拟器。
- 缺点: 依赖 Windows UI Automation API,控件属性可能随微信PC版更新而频繁变动。需要PC保持开机且微信登录。
-
uiautomation2
:- 平台: Android (真机或模拟器)。
- 对象: Android 版微信 App。
- 优点: 功能强大,
weditor
工具非常好用,社区活跃。可以控制整个 Android 系统。 - 缺点: 需要配置 ADB 环境,手机/模拟器需要开启 USB 调试。元素ID也可能变化,但通常有更多属性可供选择。
选择建议:
- 如果你需要在 Windows PC 上自动化操作 PC版微信,选择
uiautomation
。 - 如果你需要在 Android 设备 (或模拟器) 上自动化操作 Android版微信,或者需要更广泛的移动端自动化能力,选择
uiautomation2
。
四、重要提示与最佳实践
-
UI稳定性是最大的挑战: 微信(或其他任何应用)的UI界面会随着版本更新而改变。这意味着你辛辛苦苦写好的定位元素的规则(如
Name
,AutomationId
,resourceId
)随时可能失效。- 应对策略:
- 尽量使用相对稳定的属性 (如
ClassName
结合层级关系,description
等)。 - 编写更具弹性的定位逻辑 (例如,如果一个ID找不到,尝试另一个备用ID)。
- 定期检查和更新你的脚本。
- 学会使用 Inspect.exe (Windows) 和 weditor (Android) 快速定位新版本的元素。
- 尽量使用相对稳定的属性 (如
- 应对策略:
-
适当延时
time.sleep()
: 自动化脚本执行速度远快于人的操作和应用的响应速度。在点击、输入、页面跳转等操作后,务必加入适当的延时,给应用足够的时间加载和响应,否则很容易因为元素未出现而报错。延时时间需要根据实际情况调整。 -
错误处理
try-except
: UI 自动化非常容易出错(元素找不到、应用崩溃等)。使用try-except
包裹关键操作,可以使你的脚本更健壮,至少在出错时能优雅地退出或记录日志,而不是直接崩溃。 -
清晰的日志: 在脚本的关键步骤打印日志 (例如 “正在搜索联系人XXX…”, “消息输入框已找到”, “发送成功/失败”),便于调试和追踪问题。
-
小步快跑,逐步调试: 不要一口气写完所有代码。每完成一小步功能(如打开微信、定位搜索框),就运行测试,确保无误后再继续下一步。
-
遵守平台规则,避免滥用:
- 不要用于发送垃圾信息或进行骚扰。
- 过于频繁的自动化操作可能会被微信安全机制检测到,导致账号功能受限甚至封号。 请谨慎使用,控制发送频率和内容。
- 本文仅作技术探讨和学习之用,滥用脚本产生的一切后果由使用者自行承担。
总结
通过 uiautomation
和 uiautomation2
这两个库,我们可以有效地实现PC端和Android端微信消息的自动发送。虽然UI元素定位是这类自动化脚本中最具挑战性的部分,但掌握了元素查看工具和基本的定位方法后,你就能应对大部分场景。
记住,自动化脚本的维护是一个持续的过程。随着微信版本的迭代,你可能需要定期调整你的脚本。希望本文能为你打开微信自动化的大门,探索更多有趣和实用的应用场景!
希望这篇博客文章对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言。