不积跬步,无以至千里;不积小流,无以成江海。

Dean's blog

  • Join Us on Facebook!
  • Follow Us on Twitter!
  • LinkedIn
  • Subcribe to Our RSS Feed

Python 模拟用户操作Windows应用程序

最近实现一个从某店通的桌面应用程序自动下载数据并入库的爬数据应用,在开发过程中,涉及到很多之前没有接触的功能点。由于原Windows 勾子了解得不多,基本的思路是通过模拟点击,实现自动下载,但由于模拟点击涉及到控件的位置,因些整体思路是:

1、自动启动应用程序;

2、调整窗口的位置和大小;

3、模拟用户点击操作;

4、操作完成后关闭应用程序

相关的关键点有:

1、启动和关闭应用程序

启动和关闭应用程序这里使用psutil库,psutil是一个跨平台的库,用于检索进程和系统利用率(CPU、内存、磁盘、网络、传感器)的信息。它的官方文档从这里打开:psutil官方文档

import psutil

class Process:
    def __init__(self, app = None, cwd = None):
        self.app = app
        self.cwd = cwd
        self.__process = None

    def run(self):
        '''尝试启动进程'''
        self.__process = psutil.Popen(self.app, cwd = self.cwd)
    
    def kill(self):
        '''中止进程'''
        try:
            self.__process.kill()
        except BaseException:
            pass

2、调整窗口位置和大小

调用窗口位置和大小主要是为了得到一致的坐标,因为有些应用在每次启动的时候,位置是有差异的,或者不同的分辨率、屏幕大小都可能会导致差异的产生。

要调整窗口位置和大小需要使用win32api,在这里使用了pywin32库,它是一个强大的Python的Windows 扩展库。由于它底层也是调用的win32api,那么可以基于win32api来查找所需要的Api接口,win32api清单可以见这里:Windows API函数大全(精心总结)

调整窗口位置和大小可以使用win32api  SetWindowPos,如下:

def setWindowPos(hwnd):
    '''设置窗口显示位置
          hwnd     窗口句柄
    '''
    win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, xOffset, yOffset, windowWidth, windowHeight, win32con.SWP_SHOWWINDOW)

其中:

       hwnd                                                   窗口句柄
       win32con.HWND_TOPMOST          置顶
       win32con.SWP_SHOWWINDOW    显示窗口
       xOffset、yOffset                               窗口的左上角坐标
       windowWidth、windowHeight       窗口大小

 执行后,窗口就会置顶显示。

但是这里存在一个问题,在使用psutil启动进程的时候,我得可以获得PID,但是SetWindowPos使用的却是句柄,在如何使用PID获取对应的主窗口句柄,网上有两种不同的办法:

方式1、使用GetWindowThreadProcessId函数,但网友测试发现存在获取不到窗口句柄的情况;

方式2、使用EnumWindows枚举所有的窗口,跟据窗口标题来判断,例如:

def getProcessMainWindowsHandle(title):
    '''得到进程的主窗口句柄'''
    hwnd_title = dict() 
    def get_all_hwnd(hwnd,mouse):
        if win32gui.IsWindow(hwnd) and win32gui.IsWindowEnabled(hwnd) and win32gui.IsWindowVisible(hwnd):
            hwnd_title.update({hwnd:win32gui.GetWindowText(hwnd)})

    win32gui.EnumWindows(get_all_hwnd, 0)

    for h,t in hwnd_title.items():
        if t is not "" and t.find(title) > -1:
            return h
    return None

3、模拟用户点击操作

应用启动后,接下来模拟鼠标点击和在需要搜索的位置输入内容,就可以了。模拟鼠标使用pywin32也是可以的,例如常见的鼠标操作这样:

#获取鼠标位置
win32api.GetCursorPos()

#鼠标左键按下
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)

#鼠标左键放开
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)

#鼠标右键按下
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)

#鼠标右键放开
 win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)

#设置鼠标位置
win32api.SetCursorPos((x, y))

#键盘输入事件
win32api.keybd_event(VK_CODE[word], 0, 0, 0)
win32api.keybd_event(VK_CODE[word], 0, win32con.KEYEVENTF_KEYUP, 0)

它这个功能强大,但是需要键入和使用的常量还是蛮多的,最后使用的是PyUserInput库,是一个使用python的跨平台的操作鼠标和键盘的模块,非常方便使用。支持的平台及依赖如下:

Linux - Xlib
Mac - Quartz, AppKit
Windows - pywin32, pyHook

使用它可以更方便实现模拟用户操作,例如:

from pymouse import PyMouse
from pykeyboard import PyKeyboard

m = PyMouse()
k = PyKeyboard()

position = m.position()         #获取当前坐标的位置
m.move(x,y)                     #鼠标移动到xy位置
m.click(x,y, 1|2)               #在xy位置点击,1左键(默认)2右键
k.type_string('text')           #在光标位置输入内容
k.tap_key(k.enter_key)          #按下回车键
k.tap_key(k.right_key)          #按下方向键右键

相比直接使用pyWin32,PyUserInput代码明显简洁很多。

而这里鼠标移动和点击都需要使用x,y坐标,在开发过程中,可以先将应用截图,再通过下面的HTML点击的所需要的位置,获取相对于图片左上角的坐标:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
	<title>获取图片坐标位置</title>
</head>
<body>
<img src="订单01.png" />
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
$("img").click(function (e) {
	alert('坐标:' + e.offsetX + "*" + e.offsetY)
});
</script>
</body>
</html>

至此主要的技术难点就都实现了。

相关链接

Windows程序设计(4):根据PID,获取句柄Handle:https://blog.csdn.net/qcyfred/article/details/78508546

Windows API函数大全(精心总结):https://blog.csdn.net/wang13342322203/article/details/8128037

不允许评论
粤ICP备17049187号-1