共计 6268 个字符,预计需要花费 16 分钟才能阅读完成。
uiautomator2 操作手机
目录
[TOC]
一: 需要:
手机插入电脑端后,能够从电脑端管制手机进行微信公众号的信息爬取
二: 抉择 uiautomator2 调研的起因?
uiautomator2 底层是 Google 提供的用来做安卓自动化测试的 UiAutomator。UiAutomator 性能很强:
能够对第三方 App 进行测试
获取屏幕上任意一个 App 的任意一个控件属性,并对其进行任意操作
测试逻辑可能用 python 编写:将 uiautomator 中的性能凋谢进去,而后再将这些 http 接口封装成 Python 库
UiAutomator 的性能能够满足操控手机,获取组件进而提取想要的信息;uiautomator2 是一个 python 库。基于这些方面的思考,抉择了 uiautomator2 进行理论调研
三: 环境搭建
3.1 装置 python 库
uiautomator2(次要库)
weditor(直观获取组件档次)
jupyter(notebook 笔记不便调试记录)
3.2 配置 adb 环境
下载 adb
ADB 和 Fastboot for Windows: https://dl.google.com/android…
ADB 和 Fastboot for Mac: https://dl.google.com/android…
ADB 和 Fastboot for Linux: https://dl.google.com/android…
配置环境变量
windows: 控制面板 -> 零碎与平安 -> 零碎 -> 高级零碎设置 -> 环境变量,将 adb 门路增加到 PATH 中
linux: 编辑.bashrc, 增加 export PATH=$PATH:adb 门路, 而后执行 source .bashrc
四: 应用
4.1 连贯手机
- 检测手机是否正确配置
关上手机的开发者性能,并开启调试性能
应用 usb 线连贯电脑时,usb 用处抉择“传输文件”
在命令行中应用 adb devices 查看是否胜利检测到 - 获取手机连贯后的识别码
import os
cmd = “adb devices”
r = os.popen(cmd)
text = r.read() # adb devices 命令的执行后果
r.close()
row = text.split(“\n”)[1]
if not bool(row):
print("未检测到设施")
raise Exception("未检测到设施")
else:
con_str = row.split('\t')[0]
print(con_str) # 获取识别码
- 应用 uiautomator2 连贯手机
import uiautomator2 as u2
d = u2.connect(con_str)
print(d.info)
4.2 操作微信搜寻公众号
- 微信版本信息
d.app_info(“com.tencent.mm”) -
关上微信
输出中文的时候须要应用 fastinputIme
d.set_fastinput_ime(True)
先关掉微信重新启动
d.app_stop(“com.tencent.mm”)
d.app_start(“com.tencent.mm”) # 微信
- 获取状态栏大小(后续滑动的时候会用到)
status_bar_selector = d.xpath(‘//*[@resource-id=”com.android.systemui:id/status_bar”]’)
status_bar_x, status_bar_y, status_bar_width, status_bar_height = status_bar_selector.get().rect
print(status_bar_x, status_bar_y, status_bar_width, status_bar_height) -
查找公众号
设定须要爬取的公众号变量
spider_name = “ 进击的 Coder”
定位增加按钮 ”+”
selector_add_button = d.xpath(‘//com.tencent.mm.ui.mogic.WxViewPager//android.widget.ListView//android.widget.FrameLayout/android.widget.LinearLayout[1]/android.widget.RelativeLayout[2]/android.widget.ImageView’)
selector_add_button.click()
定位 ” 增加敌人 ”
d(text=” 增加敌人 ”).click()
抉择 ” 公众号 ”
d(text=” 公众号 ”).click()
输出要搜寻的公众号名字, 并搜寻
d.set_fastinput_ime(True)
d.send_keys(spider_name, clear=True)
time.sleep(1)
在输入法中要关上 OSError: FastInputIME started failed
try:
d.send_action("search")
except OSError:
raise Exception("请手动切换输入法")
-
从搜寻后果中抉择
time.sleep(2)点击对应名字的公众号进入
d.xpath(‘//*[@text=”{}”]’.format(spider_name)).click()
-
判断是否关注
判断是否关注
need_subscribe=d.xpath(‘//*[@text=” 关注公众号 ”]’).all()
if bool(need_subscribe):
print(“ 没有关注该公众号 ”)
# 关注后会主动进入该公众号
d.xpath(‘//*[@text=” 关注公众号 ”]’).click()
# 等页面加载, 而后退回列表页面
time.sleep(2)
d.xpath(‘//android.support.v7.widget.LinearLayoutCompat’).click()
else:
print(“ 曾经关注该公众号 ”)
4.3 解决列表 - 层次结构
ListView 组件下有多个 LinearLayout
LinearLayout 下有 0 /1/ 多个 ViewGroup
ViewGroup 是一条音讯所占据的组件
组件层次结构图示 - 思路
因为列表页面只加载屏幕可能显示的元素, 所以不能间接获取列表的全副, 须要顺次滚动.
先解决以后显示的元素, 提取出 ListView, 而后提取出 ListView 下的所有 LinearLayout 组件
每一个 LinearView 通过 deal_linear_layout 函数进行解决
提取出 LinearLayout 下的所有 ViewGroup 组件
每一个 ViewGroup 通过 deal_view_group 函数进行解决
在进行向下细分解决时, 传入以后组件是否是同类型最初最初一个的标识
如果 LinearLayout 是最初一个
如果没有 ViewGroup, 返回该 LinearLayout 的 rect 属性
如果有 ViewGroup:
如果该 ViewGroup 是最初一个, 返回 rect 属性
如果不是, 点击进入文章具体页面, 进行提取操作, 持续循环
依据 deal_list 函数的返回进行滑动操作
-
解决显示的列表页面
def deal_list():
“”” 解决某时刻的列表 ”””
xpath_linear_layouts = ‘//*[@resource-id=”android:id/list”]/android.widget.LinearLayout’
selector_linear_layouts = d.xpath(xpath_linear_layouts)
count_linear_layout = len(selector_linear_layouts.all())
result = None
for index in range(1, count_linear_layout+1):result = deal_linear_layout('{}[{}]'.format(xpath_linear_layouts, index), index==count_linear_layout)
return result
-
解决单个 LinearLayout 组件
def deal_linear_layout(xpath_linear_layout, is_last_linear):
“”” 解决单个 linear_layout”””
xpath_group_layouts = ‘{}/android.view.ViewGroup’.format(xpath_linear_layout)
selector_group_layouts = d.xpath(xpath_group_layouts)
count_group_layout = len(selector_group_layouts.all())
result = None
if count_group_layout == 0:print("没找到 ViewGroup", is_last_linear) if is_last_linear: result = d.xpath(xpath_linear_layout).get().rect
else:
print("找到了 ViewGroup, 开始解决组音讯", count_group_layout) for index in range(1, count_group_layout+1): result = deal_view_group('{}[{}]'.format(xpath_group_layouts, index), is_last_linear and index==count_group_layout)
return result
-
解决单个 GroupView 组件
def deal_view_group(xpath_group_layout, is_last_group):
“”” 解决单个 group_layout”””
if is_last_group:# 返回最初一个 groupview 的 rect selector_last_group_layout = d.xpath(xpath_group_layout) last_group_layout = selector_last_group_layout.get() return last_group_layout.rect
else:
selector_group_layout = d.xpath(xpath_group_layout) # 进入音讯详情页面,提取音讯具体信息 # selector_group_layout.click() return None
- 解决以后列表并滑动操作(反复执行)
last_layout_rect = deal_list()
sx=360
sy=last_layout_rect[1]
ex=360
ey=status_bar_height
d.drag(sx, sy, ex, ey)
- 结尾判断(未欠缺)
注: 列表是否进行到头的判断还没有增加
4.4 解决文章详情 -
题目
获取题目名
title_selector = d.xpath(‘//*[@resource-id=”activity-name”]’)
if len(title_selector.all()) != 0:
ele = title_selector.get()
print(“ 获取到了题目:”, ele.attrib.get(“content-desc”) if bool(ele.attrib.get(“content-desc”)) else ele.attrib.get(“text”))
else:
print(“ 未找到题目 ”) -
meta 数据
版权 logo
copyright_selector = d.xpath(‘//*[@resource-id=”copyright_logo”]’)
if len(copyright_selector.all()) != 0:
ele = copyright_selector.get()
print(“ 获取到了版权 ”, ele.attrib.get(“content-desc”) if bool(ele.attrib.get(“content-desc”)) else ele.attrib.get(“text”))
else:
print(“ 未找到版权 ”)
作者
author_selector = d.xpath(‘//*[@resource-id=”js_name”]’)
if len(author_selector.all()) != 0:
ele = author_selector.get()
print("获取到了作者", ele.attrib.get("content-desc") if bool(ele.attrib.get("content-desc")) else ele.attrib.get("text"))
else:
print("未找到作者")
工夫
time_selector = d.xpath(‘//*[@resource-id=”publish_time”]’)
if len(time_selector.all()) != 0:
ele = time_selector.get()
print("获取到了工夫", ele.attrib.get("content-desc") if bool(ele.attrib.get("content-desc")) else ele.attrib.get("text"))
else:
print("未找到工夫")
-
注释(未欠缺)
views_selector = d.xpath(‘//*[@resource-id=”js_content”]/android.view.View’)
if len(views_selector.all()) !=0:
for view in views_selector.all():view_string = view.attrib.get("content-desc") if bool(view.attrib.get("content-desc")) else view.attrib.get("text") if bool(view_string): print(view_string.strip())
else:
print(“ 结尾了 ”)
也须要同解决列表雷同的操作,边滑动边解决 - 返回列表页
back_selector = d.xpath(‘//*[@resource-id=”com.tencent.mm:id/dn”]’)
if len(back_selector.all()) == 0:
print(“ 未找到返回键 ”)
else:
print(“ 返回 ”)
back_selector.click()
五: 调研后果
5.1 碰到的问题
组件辨认问题:存在在一些机型上有些界面无奈剖析组件关系,影响下一步的点击操作。
输入法切换问题:输出要搜寻的内容后,须要回车确认。因为无奈辨认手机自带输入法的“搜寻”按钮,所以须要切换 uiautomator2 库自带的输入法。局部型号手机无奈应用命令切换。
5.2 手机型号 & 微信版本
手机型号 安卓型号 微信版本 切换输入法 辨认组件
oppo A5 8.1.0 7.0.9 否 局部不能辨认
搜寻后果界面无奈辨认
oppo A5 8.1.0 7.0.10 否 可
红米 6 9 7.0.12 可 可
红米 6 8.1.0 7.0.6 可 局部不能辨认
搜寻后果界面无奈辨认
红米 note5 9 7.0.10 可 可
5.3 目前论断
组件辨认问题:须要微信版本 7.0.10 以上
输入法切换问题:红米系列手机可主动切换输入法;oppo A5 不能主动切换输入法,须要提醒用户手动切换