1 项目背景
数据采集业务中,某视频平台的数据大多数是用户上传真实场景的视频,质量较高;而该平台只提供移动端应用内访问,不支持使用浏览器访问,也未提供桌面端,数据获取难度较大。在该项目中,我们积累较多移动端应用数据采集的经验,此次从元素定位的角度出发,对比不同的元素定位方法的使用场景和优劣势分析;
2 定位固定属性元素
如下图,返回按钮是典型的固定元素;图中左侧窗口为应用当前画面,中间窗口为该画面对应源码,右侧窗口为选中元素的属性:

以当前页面的返回按钮为例,我们可以通过右侧窗口展示的元素属性,用不同方法定位到该元素。
2.1 通过 accessibility id 定位
def __traverse__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
while True:
log.info(label + 'locate element start!')
# 定位固定属性元素
accessibilityid = '返回'
element = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value=accessibilityid)
description = element.get_attribute('content-desc')
log.info(label + 'find element by accessibility id: {}'.format(description))
break
return status
结果如下:

2.2 通过 uiautomator 定位
def __traverse__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
while True:
log.info(label + 'locate element start!')
# 定位固定属性元素
uiautomator = 'new UiSelector().description("返回")'
element = self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uiautomator)
description = element.get_attribute('content-desc')
log.info(label + 'find element by uiautomator: {}'.format(description))
break
return status
结果如下:

2.3 通过 classname 定位
def __traverse__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
while True:
log.info(label + 'locate element start!')
# 定位固定属性元素
classname = 'android.widget.Button'
elements = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value=classname)
log.info(label + 'find button number: {}'.format(len(elements)))
for element in elements:
description = element.get_attribute('content-desc')
log.info(label + 'button[{}]: {}'.format(elements.index(element), description))
break
return status
结果如下:

2.4 通过 xpath 定位
def __traverse__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
while True:
log.info(label + 'locate element start!')
# 定位固定属性元素
xpath = '//android.widget.Button[@content-desc="返回"]'
element = self.driver.find_element(by=AppiumBy.XPATH, value=xpath)
description = element.get_attribute('content-desc')
log.info(label + 'find element by xpath: {}'.format(description))
xpath = '//android.widget.Button[contains(@content-desc, "回")]'
element = self.driver.find_element(by=AppiumBy.XPATH, value=xpath)
description = element.get_attribute('content-desc')
log.info(label + 'find element by xpath contains: {}'.format(description))
break
return status
结果如下:

2.5 优缺点
通过xpath定位元素提供更高的灵活性,不仅支持精确匹配,也支持通过文字的匹配等进行模糊查询,相对定位速度略低;其他几种方式在页面比较简单时,定位速度较快,但在复杂页面中,某些属性无值,这时属性就无法使用了。
3 定位固定位置元素
3.1 案例
有些页面元素在页面发生跳转/滑动等变化时,它的位置保持不变,或者元素很难通过元素属性定位,此时可以通过坐标定位该元素,如下:

上图中“听全部”的图标不方便通过属性定位,若它在下滑过程中,位置始终保持不变,则可以通过坐标定位到它,如下图:

上图是通过Appium Inspector调试时唤出的十字标,移动鼠标即可实时获取该位置的坐标,可以控制鼠标与页面元素进行交互;代码如下:
def __click_element__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
try:
# 点击
actions = ActionChains(self.driver)
action_builder = ActionBuilder(self.driver, mouse=PointerInput(POINTER_TOUCH, "touch"))
action_builder.pointer_action.move_to_location(444, 114)
action_builder.pointer_action.pointer_down()
action_builder.pointer_action.release()
actions.w3c_actions = action_builder
actions.perform()
pass
except Exception:
log.error(label + traceback.format_exc())
status = -1
return status
3.2 优缺点
通过这种方式只能与元素进行交互,无法获取元素具体属性。
4 定位固定图案元素
4.1 案例
有些页面元素位置会随着操作改变,甚至同一页面不同时间,元素位置也会改变,若元素图案固定,则可以通过图案定位该元素,如下:

上图中,点赞的位置会随着点赞数不断变化,不同文章点赞的位置也不相同;文本也会随着时间推移发生变化,但是图标不会改变,此时,我们可以使用图标页面进行匹配,获取元素位置:
def __locate_by_image__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
try:
# # 调试过程中获取点赞页面的图片
# # 截图
# image = cv2.imdecode(np.frombuffer(base64.b64decode(self.driver.get_screenshot_as_base64()), dtype=np.uint8), cv2.IMREAD_COLOR)
# # 保存截图
# cv2.imwrite('./store/output/{}.png'.format(datetime.now().strftime('%Y%m%d.%H%M%S')), image)
# 加载编辑得到的图标
with open('./store/input/images/like.icon.png', 'rb') as reader:
image_base64 = base64.b64encode(reader.read()).decode('utf-8')
element = self.driver.find_element(by=AppiumBy.IMAGE, value=image_base64)
# 点击元素
element.click()
pass
except Exception:
log.error(label + traceback.format_exc())
status = -1
return status
def __traverse__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
while True:
log.info(label + 'locate element start!')
# 定位固定图案元素 - 移动坐标
if self.__locate_by_image__():
break
log.info(label + 'locate by image pass')
break
return status
4.2 优缺点
通过图案进行定位解决通过元素属性无法获取元素位置的的问题,且可以直接与元素交互;但是它除了交互之外无法获取元素属性,且对图像精度要求较高,抗干扰能力较弱;
5 定位固定图标元素
5.1 案例
有些页面会根据画面的整体颜色或图像分辨率填充画面,此时很难通过固定图案定位元素,如下:

在上面的图片中,图标相同,但是背景色差异巨大,此时无法通过固定图案定位元素;若直接使用坐标定位,需要判断页面结构是否发生变化(比如进入博主主页)以免发出错误的点击。
此时可以通过灰度图消除背景差异,通过灰度图与图标做相似度判断,进而获取图标位置,并与之交互,如下:

上图中,我们可以通过截图,获取灰度图,保存图标,使用图标与新页面进行灰度图对比,获取图标位置:
def __locate_by_gray_image__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
try:
# # 调试过程中获取分享页面的图片
# # 截图
# image = cv2.imdecode(np.frombuffer(base64.b64decode(self.driver.get_screenshot_as_base64()), dtype=np.uint8), cv2.IMREAD_COLOR)
# # 保存截图
# cv2.imwrite('./store/output/{}.png'.format(datetime.now().strftime('%Y%m%d.%H%M%S')), image)
# # 将截图转换为灰度图
# gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# # 保存灰度图
# cv2.imwrite('./store/output/{}.gray.png'.format(datetime.now().strftime('%Y%m%d.%H%M%S')), gray)
# 截图
image = cv2.imdecode(np.frombuffer(base64.b64decode(self.driver.get_screenshot_as_base64()), dtype=np.uint8), cv2.IMREAD_COLOR)
# 将截图转换为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 读取弹窗图标的灰度图
icon = cv2.imread('./store/input/images/share-gray.png', cv2.IMREAD_GRAYSCALE)
# 使用模板匹配
result = cv2.matchTemplate(gray, icon, cv2.TM_CCOEFF_NORMED)
# 获取匹配结果
_, max_value, _, max_location = cv2.minMaxLoc(result)
# 获取可信值
self.confidence = max_value
# 计算图标的中心位置
self.location = (max_location[0] + icon.shape[1] // 2, max_location[1] + icon.shape[0] // 2)
pass
except Exception:
log.error(label + traceback.format_exc())
status = -1
return status
def __traverse__(self) -> int:
label = '{} '.format(multiprocessing.current_process().name)
status = 0
while True:
log.info(label + 'locate element start!')
# 定位固定图标元素
if self.__locate_by_gray_image__():
break
log.info(label + 'locate by gray image pass, location: {}, confidence: {:.2f}%'.format(self.location, self.confidence * 100))
break
return status
结果如下(基本与使用Appium Inspector获取的坐标一样):


5.2 优缺点
通过图标进行定位削弱页面元素结构不变但位置/背景颜色等变化对元素定位带来的干扰,使用起来几乎无限制,自由度较高;但是它依赖OpenCV,不再兼容旧版Appium搭建的环境,可能会面临一些未知的环境和实现的冲突。
写在最后
您的鼓励是我持续更新的动力,感谢您的支持~

2713

被折叠的 条评论
为什么被折叠?



