写完代码以后运行程序, 可以看到知乎 App 被打开了. 如下图所示.
注意, 如果你发现手机真机显示的界面与 Airtest 屏幕显示的手机界面不一致, 可能是因为 Airtest 的屏幕被你锁定了. 在 F 区点一下锁形图标, 取消锁定, Airtest 中的手机屏幕就会更新了.
定位并输入
打开知乎以后, 我想使用知乎的搜索功能, 那么继续, 把锁形图标激活, 然后点击知乎顶部的搜索框, 如下图所示:
继续看 C 区显示的搜索框属性, 可以看到这里有一个 name 属性, 它的值是 com.zhihu.Android:id/input, 还有一个 text 属性, 它的值为蔡徐坤任 NBA 新春贺岁大使. 能不能像前面打开知乎一样, 使用 text 这个属性呢? 也行, 也不行. 说它行, 是因为你这么做确实现在能工作; 说它不行, 因为这是知乎的热门搜索关键词, 随时会改变. 你今天使用这一句话成功了, 明天热门关键词变化了, 那么你的代码就无法使用了. 所以此时需要使用 name 这个属性.
常见的基本上不会变化的属性包含但不限于: name type resourceId package.
另外还有一点, 知乎首页的这个搜索框, 实际上是不能输入内容的, 当你点击以后, 会跳转到另一个页面, 如下图所示.
因此你需要先点击一下这个输入框, 跳转到真正的搜索界面:
poco(name="com.zhihu.android:id/input").click()
在真正的搜索界面如下图所示.
可以看到, name 属性的值依然是 com.zhihu.Android:id/input, 此时就可以输入内容了.
输入内容使用的方法为 set_text, 用法为:
poco(name="com.zhihu.android:id/input").set_text('古剑奇谭三')
定位并筛选
输入了搜索关键词以后, 再来看看当前页面, 搜索出现了三个结果:
通过对比这三个结果的属性信息, 发现他们的 name 属性都是相同的, 而 text 不同. 如果像下面这样写点击动作:
poco(name='com.zhihu.android:id/magi_title').click()
那么默认就会点击第一个搜索结果.
如果我想点击第二个搜索结果怎么办呢? 可以这样写代码:
poco(name='com.zhihu.android:id/magi_title', text='古剑奇谭(电视剧)').click()
或者你也可以像列表一样使用索引定位:
poco(name='com.zhihu.android:id/magi_title')[1].click()
这两种写法的前提, 都是我们已经知道了每个结果分别是什么. 假设现在我就想搜索古剑奇谭三, 但我不知道搜索结果是第几项, 又应该怎么办呢? 此时还可以使用正则表达式:
poco(name='com.zhihu.android:id/magi_title', textMatches='^ 古剑奇谭三.*$').click()
滑动屏幕
进入搜索结果以后, 需要查看下面的各种问题, 此时就需要不断向上滑动屏幕. 这里有一点需要特别注意, Airtest 只能获取当前屏幕上的元素布局信息, 不在屏幕上的内容是无法获取的. 这一点和 Selenium 是不一样的.
滑动屏幕使用的命令为 swipe, 滑动屏幕需要使用坐标信息. 但这种坐标和屏幕分辨率无关. 这里的坐标定义为:(x, y), 其中 x 为横坐标, y 为纵坐标. 屏幕左上角为(0, 0), 屏幕右下角为(1, 1), 从左向右, 横坐标从 0 逐渐增大到 1, 从上到下, 纵坐标从 0 逐渐增大到 1.
现在我要把屏幕向上滑动, 那么在真机上面, 我是先按住屏幕下方, 然后把屏幕向上滑动, 所以代码可以这样写:
- # poco.swipe(起点坐标, 终点左边)
- poco.swipe([0.5, 0.8], [0.5, 0.2])
方向示意图如下图所示:
在一般情况下:
向上滑动, 只需要改动纵坐标, 且起点值大于终点值
向下滑动, 只需要改动纵坐标, 且起点值小于终点值
向左滑动, 只需要改动横坐标, 且起点值大于终点值
向右滑动, 只需要改动横坐标, 且起点值小于终点值
在爬虫开发中, 涉及到的 Airtest 操作基本上已经介绍完毕.
单独使用 Python 控制手机
在 Airtest 操作手机虽然方便, 但是不可能在每一台电脑上都安装 Airtest 吧. 所以需要想办法把代码从 Airtest 这个程序中分离出来.
Airtest 基于 Python 的一个开源库 Poco 开发, 而在 Airtest 的 B 区写的 Python 代码, 实际上就是 Poco 的代码. 所以只要安装 Poco 库, 就可以在 Python 中直接控制手机.
安装 Poco 库的命令为:
pip install pocoui
这个库依赖的东西有点多, 安装稍稍慢一些. 安装完成以后, 我们把代码复制到 PyCharm 中, 如下图所示.
运行这段代码, 如果是 Linux 或者 macOS 的用户, 请注意看运行结果是不是有报错, 提示 adb 没有运行权限. 这是因为随 Poco 安装的 adb 没有运行权限, 需要给它添加权限, 在终端执行命令:
- # chmod +x 报错信息中给出的 adb 地址
- chmod +x /Users/kingname/.local/share/virtualenvs/ZhihuSpider/lib/python3.7/site-packages/airtest/core/Android/static/adb/Mac/adb(实际执行时请换成你的地址)
命令运行完成以后再次执行代码, 可以看到代码运行成功, 手机被成功控制了, 如下图所示.
如何获取屏幕文字
由于 Airtest 的编辑器中的代码运行后无法正常打印出中文, 因此后面的代码都直接在 PyCharm 中执行.
既然要做爬虫, 就需要获取手机上的文字内容. 回到搜索页面, 我想知道 "古剑奇谭" 三这个关键字能搜索出多少条结果, 每条结果有多少个讨论, 如下图所示:
此时我们需要做两件事情:
分别查看每一个搜索结果
获取屏幕上的文字
E 区的树状结构如下图所示:
每一个搜索结果的标题作为 text 属性的值, 在 name='com.zhihu.android:id/magi_title'对应的元素中; 每一个搜索结果的讨论数作为 text 属性的值, 在 name='com.zhihu.android:id/magi_count'对应的元素中.
最直接的做法就是分别获取三个标题和三个讨论数, 然后把它们合并在一起:
- title_obj_list = poco(name='com.zhihu.android:id/magi_title')
- title_list = [title.get_text() for title in title_obj_list]
- discuss_obj_list = poco(name='com.zhihu.android:id/magi_count')
- discuss_list = [discuss.get_text() for discuss in discuss_obj_list]
- for title, discuss in zip(title_list, discuss_list):
- print(title, discuss)
运行效果如下图所示:
但是这种做法实际上是很危险的, 假设会有某一个很生僻的搜索结果, 只有标题没有讨论数, 那么这样分开抓取再组合的做法, 就会导致最后匹配错位. 所以合理的做法是先抓大再抓小. 每一组标题和讨论数, 他们都有自己的父节点, 如下图箭头所指向的三个 Android.widget.LinearLayout:
那么现在, 使用先抓大再抓小的技巧, 先把每一组结果的父节点抓下来, 再到每一个结果里面分别获取标题和讨论数.
然而这个父节点又怎么获取呢? 如下图所示, 这个父节点每一个属性值都没有什么特殊的, 写任何一个都有可能与别的节点撞上.
此时, 最简单的办法, 就是在 E 区, 双击父节点. 定位代码就会自动添加, 如下图所示.
这个定位代码看起来非常复杂, 但实际上它的内在逻辑非常简单, 就是从顶层一层一层往下找而已.
自动生成的定位代码如下:
poco("android.widget.LinearLayout").offspring("com.zhihu.android:id/action_bar_root").offspring("com.zhihu.android:id/parent_fragment_content_id").offspring("android.support.v7.widget.RecyclerView").child("android.widget.LinearLayout")[0]
在这个自动生成的定位代码中, 我们看到了 offspring,child 这两种方法. 其中 child 代表子节点, offspring 代表孙节点, 孙节点的子节点, 孙节点的孙节点....... 简言之, 使用 child 只会在子节点中搜索需要的内容, 而使用 offspring 会像文件夹递归一样把里面的所有节点都遍历一次, 直到找到符合条件的属性为止. 显然, offspring 速度会比 child 慢.
实际上, 我们可以对这个定位代码做一些精简:
poco("com.zhihu.android:id/parent_fragment_content_id").offspring("android.support.v7.widget.RecyclerView").child("android.widget.LinearLayout")[0]
这个精简的方法, 与从 Chrome 复制的 XPath 中进行精简是一样的逻辑, 根本原则就是找到 "独一无二" 的属性值, 然后用这个属性值来进行定位.
由于我点击的是第一个搜索结果, 所以定位代码的最后有一个 [0]. 现在由于需要获得所有搜索结果的内容, 所以应该去掉[0] 而使用 for 循环展开, 然后获取里面的内容:
- result_obj = poco("com.zhihu.android:id/parent_fragment_content_id").offspring("android.support.v7.widget.RecyclerView").child("android.widget.LinearLayout")
- for result in result_obj:
- title = result.child(name='com.zhihu.android:id/magi_title').get_text()
- count = result.child(name='com.zhihu.android:id/magi_count').get_text()
- print(title, count)
运行效果如下图所示.
控制多台手机
当我们在电脑上插入多个 Android 手机时, 执行命令:
adb devices -l
运行效果如下图所示.
每个手机都会被列出来. 在最左边的编号就是手机串号. 使用这个串号可以指定多个手机:
- from airtest.core.API import auto_setup
- from airtest.core.Android import Android
- from poco.drivers.Android.uiautomation import AndroidUiautomationPoco
- auto_setup(__file__)
- device_1 = Android('76efadf3a7ce4')
- device_2 = Android('adfasdfasf23')
- device_3 = Android('adifu39ernla')
- poco_1 = AndroidUiautomationPoco(device_1, use_airtest_input=True, screenshot_each_action=False)
- poco_2 = AndroidUiautomationPoco(device_2, use_airtest_input=True, screenshot_each_action=False)
- poco_3 = AndroidUiautomationPoco(device_3, use_airtest_input=True, screenshot_each_action=False)
通过这种方式, 在一台电脑上使用 USBHub, 连上二三十台手机是完全没有问题的.
无线模式
Airtest 支持无线模式, 不需要 USB, 只要电脑和手机连接同一个 Wi-Fi 就能控制:
原文来自: https://juejin.im/post/5c42fd6251882525153c325a
来源: http://www.bubuko.com/infodetail-3069923.html