Python控制浏览器和桌面应用

1,022 阅读12分钟

Clicknium是一个可以用来控制桌面应用和浏览器的Python库。 在Clicknium使用指南1 中介绍了Clicknium的基本概念和使用方法。本章将更深入的介绍Clicknium的高级功能。

1. 获取相似元素(Similar elements)

我们经常需要处理网页或应用中大量相似的元素,例如List和Grid中的UI元素。 依次抓取这些元素会产生大量的locator,显然不是一个好方法。 有用户甚至提问,将同一类locator放在了一个文件夹下,怎么能遍历locator文件夹。 当然,采用之前提到了参数化locator也是一种做法。但是,一方面你需要分析是用哪个属性和什么值来遍历UI元素,另一方面,参数的范围很可能不得而知,尤其随着应用越来越复杂,越来越多采用动态加载的方式导致要抓取的UI元素总量无法预测。 通常我们会使用 BeautifulSoup这类的库来解析网站的HTML结构,由于网站的样式越来越复杂,抓取的内容并不会总是在同一个结构中, 每个Item可能被多层div或者其他元素包裹,所以在解析时,我们需要分析HTML的结构并构建一个算法找所有元素的最小公共节点,并且遍历该结构。 这一过程确实比较花时间, 同时也是一个能被算法解决的问题。

Clicknium Recorder中,包含了抓取相似元素的功能。 用户通过抓取两个相似(同一层级)的元素, Clicknium内置算法自动解析HTML结构,并计算最小公共节点和层级信息 ,以实现抓取相似元素。 如果计算出来的元素并不是用户想要的,可进行第三次抓取,算法将通过三个UI元素的信息,重新计算抓取路径。

1.1 使用方法:

以Opensea中Okay Bear图片为例,这是一个典型的Grid结构:

a. 点击VS Code 插件中的Capture 按钮,启动录制器:

b. 点击录制器中的Similar elements:

c. Ctrl +鼠标单击抓取第一和第二个元素:

抓取到相似元素对应的locator后, 调用find_elements返回一个UI element List(find_element返回单个UI element。

Sample code:

使用get_property获取图片地址后,通过request下载图片,并通过send_hotkey触发PgDn下滑加载图片。

import requests
from time import sleep
from clicknium import clicknium as cc, locator
def main():
    cc.chrome.open("https://opensea.io/collection/okay-bears")
    nftDic = {}

    while True:
        newDic = {}
        finish = True
        sleep(4)
        nfts = cc.find_elements(locator.sample.opensea.img_okay_bear)
        for nft in nfts:
            src = nft.get_property("src")
            name = nft.get_property("alt")
            if name not in nftDic:
                newDic[name]=src
                finish = False

        for key,value in newDic.items():
            r = requests.get(value)
            print("Downloading:"+ key)
            imagePath = ".\image\" + key + ".jpg"
            with open(imagePath,'wb') as f:
                f.write(r.content)

        nftDic = nftDic | newDic
        if finish:
            break
        else:
            print("next page")
            cc.send_hotkey("{PGDN}")

if __name__ == "__main__":
    main()

2. 获取结构化数据

在自动化场景中, 获取结构化数据,尤其是表格数据也有很大的需求。获取相似元素只适合抓取一列数据。当然可以一列一列抓取一个表格,当这种方式会存在不同列之间的对齐问题,存在出错的可能同时操作也非常麻烦。

结构化数据有两个常见的场景:

  • 表格
  • 非表格类结构化数据

表格比较好理解, 什么是非表格类结构化数据呢?如上面提到了OpenSea的页面中, 如果同时需要抓取NFT图片对应的价格,编号,从而构成一个表格就可以认为非表格类结构化数据。相比获取相似元素,结构化数据增加了不同列之前的匹配算法和一系列方便表格操作的功能。

2.1 抓取表格数据:

  1. 在VS Code中启动录制器
  2. 在录制器中选择 data scraper

Coingecko举例,当需要抓取下面的表格

  1. 使用Ctrl+鼠标左键单击表格第一行第一列数据

Clicknium会自动探测抓取对象为表格,并提示是否抓取全表信息:

点击Yes,即可看到数据预览。在预览页面,支持修改列名,改变列的顺序, 删除列等操作。也可以选择No,然后一列一列选择需要的数据。

2.2 抓取非表格类结构化数据:

以最常见的京东作为例:京东手机页面

在这个例子中, 我们需要抓取商品名,价格和图片。操作的方式与获取相似元素类似, 两个元素决定一列:

a. 抓取第一列,第一个元素:第一个商品名。

b. 抓取第一列,第二个元素:第二个商品名。

c. 第一列抓取成功,点击添加列,开始抓取第二列元素。

d. 抓取第二列,第一个元素: 第一个商品价格。

。。。。。。

需要注意的是不同列抓取的元素的相对位置需要是一致的,即都是前两个元素。不能出现第一列抓取第一第二行,第二列抓取第三第四行,这样会导致匹配出现问题。

抓取结束后, Clicknium会将数据整理为表格:

在预览页面同样可以修改列名,列顺序和选择不同的属性作为列的值:

Sample Code:

对于结构化数据, Clicknium提供了scrape_data方法,返回Json格式的文本数据, 不仅如此该函数支持传入翻页按钮的 locator 实现自动翻页,翻页支持设置控件和模拟鼠标等方式,等待页面加载,抓取数据条数控制和超时。

from clicknium import clicknium as cc, locator
import pandas as pd

row = cc.scrape_data(locator.jd.phone)
df = pd.json_normalize(row)
print(df.head(10))

3. 自动化技术

在默认情况下,Clicknium会自动选择合适的自动化技术。 但在自动化桌面端应用时, 有时候还是需要我们来选择不同的自动化技术, 同时不同自动化技术也以为可能会产生不同的locator attributes。

Clicknium 包含的自动化技术:

需要注意的是, 不同自动化技术录制的Locator有着不同的Locator attributes和 Element properties。对于Element properties,可以查上面对应技术的文档中,通过get_property方法获得property的值。

下面的表格对Clicknium支持的自动化技术做了概括和总结

自动化技术场景简介
浏览器自动化Chrome/Edge/IE/Firfox 等浏览器。Chrome DevTools Protocol/ 利用Clicknium浏览器插件,采用注入JavaScript脚本对Web元素进行控制。
UIAWindows 桌面应用基于Microsoft UI Automation,相比IA有更丰富的方法和属性信息, 针对WPF特别优化。
IAWindows 桌面应用基于Microsoft Active Accessibility(MSAA), 利用COM 接口将界面的部分属性和API暴露出来。对于开发年代比较早的软件支持较好。
图像识别其他自动化识别不准确时采用基于Open CV,也是导致Clicknium Python SDK比较大的原因
JAVA 自动化自动化JAVA应用以Java Access Bridge为基础,并对AWT组件等进行加强,需要依赖Clicknium Java 插件。
SAP自动化SAP WinGUI API基于 SAP GUI Scripting。

在录制器中,可以修改默认选项,选择自己需要的自动化技术:

4. 打包Clicknium项目为可执行文件

自动化脚本的用户,不一定就是Python程序员本人。 当自动化脚本需要交给不同用户时,会需要各种各样的问题。 有可能对方没有Python环境,缺乏某个Python库,或者存在版本冲突,甚至对方是业务人员不是程序员。 将脚本打包成人人可运行的exe可执行文件能很好的解决上述问题。

打包方式:

  • Clicknium Project
  • PyInstaller

4.1 Clicknium Project打包

Clicknium Package是Clicknium内置的项目管理方式,旨在保证自动化脚本在开发,调试,执行和分发过程中保证相同的运行结果。

a. 创建Clicknium Project

在VS Code中, 按下Ctrl+Shift+P调出Command Palette, 选择Clicknium: Create Project,然后指定一个路径用来保存项目。 在项目创建的过程中, 会同时为项目创建一个虚拟环境。 可以在VS Code的右下角看到Python解释器旁显示的环境信息。

区别于普通的Python项目,这个Project中会自动创建几个文件:

  • app.py: Python源文件
  • logo.icon:打包后可执行文件的Icon
  • clicknium.yaml:Clicknium Project的配置文件

clicknium.yaml,其中包含了项目的基本信息,包括入口和依赖,日志路径等。logo.icon 可以替换为你想要的icon文件。

b. 运行和调试Clicknium Project

同样,在VS Code中, 按下Ctrl+Shift+P调出Command Palette, 选择Clicknium: Run Project。Clicknium就会更具clicknium.yaml中的配置运行项目。 F5直接运行文件时,是不会查看yaml中的配置进行部署和执行的。

c. Clicknium Project打包

在VS Code中, 按下Ctrl+Shift+P调出Command Palette, 选择Clicknium: Package Project, 然后选择一个路径存放可执行文件,然后选择console,如果脚本中包含图形界面请选择GUI,否则图形界面可能无法正常显示。 Clicknium会根据clicknium.yaml中的信息进行打包。

下面是一个clicknium.yaml文件的示例:

startUp: app
ignoreFiles: .gitignore
log:
  folder: 
  type: File
type: project
requirements:
  python: 3.9.13
  pip: 
  packages:
  - package: clicknium
    version: 
  - package: pandas
    version: 1.5.3
  locators: []

在上面的例子中:

app:是Python程序的entrypoint文件

log: 默认的log path是C:\Users{currentUser}\AppData\Local\Clicknium\Log。可以修改到其他路径,比如当前路径为 .\

python: 打包进可执行文件的Python解释器版本

packages: 需要打包进可执行文件的依赖库。Clicknium会自动扫描脚本中使用到的Python库,并自动添加对应的库名和版本号。如果自动添加功能有问题, 可以收到修改这个部分。

4.2 使用Pyinstaller打包

除了内置的打包方式, 在Python开发中, 更常见的做法是使用Pyinstaller: PyInstaller Manual进行打包。

为保证项目的干净, 仍然建议使用Python虚拟环境进行开发。 通常,可以打开项目文件中的.vscode文件夹中的setting.json文件找到项目使用的Python虚拟环境:

a. 进入虚拟环境目录,使用pip install pyinstaller安装pyinstaller。可用pip list查看pyinstaller是已包含和具体的版本信息。

b. 编辑publish.spec

配置说明:

  • 项目地址:{$"project app.py full path"},填写project app.py全路径,如:'c:\Users\{Username}\Desktop\Sample\app.py'
  • Clicknium SDK 路径:($'clicknium .lib automation full path', 'clicknium\.lib\automation'),填写Python虚拟环境site-packages文件夹clicknium文件夹下.lib\automation全路径,譬如:binaries=[('C:\Users\{Username}\AppData\Local\Clicknium\Envs\Sample_17aea73e4ca65262\.virtualEnv\Lib\site-packages\clicknium\.lib\automation', 'clicknium\.lib\automation')]
  • 项目Locator地址:datas=[($'project .locator full path', '.locator')],填写project .locator文件夹全路径:譬如: datas=[('c:\Users\YanZhiwei\Desktop\Sample\.locator', '.locator')]
  • 可执行文件名:name=$'project name',填写打包生成exe名称,可自行填写

下面是一个例子:

# -*- mode: python ; coding: utf-8 -*-


block_cipher = None


a = Analysis(
    ['c:\Users\KayYOLO\Desktop\Sample\app.py'],
    pathex=[],
    binaries=[('C:\Users\KayYOLO\AppData\Local\Clicknium\Envs\Sample_17aea73e4ca65262\.virtualEnv\Lib\site-packages\clicknium\.lib\automation', 'clicknium\.lib\automation')],
    datas=[('c:\Users\KayYOLO\Desktop\Sample\.locator', '.locator')],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='Sample',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None
)

5. 常见的问题:

5.1 如何启动Windows应用程序

可以利用Python的subprocess启动桌面应用程序

import subprocess

process_notpad = subprocess.Popen("notepad")
process_feishu = subprocess.Popen("C:\Users\kay\AppData\Local\Feishu\Feishu.exe")  

5.2 如果设置定时运行

a. 在任务栏搜索任务计划程序

b. 新建基本任务或新建任务

c. 依次填入:

  • 任务名称
  • 触发器:选择触发条件,每日、周或月,启动时,登录时等
  • 操作: 选择启动程序,程序或脚本: 填写python.exe的地址。添加参数:自动化脚本的地址。

5.3 对同一网站,如果开多个浏览器登录不同账号

在浏览器自动化插件模式下,采用的是attach的方式,这种情况推荐使用Chrome和Edge浏览器调用CDP接口,将tab对象绑定对应的浏览器tab。 要使web浏览器实例之间不共享cookie并能在相同网站登录不同的账号,可以采用不同的User profile。

根据open方法:

def open(
        self,
        url: str,
        is_maximize: bool = True,
        is_wait_complete: bool = True,
        userdata_folder_mode: Literal["automatic", "default", "custom"] = WebUserDataMode.Automatic,
        userdata_folder_path: str = "",
        timeout: int = 30
    ) -> BrowserTab 

将userdata_folder_mode设置为'custom', userdata_folder_path传入不同的文件夹路径,作为不同user profile存储路径。不同浏览器tab,使用不同的路径,即可实现多账户登录。

5.4 为什么有时候脚本会卡主很长时间,如何查看日志

大部分脚本卡住的情况是因为无法找到locator对应的UI元素造成的。Clicknium对locator的操作有默认30秒的timeout时间。 可以查看API文档,方法的timeout时间。 如果出现卡住的情况,先利用Validation确认locator是否能识别到UI元素。

日志的默认路径为:C:\Users{currentUser}\AppData\Local\Clicknium\Log

5.5 为什么一段时间后,locator失效了

导致locator失效的原因有很多,动态加载,版本更新,上下文变化都有可能。 首先可以在locator页面使用Recapture&Compare功能进行重新录制并对比新旧locator的区别。 不同的属性会高亮。

对比不同属性,修改属性的值以匹配新UI。为了使locator更加稳定,推荐使用通配符,正则表达式,参数化locator等功能提高locator的鲁班性。

5.6 为什么鼠标点击,键入文字没有效果

可以查看Clicknium使用指南1。 切换系统调用方式和模拟鼠标方式。 某些情况录制按钮时,可能会录制到按钮内的logo或者外围的边框,对于系统调用来说,只有按钮才有点击属性。部分输入框也存在click或者focus状态下才能激活的情况,可选择输入前点击或者focus参数。 由于系统调用触发后并没有返回值,所以Clicknium无法抛出错误。 这种时候,建议采用模拟鼠标的方式点击按钮。

上一篇: Clicknium使用指南1