Airtest 的使用

Airtest 介绍

Airtest Project 是网易游戏推出的一款自动化测试框架,其项目由以下几个部分构成

Airtest :  一个跨平台的,基于图像识别的 UI  自动化测试框架,适用于游戏和 App , 支持 Windows, Android 和 iOS 平台, 基于 Python 实现。

Poco : 一款基于 UI 组件识别的自动化测试框架,目前支持 Unity3D , cocos2dx, Android 原生 App , iOS 原生 App 和 微信小程序, 也可以在其他引擎中自行介入 poco-sdk 使用, 基于 Python 实现

AirtestIDE : 提供一个跨平台的 UI 自动化测试编辑器, 内置了 Airtest 和 Poco 的相关插件功能,能够快速,简单的编写 Airtest 和 Poco 代码

AirLab:  真机自动化云测试平台, 目前提供 Top 100 手机兼容性测试,海外云真机兼容性测试等服务

私有化手机集群技术: 从硬件到软件, 提供在企业内部私有化手机集群解决方案

总之,Airtest 建立了一个比较完善的自动化测试方案,我们利用它实现所见即所爬,个人认为比 Appium 更加简单易用。本节我们先了解一下 AirtestIDE 的基本使用

准备工作

确保安装好 AirtestIDE ,  Airtest Python  库 和 Poco Python 库

只使用 AirtestIDE 实现自动化模拟和数据爬取也是没问题的, 因为它里面已经内置了 Python 模块, Airtest Python 库和 Poco Python 库,并且提供了非常便捷的可视化点选和代码生成等功能,即使使用者没有任何 Python 基础, 也能自动化控制 App 和完成数据爬取

但是对于需要爬取大量数据和控制页面跳转的场景而言, 仅依靠可视化点选和自动生成代码来自动化控制 App , 其实是不灵活的。 进一步讲,如果我们加入一些代码逻辑,例如流程控制,循环控制语句,就可以爬取批量的数据了,这时候需要依赖 Airtest, Poco 以及一些自定义逻辑和第三方库

Airtest 的官方文档: https://airtest.doc.io.netease.com/tutorial/1_quick_start_guide

详细介绍了 Airtest 的安装方式,包括 AirtestIDE , Airtest Python 库和 Poco Python 库,所以这里建议同时安装

下载路径: Airtest Project (netease.com)

下载后解压, 然后打开 AirtestIDE.exe  会弹出一个登录提示,跳过即可

安装完 AirtestIDE 之后, 它还会安装一个 Python  环境, 这个环境中附带安装了 Airtest Python 库和 Poco Python 库, 不过这个被打包在 AirtestIDE 里面的环境,和系统里安装的 Python 环境并不是同一个,所以推荐直接使用 pip 工具将 Airtest Python 库和 Poco Python 库安装到系统环境中

pip install airtest

pip install pocoui

安装完成之后,在 AirtestIDE 中把默认的 Python 环境由 AirtestIDE 附带的 Python  环境更换成系统的 Python  环境。 

选项 ---- 设置 ---- 自定义 Python.exe  选择已有的 python 解释器即可

安装好之后,需要准备一台 Android 真机 或者这模拟器,真机需要通过 USB 线和电脑相连, 确保 adb  能够正常连接到手机

AirtestIDE 体验

我这里使用的是夜神模拟器,在 CMD 输入  adb devices

然后启动 AirtestIDE , 打开菜单中的 文件----新建脚本---  .air Airtest 项目, 新建一个脚本,选择一个路径,将脚本命名为  script.air   之后点击确定

正常情况下,已经链接上设备了,如果没有连接,刷新一下

我们可以点击页面中的屏幕对手机进行控制,如果出现了链接问题,可以参考官网

这时候可以点击 connect ,然后就可以看到手机屏幕了

至此,要确保所有的步骤都成功了,不然后面可能无法继续

我们来观察一下整个 AirtestIDE 页面,分为 左, 中, 右 三个部分, 一下内容为各组件介绍

左侧靠上的部分是 AirtestIDE 辅助窗, 可以通过一些点选操作实现基于图像识别的自动化配置

左侧中间偏上的部分是 Poco 辅助窗, 可以通过一些点选操作实现基于 UI 组件识别的自动化配置

中间靠上的部分是脚本编辑窗, 即代码编辑区域,可以通过 Airtest 辅助窗和 Poco 辅助窗自动生成代码,也可以自己编写代码,这个代码是基于 Python 语言的

中间靠下是 log 查看窗, 即日志区域, 会输出运行,调试的一些日志

右侧是设备窗,内容为手机屏幕,用鼠标点击这个屏幕,真机或模拟器的屏幕也会跟着变化,而且响应速度很快

Airtest 的图像识别与自动化控制

Airtest 可以基于图像识别来自动化控制 App , 本节我们就来体验一下这个功能。例如先点击作恶的 touch 按钮, 意思是点击屏幕的某个位置

这时 AirtestIDE 会提示我们在右侧手机屏幕上截图,这里我们截取的是 大众点评 的 APP 图标,会发现 script.air 脚本中出现了一行代码, 代码内容为 touch 方法, 其参数是我们刚截取的图片

然后点击右侧的手机屏幕上的 “大众点评” 图标, 进入这个 App  , 再点击左侧的 wait 按钮, 意思是等待指令内容加载出来,之后同样根据提示截图, 如截取首页左上角的美食图标

再点击左侧的 swipe 按钮, 意思是滑动屏幕

这时 AirtestIDE 会提示我们框选一个位置, 联想自己平时滑动屏幕的场景,手指一开始先放在一个位置,然后滑动,到某个位置停止。那么这时的第一步需要框选的位置就是手指一开始需要放置的位置,如 1  的地方 ---中间菜单栏 (菜单栏下方加载的内容会变化, 故选择相比之下更加通用不变的菜单栏作为识别目标) 框选完毕后, AirtestIDE 会提示我们点选一个滑动目标位置,这时选择点选上方的一个点即可, 如果中标 2 的地方,此时会发现 scrpt.air 脚本生成了一个 swipe 方法, 其第一个参数是我们框选的菜单栏图片, 第二个参数是一个 vector, 代表滑动的方向。

这样我们就通过一些可视化完成了自动化控制

最后我们再通过左侧的 keyevent 按钮添加两个键盘事件, 在已经生成的代码开头和结尾分别加一个 HOME 键盘事件, 代表进入首页和返回首页

 先让鼠标停留在需要添加事件的位置,然后点击 keyevent, 输入 HOME, 开头和结尾都是一样

现在总结一下我们实现自动化控制的流程

1. 进入手机首页

2. 点击 “大众点评”  App的图标

4. 等待左上角的 “美食” 图标加载出来

5. 向上滑动手机屏幕

6. 返回手机主页

接下来点击 script.air 脚本上方的运行按钮 (三角按钮), 会发现 AirtestIDE 可以驱动手机完成指定操作了,和我们期望的一样,点击,等待, 滑动操作顺序执行,切 log 查看窗会显示执行的具体过程

以上便是 Airtest 提供的基于图像识别来自动化控制 App 的过程,利用这项技术,我们不用编写任何代码就可以让手机自动操作

其实 script.air 脚本内部对应的就是 Python 代码, 只不过利用 AirtetIDE 封装了一层, 使得编写和操作更加简单了。 我们可以追踪一下源码, 在当前脚本的选项卡右击,在弹出的菜单选项中选择“打开当前项目目录” 就会看到源码内容

可以看到其中有一个 Python 脚本, 和 3 张刚才截取的图片,打开 Python 脚本

可以看到其内容和 AirtestIDE 中自动生成的代码基本一致,不同之处在于这里用了一个  Template 对象代替了图片, 该对象包含图片名,位置,分辨率三个参数,而 AirtestIDE 对图片进行了可视化, 使其更加直观

我们可以更具Python 环境运行这个脚本么?可以, 但是需要在代码开始的 auto_setup(__file__) 和 keyevent("HOME") 之间添加一行代码 init_device() , 调用 init_device 方法的作用完成一些手机初始化配置, 不做这一步可能会报错。 运行脚本后会产生同样的效果, 手机会被自动化控制执行一系列的操作, 同时控制台输出对应的操作日志

Airtest 的相关 API

上面的内容只是 Airtest Python 库的冰山一角,本节列举一些它提供的便捷 API 。 从刚才的 init_device 方法说起,这个方法是用来连接设备并初始化一些连接对象的。如果设置没有初始化则会先初始化设置,并把初始化后的设备当作当前设备

def init-device(platform="Android", uuid=None, **Kwargs)

用法示例:

device: Android = init_device('Android')

print(device)

<airtest.core.android.android.Android object at 0x00000234FE2EC510>

可以发现返回结果是一个 Android 对象, 这个 Android 对象实际上属于 airtest.core.android 包, 继承自 airtest.core.device.Device 类, 与之并列的对象还有 airtest.core.ios.ios.IOS    airtest.core.Linux.linux.Linux    airtest.core.win.win.Windows 等。这些对象都有一些用来操作设备的 API , 下面我们以 Android 对象的 API  为例总结一下

get_default_device :  获取默认设备

uuid :  获取当前设备 UUID

list_app :  列举设备上的所有 App

path_app : 打印出某个 App的完整路径

check_app: 检查某个APP 是否在当前设备上

start_app : 启动某个 APP

stop_app : 停止某个 APP

start_app_timing : 启动某个 APP 并计算启动时间

clear_app : 清空某个 APP 的全部数据

install_app : 安装某个 APP

install_multiple_app : 安装多个 APP

uninstall_app  : 卸载某个 APP

snapshot : 获取屏幕截图

shell : 获取 adb shell 命令执行结果

keyevent : 执行键盘操作

wake : 唤醒当前设备

home : 点击 HOME 键

text ; 向设备输入内容

touch : 点击屏幕上的某处

double_click :双击屏幕某处

swipe : 滑动屏幕, 由一点滑动至另一点

pinch ; 通过手指的捏合操作放大或缩小屏幕

logcat : 记录日志

getprop : 获取某个特定属性的值

get_ip_address : 获取 IP 地址

get_top_activity :获取当前 Activity 

get_top_activity_name_and_pid : 获取当前 Activity 的名称和进程号

get_top_activity_name :  获取当前 Ativity 的名称

is_keyboard_shown : 判断当前是否显示键盘了

is_locked : 判断设备是否锁定了

unlock : 解锁设备

get_display_info :  获取当前显示信息, 如屏幕宽高等

get_current_resolution ; 获取当前设备的分辨率

get_render_resolution : 获取当前渲染的分辨率

start_recording : 开始录制

stop_recording : 结束录制

adjust_all_screen :  调整屏幕的适配分辨率

下面做一些实例感受

from airtest.core.api import *
from airtest.core.android import Android
import logginglogging.getLogger("airtest").setLevel(logging.WARNING)#  初始化设备
device: Android = init_device('Android')
# 是否上锁
is_locked = device.is_locked()
print(f'is_locked {is_locked}')# 如果上锁, 就解锁
if is_locked:device.unlock()
# 唤醒设备
device.wake()
# 列举设备上的 APP
app_list = device.list_app()
print(f'app_list  {app_list}')
# 获取当前设备 UUID
uuid = device.uuid
print(f'uuid {uuid}')
# 获取当前显示信息, 如屏幕宽高等
display_info = device.get_display_info()
print(f'display_info {display_info}')
# 获取当前渲染的分辨率
resolution = device.get_render_resolution()
print(f'resolution {resolution}')
# 获取 IP 地址
ip_address = device.get_ip_address()
print(f'ip_address {ip_address}')
# :获取当前 Activity
top_activity = device.get_top_activity()
print(f'top_activity {top_activity}')
# 判断当前是否显示键盘了
is_keyboard_shown = device.is_keyboard_shown()
print(f'is_keyboard_shown {is_keyboard_shown}')

is_locked False
app_list  ['com.android.cts.priv.ctsshim', 'com.android.providers.telephony' ]
uuid 127.0.0.1:62001
display_info {'width': 720, 'height': 1280, 'density': 1.5, 'orientation': 0, 'rotation': 0, 'max_x': 720, 'max_y': 1280}
resolution (0.0, 0.0, 720.0, 1280.0)
ip_address 172.16.38.15
top_activity ('com.android.launcher3', '.launcher3.Launcher', '2509')
is_keyboard_shown False

从结果可以看出, 借助一些常用的 API , 我们就完成了唤醒手机和获取 APP 列表, UUID ,显示器信息, 分辨率, IP地址, 当前运行的 Activity , 是否显示键盘等操作

获取当前设备

Airtest 中有一个全局变量 G, 它的 DEVICE 属性代表当前的设备对象。这直接调用 device 方法即可获取当前设备,该方法定义如下

def device():

        return G.DEVICE

获取所有设备

print(G.DEVICE_LIST)
uri = 'Android://127.0.0.1:5037/127.0.0.1:62001'
device: Android = connect_device(uri)
print(G.DEVICE_LIST)

[]

[<airtest.core.android.android.Android object at 0x0000028A8B6CE250>]

uri 获取

DEVICE_LIST 是一个列表,元素是 Airtest , 当前已经连接的设备,需要注意的是,在没有调用 connect_device 方法的时候, DEVICE_LIST 是空的, 调用 connect_device 方法后,DEVICE_LIST 会自动添加已经连接的设备

执行命令

可以调用 shell 方法,传入 cmd 参数来执行命令行,直接调用 adb 命令就可以了

@logwrap
def shell(cmd):return G.DEVICE.shell(cmd)
uri = 'android://127.0.0.1:5037/127.0.0.1:62001'
connect_device(uri)
result = shell('cat /proc/meminfo')
print(result)

MemTotal:        3566528 kB
MemFree:         3061388 kB
MemAvailable:    3285952 kB
Buffers:             420 kB
Cached:           280936 kB
SwapCached:            0 kB
Active:           259808 kB
Inactive:         210492 kB

这样就获取了设备的内存信息

启动和停止

调用设备的 start_app 和 stop_app 方法,然后传入 App 的包名,即可启动和停止这个 App

@logwrap
def start_app(package, activity=None):G.DEVICE.start_app(package, activity)@logwrap
def stop_app(package):G.DEVICE.stop_app(package)uri = 'android://127.0.0.1:5037/127.0.0.1:62001'
connect_device(uri)
package = 'com.goldze.mvvmhabit'
start_app(package)
sleep(10)
stop_app(package)

这里指定了 package 为 app5的包名,然后调用 start_app 方法启动了 app5, 等待 10 秒后, 调用 stop_app 方法停止了 app5  的运行

安装和卸载 app

调用设备的 install 和 uninstall 方法, 前者传入 APP 的保存路径,后者传入包名, 即可安装和卸载 app 

# 安装 app

@logwrap

def install(filepath, **kwargs):

        return G.DEVICE.install_app(filepath, **kwargs)

# 卸载 app

@logwrap

def uninstall(package):

        return G.DEVICE.uninstall_app(package)

截图

利用 snapshot 方法获取屏幕截图, 可以通过参数设置存储截图的文件名称和图片的质量等 

def snapshot(filename=None, msg=" ", quality= ST.SNAPSHOT_QUALITY)

用法示例

uri = 'android://127.0.0.1:5037/127.0.0.1:62001'
connect_device(uri)
package = 'com.goldze.mvvmhabit'
G.DEVICE.start_app(package)
sleep(3)
snapshot('app5.png', quality=30)
G.DEVICE.stop_app(package)

唤醒和回到首页

调用设备的wake 和 home 方法,即可唤醒 APP 和回到首页,两个方法的定义如下

# 唤醒

@logwrap

def wake():

        G.DEVICE.wake()

# 回到首页

@logwrap

def home():

        G.DEVICE.home()

这两个方法不需要参数,直接调用即可

点击屏幕

调用 touch 方法点击屏幕, 可传入要点击的图片或绝对位置,还可以指定点击次数,声明如下

@logwrap

def touch(v, times=1, **kwargs)

例如,我们从手机上截一张需要点击的 app 的图片,然后把这张图片声明成一个 Template 对象传入 touch 方法

uri = 'android://127.0.0.1:5037/127.0.0.1:62001'
connect_device(uri)
touch(Template('app.png'))

运行这段代码后,设备就会启动,然后点击这张图片

我们也可以传入绝对位置,具体位置以自己设备为准,需要测量

uri = 'android://127.0.0.1:5037/127.0.0.1:62001'
connect_device(uri)
home()
touch((70, 645))

另外,touch 完全等同于 click 方法,如果需要双击,可以调用 double_click 方法,等同于 touch 的 times = 2 ,  click 方法的参数和 touch 的参数是一样的

滑动

调用 swipe 方法滑动屏幕, 可以传入起始位置和结束位置,两个位置都可以是图片或者绝对位置

声明如下

@logwrap

def swipe(v1, v2=None, vector=None, **kwargs)

例如我们想要控制手机向右滑动,可以实现如下代码

uri = 'android://127.0.0.1:5037/127.0.0.1:62001'
connect_device(uri)
home()
swipe((200, 300), (900, 300))

放大缩小

放大缩小是调用 pinch 方法, 可以通过 in_or_out 参数指定放大还是缩小,还可以指定手指捏合的中心点位置和放大缩小的比例 ,该方法的声明如下

@logwrap

def pinch(in_or_out='in', center=None, percent=0.5)

用法如下

uri = 'android://127.0.0.1:5037/127.0.0.1:62001'
connect_device(uri)
home()
pinch(in_or_out='out', center=(300, 300), percent=0.4)

这里我们调用了  pinch 方法, 并且制定了放大动作 out , 同时指定了捏合的中心点和捏合比例

键盘事件

调用 keyevent 方法来按下某个键, 例如 HOME 键,返回键等。声明如下

def keyevent(keyname, **kwargs)

用法示例

keyevent('HOME')

表示按下HOME 键

输入内容

调用 text 方法来输入内容,前提是目标 Widget 需要处于 active 状态。声明如下

@logwrap

def text(text, enter=True, **kwargs)

调用该方法后,目标 Widget 就会输入相应的字符,输入完之后会执行一次确认(按回车键)

基于 Poco 的 UI 组件自动化

在某些场景下,基于图像的识别来自自动化控制 App 是比较方便的,但也存在一定的局限性。例如图像识别速度可能不快,以及 App 中的某些 UI 如果更换了,就无法和之前截图的图片匹配成功,这些很可能影响自动化测试流程

所以,这里再介绍一些基于 Poco 的 UI 组件自动化控制,说白了就是基于 UI 名称和属性选择器的自动化控制,有点类似于 Appium, Selenium 中的 XPath

新建一个脚本,命名为 script2.air , 右侧同样连接好手机,然后点击左侧 Poco 辅助窗, 选择 Android ,这时会提示我们更新代码,点击确定后脚本中自动添加了代码

from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)

意思是导入了 Poco 包的 AndroidUiautomationPoco 模块,然后声明了一个 poco 对象。接下来就可以通过 poco 对象选择一些内容了。 例如点击左侧 UI 组件树中的 “大众点评“ 节点就会发现手机右侧屏幕上对应的 app 高亮显示了,在 Log 查看窗 还可以看到该节点对应的所有属性。这个操作有点像在浏览器开发者工具中选取网页源代码, 其中的 UI 组件树就相当于网页里的 HTML DOM 树

直接双击 “大众点评”  节点, script2.air 脚本就会出现对应的代码

poco("大众点评")

我们来看一下 poco 的 API ,这是一个 AndroidUiautomationPoco 对象

官方文档: https://poco.readthedocs.io/zh_CN/latest/source/poco.pocofw.html

其用法类似如下

poco = AndroidUiautomationPoco(...)

close_bin = poco('close', type='Button')

会发现 Poco 本身就是一个对象,但可以直接调用 UI 组件的名称, 这归根结底是因为实现了一个 __call__ 方法:

def __call__(self, name=None, **kw):

        if not name and len(kw) == 0:

                warnings.warn("Wildcard selector may cause performance trouble, Please give at least one condition to shrink range of results")

        return UIObjiectProxy(self, name, **kw)

可以看到 __call__ 方法第一个参数是 name  , 其他参数都以 kw  的形式传入,可以任意指定, 最后返回一个 UIObjectProxy 对象

回过头来, 我们看看 “大众点评”  这个节点的 name 参数值是什么, 这个在 Log查看窗 内显示的很清楚

可以看到其 name 就是 “大众点评” 而且整个 UI 树没有与其同名的节点, 于是可以直接调用 poco('大众点评')  选取这个节点,当然也可以任意指定 poco 的其他参数

poco("大众点评", type='android.widget.TextView')

poco("大众点评", text='大众点评')

poco("大众点评", text='大众点评', desc='大众点评')

这三种方法都能选取同样的节点

刚才说到 __call__  会返回一个 UIObjectProxy 对象, 现在我们来看一下这个对象的实现,其 API 链接为: https://poco.readthedocs.io/zh_CN/latest/source/poco.pocofw.html 从中可以看出它实现了 __getitem__ , __iter__, __len__ , child,   children ,  offspring 等方法,所以可以实现链式调用,索引操作和循环遍历

其中一些比较常用的方法如下

child :  选择子节点。 第一个参数是 name ,  即 UI 组件的名称, 如 android.widget.LinearLayout 等, 还可以传入一些属性辅助选择, 其返回结果也是 UIObjectProxy 对象

perent: 选择父节点。 该方法无需传入参数, 可以直接返回当前节点的父节点,返回同样是 UIObjectProxy 对象

sibling:  选择兄弟节点。 第一个参数是 name , 即 UI 组件的名称, 同样可以额外传入一些属性辅助选择,返回结果依然是 UIObjectProxy 对象

click, rclick ,  double_click, long_click : 分别是点击, 右击,双击, 长按。 UIObjectProxy 对象可以直接调用这几个方法, 参数 focus 用于指定点击的偏移量, sleep_interval 用于指定点击完成后的等待时间 (单位为 秒)

swipe :滑动操作。参数 direction 用于指导滑动方向, focus 用于指导滑动焦点的偏移量, duration 用于指导完成滑动所需的时间

wait , wait_for_appearance ; 等待某节点的出现。 参数 timeout 用于指定最长等待时间

attr : 获取节点的属性值, 参数 name 用于指定要获取的属性名, 如 visable ,  text , type, pos, size 等

get_text: 获取节点的文本值。 这个方法非常有用, 可以获取某个文本节点内部的文本数据。

下面调用 click 方法, 将代码改为

poco("大众点评").click()

这样就可以选中并点击  “大众点评”  节点, 点击之后,就进入 “大众点评” 这个 App ,然后可以设置一下等待条件, 等待某个节点加载出来, 证明已经进入 App 了 ,然后点击 左侧的 Poco Pause 按钮, 可以在右侧屏幕上点击想要查看的位置,左侧 UI 组件树就会自动定位到对应的节点, 同时 Log查看窗 会实时显示节点信息

双击左侧 UI 组件树中定位到的节点, script2.air 中又会增加如下内容

poco("com.dianping.v1:id/home_category_layout")

然后可以在后面加上等待时间

poco("com.dianping.v1:id/home_category_layout").wait_for_appearance(10)

代表等待 10 秒,如果加载不出来就报错

同样可以选中中间菜单栏的位置向上滑动

poco(desc="美食").swipe([0, -0.1])

这里往 swipe 方法的参数传入一个列表, 代表滑动方向, 列表第一个元素代表横向偏移量,第二个元素代表纵向偏移量, 由于我们要向上滑动, 因此第一个元素是 0 , 第二个元素是  -0.1

最后在代码开头和结尾添加键盘事件,回到首页,整理代码如下

# -*- encoding=utf8 -*-
__author__ = "86151"

from airtest.core.api import *
auto_setup(__file__)
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
keyevent('HOME')
poco("大众点评").click()
poco("com.dianping.v1:id/home_category_layout").wait_for_appearance(25)
poco(desc="美食").swipe([0, -0.1])
keyevent('HOME')

运行这段代码之后,手机上就会先进入桌面, 然后点击 “大众点评”  图标进入 APP , 等待相应内容加载出来之后, 向上滑动, 最后返回桌面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/51948.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决银河麒麟V10登录循环的方法

解决银河麒麟V10登录循环的方法 一&#xff1a;进入命令行二&#xff1a;删除.Xauthority文件三&#xff1a;重启系统 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在使用银河麒麟桌面操作系统V10时&#xff0c;有时可能会遇到一个令人头…

【题解】—— LeetCode一周小结32

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结31 5.不含连续1的非负整数 题目链接&#xff1a;600. 不含连续…

redis列表若干记录

2、列表 ziplist ziplist参数 entry结构 entry-data:节点存储的元素prelen&#xff1a;记录前驱节点长度encoding&#xff1a;当前节点编码格式encoding encoding属性 使用多个子节点存储节点元素长度&#xff0c;这种多字节数据存储在计算机内存中或者进行网络传输的时的字节…

小型超声波清洗机哪个品牌好用?小型超声波清洗机排名前四

第一次见识到超声波清洗机的神奇是在几年前&#xff0c;当时我去眼镜店配眼镜。等待的过程中&#xff0c;店员把旧的眼镜拿去清洁了&#xff0c;30秒&#xff0c;我就看到了到小污渍和油污被震出来了&#xff0c;感觉特别神奇。几分钟后&#xff0c;清洁完毕&#xff0c;擦干镜…

3个常用zip压缩包文件打来密码删除方法

ZIP压缩包作为一种广泛使用的文件压缩格式&#xff0c;常常用于节省存储空间或便于文件传输。一般情况下为保护文件数据的安全我们会给zip压缩文件设置密码安全保护&#xff0c;但如果后续不需要密码保护了&#xff0c;如何删除密码呢&#xff1f;下面小编给大家介绍三种常用的…

java 函数接口Consumer简介与示例【函数式编程】【Stream】

Java 8 中的 消费者接口Consumer 是一个函数接口&#xff0c;它可以接受一个泛型 类型参数&#xff0c;它属于java.util.function包。 accept(T) 方法&#xff1a;是 Consumer 函数式接口的方法&#xff0c;传入单个输入参数&#xff0c;无返回值&#xff0c;可以用于 Lambda 表…

电脑监控软件有哪些,哪款更好用?一网打尽!电脑监控软件大搜罗,总有一款适合你!

甲&#xff1a;哎&#xff0c;您听说了吗&#xff1f;这年头&#xff0c;电脑监控软件那是五花八门&#xff0c;跟变戏法似的&#xff01; 乙&#xff1a;哦&#xff1f;怎么个五花八门法&#xff1f; 甲&#xff1a;嘿&#xff0c;您还别说&#xff0c;从实时监控到网络追踪…

最佳实践:敏捷需求管理——如何写好用户故事丨IDCF

丁仿&#xff0c;圣略咨询首席敏捷教练&#xff0c;研发效能&#xff08;DevOps&#xff09;工程师&#xff08;中级&#xff09;课程学员 在敏捷项目管理中&#xff0c;用户故事&#xff08;User Stories&#xff09;是需求管理的核心工具。本篇文章将从用户故事的基本概念、编…

复习之 java 锁

裁员在家&#xff0c;没有面试机会&#xff0c;整理整理面试知识点吧&#xff01; 不得不知道的java 锁 Java 中&#xff0c;提供了两种方式来实现同步互斥访问&#xff08;也就是锁&#xff09;&#xff1a;synchronized 和 Lock 多线程编程中&#xff0c;有可能会出现多个线…

期权中非常重要的行权!不懂行权先别交易!

今天带你了解期权中非常重要的行权&#xff01;不懂行权先别交易&#xff01;期权是金融市场中一种常见的衍生品工具&#xff0c;它给予持有者在特定时间内以特定价格购买或出售某个资产的权利。而“行权”是指期权持有者行使期权权利的行为。 期权行权是指期权持有者选择执行…

超网和无类间路由是什么?

​一、超网概述 超网是将多个连续的网络地址组合成一个增加的网络地址的技术。常用于减少路由器的路由表大小&#xff0c;网络的可扩展性。通过合并连续的子网&#xff0c;超网可以减少路由入侵的数量&#xff0c;从而提高网络的效率。 超网的实现基于合并多个具有连续IP地址…

java知识点详解——异常

当输入的数不是整数时 会报错 当输入的被除数为0时 会报错 在以前我们会使用if—else语句用来堵住漏洞&#xff0c;但是那样相当繁琐&#xff0c;很难穷举所有错误 Scanner in new Scanner(System.in); System.out.print("请输入被除数:"); int num1 in.nextInt(…

【Python快速入门和实践016】Python常用脚本-对视频抽取指定帧数并保存

一、功能介绍 这段代码的功能是从一个视频文件中抽取指定数量的帧&#xff0c;并将这些帧保存为图像文件。步骤如下&#xff1a; 设置路径和参数&#xff1a; video_path&#xff1a;视频文件的路径。image_folder&#xff1a;保存抽取图像的目录。num_frames_to_extract&#…

工业相机图像采集卡

什么是图像采集卡&#xff1f; 图像采集卡又称为图像卡&#xff0c;它将相机的图像视频信号&#xff0c;以帧为单位传送到计算机的内存和VGA帧存&#xff0c;供计算机处理&#xff0c;存储&#xff0c;显示和传输等使用。在机器视觉系统中&#xff0c;图像采集卡采集到的图像供…

【C语言】双链表

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、什么是双链表&#xff1f; 二、双链表温习 1. 双链表的结构…

【Django开发】前后端分离django美多商城项目第2篇:展示用户注册页面,1. 创建用户模块子应用【附代码文档】

全套笔记资料代码移步&#xff1a; 前往gitee仓库查看 感兴趣的小伙伴可以自取哦~ 本教程的知识点为&#xff1a; 项目准备 项目准备 配置 1. 修改settings/dev.py 文件中的路径信息 2. INSTALLED_APPS 3. 数据库 用户部分 图片 1. 后端接口设计&#xff1a; 视图原型 2. 具体…

如何诱导AI犯罪-提示词注入

我们用到的大模型基本把政治类信息、犯罪相关信息都已屏蔽。但是&#xff0c;黑客依旧可以使用提示词诱导和提示词注入的方式对大模型进行攻击。 1、提示词诱导 如果直接让AI提供犯罪过程&#xff0c;AI会直接拒绝。虽然AI对于大部分知识了然于心&#xff0c;但因为经过了人工…

javase综合案例3 -- 通讯录

文章目录 一&#xff0c;项目要求基础功能拓展 二&#xff0c;导入jar包 pinyin4j.jar三&#xff0c;程序报下建立Pinyin4j类四&#xff0c;创建实体类Contact五&#xff0c;创建通讯录接口ContactDao六&#xff0c;创建ContactDao接口的视线子类ContactDaoImpl6.1 创建全局的M…

【Dash】Dash模块介绍

什么是Dash&#xff1f; Dash 是一个开源的 Python 框架&#xff0c;用于创建基于 Web 的应用程序。它由 Plotly 公司开发&#xff0c;专为数据科学家和分析师设计&#xff0c;以便他们可以构建自定义的数据可视化 Web 应用程序&#xff0c;而无需具备前端开发知识。Dash 提供…

NGINX项目实战

一、nginx四层代理 部署支持4层TCP/UDP代理的Nginx服务器 部署nginx服务器 编译安装必须要使用--with-stream参数开启4层代理模块。 [rootproxy ~]# rm -rf /usr/local/nginx/ #清理环境 [rootproxy nginx-1.16.1]# ./configure --with-http_ssl_module --with-stream #开…