大家好,我是小小明。昨天我在《UI自动化工具轻松实现微信消息的自动收发和朋友圈爬取》一文中演示了UIAutomation的三个使用示例,链接:https://blog.csdn.net/as604049322/article/details/119899542
由于昨天对UIAutomation的API了解还不够全面,个别代码优化空间还较大。今天我们的目标是实现微信PC版的联系人信息列表的爬取,将分别使用PyWinAuto和uiautomation来实现,通过对比的大家将会有更好理解。
💻PyWinAuto实现
PyWinAuto官方文档地址:https://pywinauto./
PyWinAuto连接一个桌面程序的方式主要有两种:
- process: a process ID of the target
- handle: a window handle of the target
分别是进程的pid和窗口句柄,下面我我分别演示如何获取微信的进程id和窗口句柄。
根据进程名获取进程ID:
import psutil
def get_pid(proc_name):
for proc in psutil.process_iter(attrs=['pid', 'name']):
if proc.name() == proc_name:
return proc.pid
%time get_pid("WeChat.exe")
Wall time: 224 ms
7268
根据窗口标题和类名查找窗口句柄:
import win32gui
%time hwnd = win32gui.FindWindow("WeChatMainWndForPC", "微信")
hwnd
Wall time: 0 ns
264610
耗时几乎为零,比前一种方法快了100倍以上。
所以我使用窗口句柄来连接微信窗口:
import win32gui
from pywinauto.application import Application
hwnd = win32gui.FindWindow("WeChatMainWndForPC", "微信")
app = Application(backend='uia').connect(handle=hwnd)
app
<pywinauto.application.Application at 0x1f8dd84e588>
自动打开通讯录:
import pywinauto
win = app['微信']
txl_btn = win.child_window(title="通讯录", control_type="Button")
txl_btn.draw_outline()
cords = txl_btn.rectangle()
pywinauto.mouse.click(button='left', coords=(
(cords.right+cords.left)//2, (cords.top+cords.bottom)//2))
随便点开一个好友的信息详情后,通过inspect.exe
检查节点信息。
然后根据分析结果编写如下代码并运行:

可以看到各类信息都顺利的被提取,但耗时达到3.54秒,不见得比人工复制粘贴快,这也是pywinauto的缺点,太慢了。
下面我们进行批量爬取,原理大致是每读取一次信息面板按一次向下的方向键,发现当前读取的数据与前一条一致则认为已经爬到末尾。
由于pywinauto的爬取速度实在过于慢,所以我将好友列表人工拖到末尾后再运行以下代码:
import pandas as pd
win = app['微信']
contacts = win.child_window(title="联系人", control_type="List")
# 点击第二个可见元素
contacts.children()[1].click_input()
result = []
last = None
num = 0
while 1:
tag = win.Edit2
tmp = tag.parent().parent()
nickname = tag.get_value()
# 跳过两个官方号
if nickname in ["微信团队", "文件传输助手"]:
contacts.type_keys("{DOWN}")
continue
detail = tmp.children()[-1]
whats_up = ""
if hasattr(detail, 'get_value'):
whats_up = detail.get_value()
elif hasattr(detail, 'window_text') and detail.window_text() != "":
# 这种情况说明是企业微信,跳过
contacts.type_keys("{DOWN}")
continue
items = tmp.parent().parent().children()[2].children()
row = {"昵称": nickname, "个性签名": whats_up}
for item in items:
lines = item.children()
k = lines[0].window_text()
v = lines[1].window_text()
row[k.replace(" ", "")] = v
if row == last:
# 与上一条数据一致则说明已经爬取完毕
break
result.append(row)
num += 1
print("\r", num, row,
end=" ")
last = row
contacts.type_keys("{DOWN}")
df = pd.DataFrame(result)
df
最终结果:

可以看到最后一页的11个微信号的数据爬取耗时45秒。
💻uiautomation实现
下面我们再使用uiautomation来实现这个爬取。
获取微信窗口并单击通讯录按钮:
import uiautomation as auto
wechatWindow = auto.WindowControl(searchDepth=1, Name="微信", ClassName='WeChatMainWndForPC')
wechatWindow.SetActive()
txl_btn = wechatWindow.ButtonControl(Name='通讯录')
txl_btn.Click()
然后点开云朵君的好友信息后,测试一下好友信息提取:

(这个号,大家想加可以随便加)
wechatWindow.EditControl(searchDepth=10, foundIndex=2)
表示在10层节点内搜索第二个EditControl
类型的节点(第一个是搜索框,第二个就是好友昵称)。
GetParentControl()
和GetNextSiblingControl()
就是昨天没有用到的API,分别用于获取父节点和下一个兄弟节点,使用这两个API改写昨天的文章的代码,将使程序代码简单而且性能有所提升。
然后以与PyWinAuto相同的思路进行批量提取:
import pandas as pd
result = []
last = None
num = 0
contacts = wechatWindow.ListControl(Name='联系人')
contacts.GetChildren()[1].Click()
while 1:
tag = wechatWindow.EditControl(searchDepth=10, foundIndex=2)
tmp = tag.GetParentControl().GetParentControl()
nickname = tag.Name
# 跳过两个官方号
if nickname in ["微信团队", "文件传输助手"]:
contacts.SendKeys("{DOWN}")
continue
detail = tmp.GetChildren()[-1]
whats_up = detail.Name
if detail.ControlTypeName == "TextControl":
# 这种情况说明是企业微信,跳过
contacts.SendKeys("{DOWN}")
continue
items = tmp.GetParentControl().GetNextSiblingControl().GetNextSiblingControl().GetChildren()
row = {"昵称": nickname, "个性签名": whats_up}
for item in items:
lines = item.GetChildren()
k = lines[0].Name
v = lines[1].Name
row[k.replace(" ", "")] = v
if row == last:
# 与上一条数据一致则说明已经爬取完毕
break
result.append(row)
num += 1
print("\r", num, row, end=" ")
last = row
contacts.SendKeys("{DOWN}")
df = pd.DataFrame(result)
df
同样先测试最后一页的数据:

仅11秒就就爬取了,比PyWinAuto快了4倍。
于是我们就可以批量对全部微信好友的数据进行提取,最终我这边700多个好友的耗时是10分钟,虽然也比较慢,但是相对PyWinAuto来说耗时是完全可以接受的。
🏃代码对比

对于两者我尽量按照了完全相同的逻辑的编写。
win.Edit2
同样是获取第二个EditControl
类型的节点,
type_keys
则是PyWinAuto用于模拟按键的API,{DOWN}
表示向下的方向键。
PyWinAuto获取父子节点的api都是小写不带get的,uiautomation获取父子节点的api是首字母大写而且带Get前缀的。
对于PyWinAuto的这行代码:
items = tmp.parent().parent().children()[2].children()
与uiautomation的:
items = tmp.GetParentControl().GetNextSiblingControl().GetNextSiblingControl().GetChildren()
有较大区别。
这是因为我没有找到PyWinAuto获取兄弟节点的API,所以采用了先获取父节点再获取子节点的方法。
另外就是判断是否为企业微信的逻辑不一样,PyWinAuto还需要针对个性签名为空时进行异常处理,否则程序报错退出。具体的点还需大家测试体会。
其他地方逻辑都几乎一致。
🌀总结
本文同时演示了PyWinAuto
和uiautomation
读取微信好友列表信息,通过对比的方式可以更深入的理解两者的API的用法,作为作者的我已经在实践中有了很深的理解。文中仅仅只是代码并不能体现这些细节,具体的那些东西还需读者们,在分析和对比的过程中得到答案。只看本文代码,或许能解决当前这个需求,却是很难将本文涉及的技术应用于其他需求的。
童鞋们,快动手实践学起来吧⁉️学完你就将对任何实现了Automation Provider的桌面程序可见即可爬~