欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

从0开始的appium+Android+python自动抢红包世界生活

时间:2023-07-29
前言

关于 appium 是什么之类的不再赘述,有关的博文已经很多了,本文旨在提供截至 2022 年除夕可以在 Windows 11 上复现的操作方法
虽然这篇博文被看到的时候应该只能用在 2023 年的春节了

环境配置

安装 appium desktop

安装 appium-inspector

安装 JDK

安装 MuMu 模拟器
安装好后在 系统应用 -> 设置 -> 开发者选项 中打开 USB调试
同时查看 设置 -> 关于平板电脑 -> Android版本,我的是 6.0.1

借助 Android Studio 安装 SDK
首先下载+安装+运行 Android Studio ,进入到欢迎界面,再进入 SDK 管理


安装 6.0 是因为 MuMu 模拟器是 6.0.1

进行一堆环境变量的设置
Win+R 运行 control system -> 相关链接 -> 高级系统设置 -> 环境变量

下方环境变量区中新建

变量名:ANDROID_HOME
变量值:下图所示
变量名:JAVA_HOME
变量值:刚才 JDK 的安装目录
如:C:Program FilesJavajdk-17.0.2
变量名:CLASSPATH
变量值:.;%JAVA_HOME%lib;

变量名:prog_dir
变量值:%ANDROID_HOME%platform-tools

变量名:ANDROID_SWT
变量值:%ANDROID_HOME%toolslibx86_64

已有的系统变量中找到Path -> 编辑 -> 编辑文本 -> 在文末插入
%JAVA_HOME%bin;%ANDROID_HOME%;%ANDROID_HOME%/tools;%ANDROID_HOME%/platform-tools;

测试
在 cmd 中运行

C:Usersusername> java -versionC:Usersusername> javac -versionC:Usersusername> adb devices

结果类似下图即说明设置成功

安装 appium 的 Python 驱动
pip3 install appium-python-client

链接Android模拟器

如果在 cmd 下执行 adb devices 显示出有设备链接了那就万事大吉
但是 MuMu 应该是没办法被自动检测到的,如下位置 MuMu 告诉我们运行

adb connect 127.0.0.1:7555

appium 的准备工作

分别打开 Appium Server GUI 和 Appium Inspector,原本他们是在一起的,现在分成了两个程序

GUI:直接 Start ,此时 Edit Configuration 里面应该已经自动填上了刚才设置的JAVA_HOME 和 ANDROID_HOME

Inspector:

如果你下载最新 Appium Desktop 时仍有如下提示,请把 Insepector 中的 Reomote Path 改成 /wd/hub ,否则会报错(见 Append:Failed to create session)

编辑 Desired Capabilities,我们直接用 JSON 格式

{ "platformName": "Android", "deviceName": "MuMu", "platformVersion": "6.0.1", "appPackage": "com.tencent.mm", "appActivity": ".ui.LauncherUI", "newCommandTimeout": 6000, "noReset":true}

有关 Desired Capabilities 的官方文档
、newCommandTimeout: 一个 session 在未接收到任何命令多久后会自动关闭,默认 60s 太短了

Start Session

看到这个大大的地球我们就成功了,至此最艰难的步骤都已经完成了

补充:包名获取
这里任意 appPackage 和 appActivity 的获取可以用以下方式,仍以微信为例:
在 MuMu 上打开微信
在 cmd 中运行 adb shell 进入模拟器操作系统
运行 dumpsys window windows | grep mFocusedApp,对照一下就懂了,其中第一次运行时是在登录界面,第二次是在联系人界面,所以 appActivity 不同

Inspector 演示
点击我们想要了解的地方就可以看到 id (即 resource_id) 和相关信息了。注意它的界面不是自动同步的,必须手动点一下上面的刷新才会同步

一些例子:



接下来我们先放下这边的工作,先构建脚本框架

脚本编写

from appium import webdriverfrom selenium.webdriver.support.ui import WebDriverWaitclass RedEnvelope(): def __init__(self): self.desired_caps = { "platformName": "Android", "deviceName": "MuMu", "platformVersion": "6.0.1", "appPackage": "com.tencent.mm", "appActivity": ".ui.LauncherUI", "newCommandTimeout": 6000, "noReset": True } self.driver = webdriver.Remote( 'http://127.0.0.1:4723/wd/hub', desired_capabilities=self.desired_caps) # 这里4723要改成 Appium Server GUI 启动时显示的端口 # 同时后面的 /wd/hub 也要根据 appium desktop 中的提示修改 def login(self): print('正在登陆中——————') def main(self): self.login()R = RedEnvelope()R.main()

以上代码和刚刚按的 Start Session 是一样的,观察模拟器会发现微信被打开了

使用 Insepctor 来分析 UI

Python 代码运行起来后,Inspector -> Attach to Session -> 选择 Session ID,对应的 Session ID 可以通过 driver 创建好后 driver.session_id 查看

print('session_id:', self.driver.session_id)

Ui Automator Viewer

Appium Inspector 其实是一个不太好的选择,一是慢,二是检测能力似乎不强,三是看不到元素层次,很多时候要点的元素被遮挡了
这里有两个现成的选择

sdktoolsbin 下的 uiautomatorviewer.bat ,但是 Java 版本必须是 Java 8sdktools 下的 monitor.bat

最初的环境配置中其实已经加入了与这两个软件相关的环境变量配置,以下展示 Ui Automator Viewer 的界面

这里有一个BUG,如果用 MuMu 默认的平板分辨率,打开微信的时候在 uiautomatorviewer 显示的界面也会横过来,我的解决办法是直接把 MuMu 设成类似手机的分别率

Ui Automator Viewer 增强
用 lazyuiautomatorviewer.jar 替代 Sdktoolslib 下的对应文件,这个 Viewer 可以显示元素的 xpath,注意换的时候名字记得改成和原来的一样

Appium 光速入门

from selenium.webdriver.common.by import Byfrom appium.webdriver.common.appiumby import AppiumByfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.common.exceptions import TimeoutException, NoSuchElementException'''查找元素实例'''element = driver.find_element(By.ID, 'com.tencent.mm:id/d5v')# 会返回第一个找到的,如果没找到会抛出 NoSuchElementException 错误# By.ID 是应该优先考虑的方法element = driver.find_elements(By.CLASS_NAME, 'android.widget.LinearLayout')# 返回所有符合元素的列表,如果没有符合的则返回空列表driver.find_element(By.XPATH,"//android.support.v7.widget.RecyclerView[@resource-id='com.tencent.mm:id/a5u']/android.widget.LinearLayout[1]")# By.XPATH 利用路径表达式查找元素driver.find_elements(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("'+text+'")')# 利用元素的 text 属性查找元素'''元素方法'''element.click()# 点击element.send_keys()# 输入'''元素等待'''def func(driver):return driver.find_elements(By.CLASS_NAME, 'android.widget.LinearLayout')WebDriverWait(driver = mydriver, timeout = 1, poll_frequency = 0.5).until(func)# 会在 timeout 时间内尝试调用 func(driver), 间隔 poll_frequency# 当 func 返回真时提前退出# 超时后抛出 TimeoutException 错误

完整代码

2022.01.31:还没写呢,今年的除夕都已经过了,明年再写吧

2022.02.01:还是给它写了,加了个自动登录的功能,因为模拟器和手机上切换登录时总是要输密码,于是就自动化了

import timefrom typing import Listimport keyboardfrom appium import webdriverfrom appium.webdriver.common.appiumby import AppiumByfrom appium.webdriver.common.touch_action import TouchActionfrom appium.webdriver.webdriver import WebDriverfrom appium.webdriver.webelement import WebElementfrom selenium.common.exceptions import (NoSuchElementException, StaleElementReferenceException, TimeoutException)from selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitdef is_element_exist(driver, element, method='by_id', timeout=0, frequency=0.2): """ 监听 element_text 是否在 timeout 时间内存在 , 结果以列表形式返回 element_text: list 或 str 格式,当是 list 格式时表示是否存在其中任意一个 可选method有: 'by_text', 'by_id' """ def exist(driver: WebDriver) -> List[WebElement]: if method == 'by_text': def find(text): return driver.find_elements( AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("'+text+'")') elif method == 'by_id': def find(id): return driver.find_elements(By.ID, id) else: raise ValueError if isinstance(element, str): return find(element) else: for ele in element: if find(ele): return find(ele) return [] if timeout: try: WebDriverWait(driver, timeout, frequency).until(exist) except TimeoutException: return [] else: return exist(driver) else: return exist(driver)class RedEnvelope(): def __init__(self): self.desired_caps = { "platformName": "Android", "deviceName": "pzyhfagmr8nfd6hm", "platformVersion": "6.0.1", "appPackage": "com.tencent.mm", "appActivity": ".ui.LauncherUI", "newCommandTimeout": 6000, "noReset": True } self.driver = webdriver.Remote( 'http://127.0.0.1:4723/wd/hub', desired_capabilities=self.desired_caps) actions = TouchAction(self.driver) self.password = '不告诉你' self.grab_flag = True def login(self): # d5z:紧急冻结 cns:通讯录 if not is_element_exist(self.driver, ['com.tencent.mm:id/d5z', 'com.tencent.mm:id/cns'], 'by_id', 10): print('微信启动失败') if is_element_exist(self.driver, 'com.tencent.mm:id/d5z'): print('检测到微信被登出,正在重新登录') # d5v:切换验证方式 if is_element_exist(self.driver, 'com.tencent.mm:id/d5v'): self.driver.find_element( By.ID, 'com.tencent.mm:id/d5v').click() # f40:用xx登录[重复] if is_element_exist(self.driver, 'com.tencent.mm:id/f40', timeout=1): # 用密码登录 self.driver.find_element( By.XPATH, "//android.support.v7.widget.RecyclerView[@resource-id='com.tencent.mm:id/a5u']/android.widget.LinearLayout[1]").click() self.login_with_password() else: print('等待用密码登录入口超时') else: print('登录微信成功 (由保持登录记录)') def login_with_password(self): print('正在用密码登录') if is_element_exist(self.driver, 'com.tencent.mm:id/bhn', timeout=1): self.driver.find_element( By.ID, 'com.tencent.mm:id/bhn').send_keys(self.password) self.driver.find_element(By.ID, 'com.tencent.mm:id/d5n').click() else: print('等待密码输入框超时') if is_element_exist(self.driver, ['com.tencent.mm:id/cns'], 'by_id', 10): print('登录微信成功 (由键入密码)') def main(self): print('正在启动微信') self.login() def add_hotkey_stop_grab(self): def stop_grab(): self.grab_flag = False keyboard.add_hotkey('ctrl+alt+r', stop_grab) print('激活了热键 ctrl+alt+r 以中止抢红包') def grab(self): # 如果要实现稳定多会话抢红包, 得再删除没有抢到的红包(不会显示消息:你已领取xx的红包) self.grab_from_message() return '''多会话''' # b4r 微信->每个会话窗口 pers = is_element_exist(self.driver, 'com.tencent.mm:id/b4r') if pers: for per in pers: message = per.find_element(By.ID, 'com.tencent.mm:id/cyv').text if '[微信红包]' in message: per.click() self.grab_from_message() break else: self.grab_from_message() def grab_from_message(self): try: messages = is_element_exist( self.driver, 'com.tencent.mm:id/al7', timeout=1) for message in messages[::-1]: if is_element_exist(message, 'com.tencent.mm:id/ra'): # print('哇,发现一个红包!') env_app = is_element_exist( message, 'com.tencent.mm:id/r0') if env_app: pass '''气氛组''' # if env_app[0].text == '已领取': # print('哦,是领过的') # elif env_app[0].text == '已被领完': # print('啊,是没抢到的') # else: # print('咦,这是什么') else: print('抢它!') message.click() # den:开 bottom = is_element_exist( self.driver, 'com.tencent.mm:id/den', timeout=1, frequency=0.01) if bottom: bottom[0].click() # d_h 抢到xx元 dh = is_element_exist( self.driver, 'com.tencent.mm:id/d_h', timeout=2, frequency=0.01) if dh: print('抢到 '+dh[0].text+' 元') else: print('没抢到') self.driver.find_element( By.ID, 'com.tencent.mm:id/dm').click() else: print('没抢到') # dem:红包下的x self.driver.find_element( By.ID, 'com.tencent.mm:id/dem').click() '''多会话''' # self.actions.long_press(message) # self.actions.perform() # self.driver.find_element( # By.XPATH, "//android.widget.LinearLayout[@resource-id='com.tencent.mm:id/hyf']/android.widget.LinearLayout[1]").click() break except StaleElementReferenceException: return '''多会话''' # rr:又上角的返回 # rr = is_element_exist( # self.driver, 'com.tencent.mm:id/rr', timeout=1)[0] # rr.click() def grab_start(self, frequcency=0): print('开始抢红包') self.add_hotkey_stop_grab() self.grab_flag = True while self.grab_flag: self.grab() time.sleep(frequcency) print('抢红包中止')if __name__ == '__main__': R = RedEnvelope() R.main()# 建议用交互式# R.grab_start()

Append:Failed to create session

如果运行右下角 Start Session 后遇到一个报错

这里提到了原因:Default remote path should be “/wd/hub”
appium2 把 remote path 的默认设为/,此时最新版本的 Inspector 配合 appium2 也做了同步的更改,然而appium desktop 是面向新手的 GUI 版本,其内核迟迟没有更新到新的 appium2 ,解决这个问题简单的办法是下载旧版本的 Inspector,其中 2021.8.5 版本提到了这个修改

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。