分享

❤️对比PyWinAuto和uiautomation实现微信联系人自动采集❤️

 小小明代码实体 2021-11-30

大家好,我是小小明。昨天我在《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检查节点信息。

然后根据分析结果编写如下代码并运行:

image-20210825223832196

可以看到各类信息都顺利的被提取,但耗时达到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

最终结果:

image-20210825203523043

可以看到最后一页的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()

然后点开云朵君的好友信息后,测试一下好友信息提取:

image-20210825204405969

(这个号,大家想加可以随便加)

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

同样先测试最后一页的数据:

image-20210825211019538

仅11秒就就爬取了,比PyWinAuto快了4倍。

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

🏃代码对比

image-20210825212656762

对于两者我尽量按照了完全相同的逻辑的编写。

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还需要针对个性签名为空时进行异常处理,否则程序报错退出。具体的点还需大家测试体会。

其他地方逻辑都几乎一致。

🌀总结

本文同时演示了PyWinAutouiautomation读取微信好友列表信息,通过对比的方式可以更深入的理解两者的API的用法,作为作者的我已经在实践中有了很深的理解。文中仅仅只是代码并不能体现这些细节,具体的那些东西还需读者们,在分析和对比的过程中得到答案。只看本文代码,或许能解决当前这个需求,却是很难将本文涉及的技术应用于其他需求的。

童鞋们,快动手实践学起来吧⁉️学完你就将对任何实现了Automation Provider的桌面程序可见即可爬~

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多