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不能主动切换输入法,须要提醒用户手动切换