05控件常用功能

AffettoIris 2023-11-30 2,766 11/30

 

导包

需求 - 手机安装qrcode模块

我可以理解为手机上安装的app其实是一个py解释器,然后通过airscript插件把pycharm上的代码同步到手机上运行。手机可以使用三方库,只不过是要编译和安装进手机。

# __init__.py 文件
import qrcode
img = qrcode.make('文本')
img.save('./temp.jpg')  #保存二维码

如上代码不能直接在手机上运行,报错“ModuleNotFoundError: No module named 'qrcode'”。

build.as - AirscriptApp的pip配置文件

查看官方文档 - 语言 - Python包拓展栏,开发者可通过pip配置,将需要的第三方python包编译进Airscript app中。由于Android 7.0开始对动态引入 动态链接库做了限制,因此我们只能把需要的动态链接库内置进AirscriptApp中,这需要重新打包AirScript app。

打开项目下的build.as,json格式,默认长这样:

{
    "name": "AirScriptProject3",
    "pip": {
        "options": ["--timeout", "1000"],
        "install": [
            "opencv-contrib-python==4.1.2.30",
            "requests", 
            "pymysql",
            "numpy",
            "websocket-client"
        ]
    }
}

比如我们想要拓展 qrcodepython 包,我们可以这样写

{
    "name": "AirScriptProject3",
    "pip": {
        "options": ["--timeout", "1000"],
        "install": [
            "opencv-contrib-python==4.1.2.30",
            "requests", 
            "pymysql",
            "numpy",
            "websocket-client",
            "qrcode"
        ]
    }
}

比如我们要加入 额外的包索引地址 ‘https://example.com/private/repository’,我们可以这样配置

{
    "name": "__name_zwf__",
    "pip": {
        "options": [ "--timeout", "1000",
                     "--extra-index-url","https://example.com/private/repository"
                   ],
        "install": [
            "opencv-contrib-python==4.1.2.30",
            "requests",
            "pymysql",
            "numpy",
            "qrcode"
        ]
    }
}

带着新库重新打包AirScript app

我们需要新的AirScript app,这个app有新库qrcode,但是然后才能被build.as引用到项目中。

因为AirScript 不开源,app源码在官网,所以重新打包app,只能用官网的在线打包工具。不过放心,还算是大公司,不会轻易倒闭的。

对个人来说,AirScript 可以说是免费的了,个人能用到的功能没有要vip的。

  • 登录 开发者后台

  • 点击Pip 应用管理-> Pip拓展

  • 点击右上角 新增

  • 编辑新增

  • 提交后,等待编译完成,即可下载安装使用

这会有个问题,如果官网倒闭了,就用不了新库了。

解决办法:

  • 对app进行逆向得到项目源代码。

  • 更换同行框架,AirScript app理论上就是个py解释器+方法库

  • 把新库的功能转移到线上。例如我要用qrcode库生成二维码,假设官网倒闭了,现在手机AirScript app没有qrcode库,我们把该生成二维码功能放在服务器上,服务器提供api,即可手机调用qrcode库。

测试

我现在重新安装了具有qrcode库的AirScript app,运行下述代码试试:

# __init__.py 文件
import qrcode
img = qrcode.make('文本')
img.save('./temp.jpg')  #保存二维码

这次报错不再是没有找到模块了,报错文件夹没有写权限,无法写入temp.jpg。

他人已经打包好的AirScript app

AirScript 有千个用户,例如我需要带qrcode库的AirScript app,其实已经有开发者打包好了,我们来到定制版AirScript app市场,捡现成的即可:(app从打包、存储到下载都是官网提供服务,不会夹带个人开发者的恶意指令。)

05控件常用功能

05控件常用功能

文字识别

没啥好说的,玩几下就会了。

05控件常用功能

据群里说,AirScript自带的文字识别功能不是很快,如果追求效率的话,得使用第三方api。

多点比色

例如下图,在绿色界面选两个点,生成表达式CompareColors('1041,312,#1BC88A|1059,327,#1BC48F').compare()

05控件常用功能

比色:对比多个坐标点的颜色,是否与当前屏幕想符。比色要比找色快很多

上述代码意思是,比较当前屏幕的点(1041,312)是否为颜色#1BC88A,偏色#050505、比较当前屏幕的点(1059,327)是否为颜色#1BC48F,偏色#050505。其实偏色函数取了默认值#050505,所以没显示偏色函数。

变量和节点的区别

python代码里的变量和手机里的节点(控件)还是不一样的,

follow = Selector().type("Button").visible(True).find()  # 关注按钮
head = Selector().type("Button").visible(True).brother(0.1).find() # 头像框 
head = follow.brother(0.1)  # 这是可以的
Selector().type("Button").visible(True).brother(0.1).click().find()  # 这是可以的,是在手机上操作节点
head.click()  # 不可以,head已经是一个变量了,会报错没有这个click()方法
head.find()  # 不可以
head.click().find()  # 不可以

节点的范围

a = Selector().type("LinearLayout").id("com.ss.android.ugc.aweme:id/osw").find()
print(a.rect)  # Rect(0, 926 - 1600, 1027)  # 分别是 左 上 右 下,不用记
print(a.rect.left)  # 0
print(a.rect.top)  # 926
print(a.rect.right)  # 1600 
print(a.rect.bottom) # 1027

怎么知道a.rect还有left等属性的?:

有很多方法/属性在官方文档都没有说,而且这些方法往往是java都类的成员函数/成员变量。

例如result = FindColors('55,998,#FE4DAA|65,1006,#FF43A5').rect(a, b, c, d).find_all()

type(result)是<class 'java.jarray('Landroid/graphics/Point;')'>,

print(result)是jarray('Landroid/graphics/Point;')([<android.graphics.Point 'Point(55, 998)'>]),

看起来我们无法直接获取想要的数值。

05控件常用功能

在pycharm里我们只需要打出变量.即可自动显示变量可用的方法/属性,如果是count(self,__value),说明需要一个参数。如果是clear(self,),说明不需要参数。这些安卓的java类的方法和属性,除了靠望文生义,还可以来谷歌安卓开发者官方文档来查:(不过有点玩不来,文档都是英文书写的)

05控件常用功能

如果结果是数组例如jarray,尽管不是标准python数组,但仍和标准python数组有些通用的属性/方法:

  • 上述例子中,result是一个jarray(java数组),但是我们可以直接获取数组长度(元素个数):print(len(result))

  • print(result[0]) 获取数组第一个元素。如果是空数组会报错“超出数组索引”。

Find()和Find_all()区别

a = Selector().type("LinearLayout").find()
# a有5个孩子控件
b = Selector().type("LinearLayout").child().find()  # 只获取第一个孩子控件,或曰,只保留前面筛选结果的第一个结果
c = Selector().type("LinearLayout").child().find_all()  # 获取所有孩子控件

Json的嵌套和导入Python代码并解析

有时候需要在json表达式里嵌套json来表达出属性值是个数组(有多个元素)的意思。

推荐这种写法,好记:

{
"place" : [
{"firstName":"John", "lastName":"Doe"},
{"firstName":"Anna", "lastName":"Smith"},
{"firstName":"Peter", "lastName":"Jones"}
]
}

# 上述代码把json外围某个键值对的值设置为数组,每个数组元素是一个json对象

import json

with open('./config.json', 'r', encoding='utf-8') as file: # 不加 encoding='utf-8' 遇到中文,json库会因特殊字符而报错
string = json.load(file)
string = json.load(string) # 此时string是字典
place = string['place'] # place是数组,所以从0、1、2当索引
print(place[0]) # {'firstName': 'John', 'lastName': 'Doe'}
print(place[0]['firstName']) # John

如果你有自定义索引的需求,使用下面的写法,但是难记:

{
"place":{
"person1":{"firstName":"John", "lastName":"Doe"},
"person2":{"firstName":"Anna", "lastName":"Smith"},
"person3":{"firstName":"Peter", "lastName":"Jones"}
}
}

# 上述代码把json外围某个键值对的值设置为字典/对象,每个元素又是一个字典,该字典的值是个json对象

string = 上述代码;
string = json.load(string) # 此时string是字典,相当于js里的对象
place = string['place'] # place是字典,person1、person2就是键
print(place['person1']) # {'firstName': 'John', 'lastName': 'Doe'}

其次后端在json解析、载入文件时尽量指定编码为utf-8,不然如果遇到中文,会因为特殊字符而报错,例如python的

with open('./config.json', 'r', encoding='utf-8') as file:
config = json.load(file)

Python 打印信息同时输出到控制台与日志文件

import logging

logging.basicConfig(filename='log_examp.log',level=logging.DEBUG) # 不指定路径就在当前目录下创建文件log_examp.log # 设置日志级别
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

上述信息会将信息追加到log_examp.log内:

05控件常用功能

反正我试的时候还不能同时显示在控制台上,尽管网上说是可以的。

解决:

import logging

def echo(msg):
logging.info(msg)
print(msg)

logging.basicConfig(filename='log_examp.log',level=logging.DEBUG)
echo(123)

Python获取文本输入框的值

官方文档给的方法是

# 导包
from airscript.ui.dialog import promat

# 案例: 弹出一个文本输入框
def tunner(k,v):
if(k=="__promat"):
if v == "cancle":
print('点击了取消')
else:
print('点击了确认:输入值为:',v)

dialog = promat('您确定要学习Airscript吗?').show(tunner)


print(dialog) # None。就算不是None也是个弹窗控件对象
print(tunner) # <function tunner1 at 0xc3a58778>

我们好像无法获取到输入值,也就是tunner(k,v)的v。解决思路:使用global关键字把函数内的值带出到函数外

times = "等待取值"

def tunner(k,v):
if(k=="__promat"):
if v == "cancle":
print('点击了取消')
else:
print('点击了确认:输入值为:',v)
global times # 改动之处
times = v

dialog = promat('您确定要学习Airscript吗?').show(tunner)
time.sleep(3) # AirScript目前没看到有阻塞代码执行的消息弹窗。如果不休眠会,等待用户输入,就直接执行下行了,times还是“等待取值”。
print(times) # 这下获取到了输入值

获取Airscript文件夹下的文件路径

正常安装的airscript app,安装目录airscript文件夹在打开"文件管理器"第一眼就能看到的目录下,不过这个目录还不是手机根目录/

假设有个config.json在airscript/model/project_name/config.json,我们现在只需获取airscript文件夹所在路径即可:

path = R.sd("/airscript/model/" + __name__ + "/config.json")

print(path)即可打印出完整路径,例如我的是/storage/emulated/0/airscript/model/project_name/config.json

值得注意的是,我用到了__name__变量,在普通的python项目里,__name__一般是__main__,但是在airscript项目里,项目名叫啥__name__就等于啥。

获取屏幕分辨率和滑屏函数

def switch():
a = Device.display().widthPixels # 屏幕宽度
b = Device.display().heightPixels # 屏幕高度
slide((a / 2) - randint(-50, 50), (5 * b / 8) - randint(-50, 50), a / 2, b / 8, randint(300, 500))slide(a / 2, 5 * b / 8, a / 2, b / 8, randint(300, 500)) # 滑屏

slide(x,y,x1,y1,dur) 起始点 (x, y)坐标,结束点 (x1, y1),滑动过程时长dur (ms)。

在滑屏和长按、点击等操作时,我们要做到滑动路径、点击的点坐标、滑动时长、长按时长随机,否则有些app尤其是大厂app,会检测这些反人类行为。

Python的Try Except时打印报错原因和位置

try:
pass
except Exception as e:
print(e)
# 这样子只显示原因,不知道哪里错了
import traceback

try:
pass
except Exception:
traceback.print_exc()
# File "/storage/__init__.py", line 318, in main task(config)
# TypeError: 'NoneType' object is not subscriptable

识别不到控件

我在爬一个app评论区时,明明元素就在图上,查看控件类型是ViewGroup,但是Selector(1).type("ViewGroup").find()就是搜不到。再一看,整个评论区的内容都搜不到。

  • 可能是没开“复杂模式”的原因:

    05控件常用功能

  • (我猜的)可能是动态加载出的元素就不支持被查找,比如评论区的文字,就像网页里通过js添加的元素、文字。好消息是,这样动态加载的控件少,开发了两天,我也才遇到一个评论区动态加载,而且它也只有评论内容是动态加载,而头像、点赞数等则不是,可以被获取。

  • 代码是执行在手机上的,所以你在网页上执行代码,其实是以手机屏幕为输入的,而不是网页端的图像缓存。

    05控件常用功能

解决:控件手段用不了了,改用识图吧。

查到Node后,基于此Node去二次查找

a_node = Selector().text("demo").find()
b_node = a_node.find(Selector().text("回复"))
# 基于a_node查询其下控件,需要构成 a_node.find(`查询表达式`) 格式,注意此时查询表达式的find()在外边而不是在后面
c_node = a_node.find_all(Selector().text("回复")) ¥ 当然也可以查找多个

Bug

# 假设有一个a_node,它下面只有一个孩子控件
if a_node.child(2) is None: # 获取a_node的第二个孩子
pass
# 这是不建议的,目前插件还有bug,会报错说什么json传参数目不对
# 而且不建议这样犯险的写法,要是强类型语言,C这种,活该报错
# 改进
if len(a_node.child()) == 1: # 获取a_node的所有孩子,fan'hui数组
pass

Import

在普通的python项目里,例如Project文件夹下有__init.py__functions.py,前者要想引用后者,前者写import functionsform functions import *即可。

但是在AirScript项目下,有独特的写法:

from .functions import *

from . import functions # .代表AirScript项目根目录

AirScript项目,如果Project文件夹下还有/app/home/banner.py,如何表示路径呢?:像java一样用.表示:

from .app.home import banner

 

- THE END -

AffettoIris

11月30日20:28

最后修改:2023年11月30日
1

非特殊说明,本博所有文章均为博主原创。

共有 2 条评论

回复给 AffettoIris 点击这里取消回复。

  1. 哈喽呀大佬,有没啥好玩的项目推荐一下 ?

    1. AffettoIris

      AffettoIris博主

      @棋: 最近迷上了三国杀OL,还没有搞计算机,没有啥新项目,我以前搞过QQ聊天机器人,是基于NONEBOT框架和python语言的,框架帮你疏通好QQapi和让你的脚本能一键聊天、发图片啥的,挺好玩的