乐趣区

关于python爬虫:uiautomator2-操作手机

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 连贯手机

  1. 检测手机是否正确配置
    关上手机的开发者性能,并开启调试性能
    应用 usb 线连贯电脑时,usb 用处抉择“传输文件”
    在命令行中应用 adb devices 查看是否胜利检测到
  2. 获取手机连贯后的识别码
    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) # 获取识别码

  1. 应用 uiautomator2 连贯手机
    import uiautomator2 as u2

d = u2.connect(con_str)
print(d.info)
4.2 操作微信搜寻公众号

  1. 微信版本信息
    d.app_info(“com.tencent.mm”)
  2. 关上微信

    输出中文的时候须要应用 fastinputIme

    d.set_fastinput_ime(True)

先关掉微信重新启动

d.app_stop(“com.tencent.mm”)
d.app_start(“com.tencent.mm”) # 微信

  1. 获取状态栏大小(后续滑动的时候会用到)
    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)
  2. 查找公众号

    设定须要爬取的公众号变量

    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("请手动切换输入法")
  1. 从搜寻后果中抉择
    time.sleep(2)

    点击对应名字的公众号进入

    d.xpath(‘//*[@text=”{}”]’.format(spider_name)).click()

  2. 判断是否关注

    判断是否关注

    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 解决列表

  3. 层次结构
    ListView 组件下有多个 LinearLayout
    LinearLayout 下有 0 /1/ 多个 ViewGroup
    ViewGroup 是一条音讯所占据的组件
    组件层次结构图示
  4. 思路
    因为列表页面只加载屏幕可能显示的元素, 所以不能间接获取列表的全副, 须要顺次滚动.
    先解决以后显示的元素, 提取出 ListView, 而后提取出 ListView 下的所有 LinearLayout 组件

每一个 LinearView 通过 deal_linear_layout 函数进行解决

提取出 LinearLayout 下的所有 ViewGroup 组件

每一个 ViewGroup 通过 deal_view_group 函数进行解决
在进行向下细分解决时, 传入以后组件是否是同类型最初最初一个的标识

如果 LinearLayout 是最初一个

如果没有 ViewGroup, 返回该 LinearLayout 的 rect 属性
如果有 ViewGroup:

如果该 ViewGroup 是最初一个, 返回 rect 属性
如果不是, 点击进入文章具体页面, 进行提取操作, 持续循环
依据 deal_list 函数的返回进行滑动操作

  1. 解决显示的列表页面
    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

  2. 解决单个 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

  3. 解决单个 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
  4. 解决以后列表并滑动操作(反复执行)
    last_layout_rect = deal_list()

sx=360
sy=last_layout_rect[1]
ex=360
ey=status_bar_height
d.drag(sx, sy, ex, ey)

  1. 结尾判断(未欠缺)
    注: 列表是否进行到头的判断还没有增加
    4.4 解决文章详情
  2. 题目

    获取题目名

    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(“ 未找到题目 ”)

  3. 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("未找到工夫")
  1. 注释(未欠缺)
    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(“ 结尾了 ”)
    也须要同解决列表雷同的操作,边滑动边解决

  2. 返回列表页
    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 不能主动切换输入法,须要提醒用户手动切换
退出移动版