想开发网页爬虫,发现被反爬了?想对 App 抓包,发现数据被加密了?不要担忧,使用 Airtest 开发 App 爬虫,只要人眼能看到,你就能抓到,最快只须要2分钟,兼容 Unity3D、Cocos2dx-*、Android 原生 App、iOS App、Windows Mobile……。html
Airtest是网易开发的手机UI界面自动化测试工具,它本来的目的是经过所见即所得,截图点击等等功能,简化手机App图形界面测试代码编写工做。python
爬虫开发本着天下工具为我所用,能让我获取数据的工具都能用来开发爬虫这一信念,决定使用Airtest来开发手机App爬虫。android
因为本文的目的是介绍如何使用Airtest来开发App爬虫,那么Airtest做为测试开发工具的方法介绍将会一带而过,仅仅说明如何安装并进行基本的操做。正则表达式
从Airtest官网:https://airtest.netease.com下载Airtest,而后像安装普通软件同样安装便可。安装过程没有什么须要特别说明的地方。Airtest已经帮你打包好了开发须要的所有环境,因此安装完成Airtest之后就可以直接使用了。spring
Airtest运行之后的界面以下图所示。网页爬虫
以Android手机为例,因为Airtest会经过adb命令安装两个辅助App到手机上,再用adb命令经过控制这两个辅助App进而控制手机,所以首先须要确保手机的adb调试
功能是打开的,并容许经过adb命令安装App到手机上。api
启动Airtest之后,把Android手机链接到电脑上,点击下图方框中的refresh ADB
:微信
此时在Airtest界面右上角应该可以看到手机的信息,以下图所示。编辑器
点击connect
按钮,此时能够在界面上看到手机的界面,而且当你手动操做手机屏幕时,Airtest中的手机画面实时更新。以下图所示。ide
对于某些手机,例如小米,在第一次使用Airtest时,请注意手机上将会弹出提示,询问你是否容许安装App,此时须要点击容许按钮。
先经过一个简单的例子,来看看如何快速上手Airtest,稍后再来详解。
例如我如今想使用电脑控制手机,打开微信。
此时,点击下图中方框框住的touch
按钮:
此时,把鼠标移动到Airtest右边的手机屏幕区域,鼠标会变成十字型。在微信图标的左上角按下鼠标左键不放,并拖到微信右下角松开鼠标。此时请注意中间代码区域发生了什么变化,以下图所示。
好了。以上就是你须要使用电脑打开微信所要进行的所有操做。
点击上方工具栏中的三角形图标,运行代码,以下图所示。
代码运行完成之后,微信被打开了。
在有了一个直观的使用之后,咱们再来介绍一下Airtest的界面,将会更加有针对性。
Airtest的界面以下图所示。
这里,我把Airtest分红了A-F6个区域,他们的功能以下:
A区是经常使用的基于图像识别
的屏幕操做功能,例如:
touch
: 点击屏幕元素swipe
: 滑动屏幕exists
: 判断屏幕元素是否存在text
: 在输入框中输入文字snashot
: 截图通常来讲,是点击A区里面的某一个功能,而后在D区屏幕上进行框选操做,B区就会自动生成相应的操做代码。
B区用来显示和编写Python代码。在多数状况下,不须要手动写代码,由于代码会根据你在手机屏幕上面的操做自动生成。只有一些须要特别定制化的动做才须要修改代码。
D区显示了手机屏幕,当你操做手机真机时,这个屏幕会实时刷新。你也能够直接在D区屏幕上使用鼠标操做手机,你的操做动做会被自动在真机上执行。
F区是一些经常使用工具,从左到右,依次为:
其中1-5很好理解,那么什么是查看运行报告呢?
当你至少运行了一次之后,点击这个功能,会自动给你打开一个网页。网页以下图所示,这是你的代码的运行报告,详细到每一步操做了什么元素。
经过截图功能操做手机虽然方便,可是截图涉及到分辨率的问题,代码不能在不一样的手机上通用。因此对于A区的功能,作点简单操做便可,不用深刻了解。
更高级的功能,须要经过E区实现。
App的布局信息就像网页的HTML同样,保存了App上面各个元素的相对位置和各个参数。对于一个App而言,在不一样分辨率的手机上,可能相同的元素有着不一样的坐标点,可是这个元素的属性参数通常是不会变的。所以,若是使用元素的属性参数来寻找并控制这个元素,就能实如今不一样分辨率手机上的精肯定位。
App的布局信息的格式与App的开发环境有关。点击F区的下拉菜单,能够看到这里可以指定不一样的App开发环境。其中的Unity
、Cocos-*
等等通常是作游戏用的,Android
是安卓原生App,iOS
是苹果的App……以下图所示。
以手机版知乎为例,因为它是Android原生的App,因此在F区下拉菜单选择Android
,此时注意B区弹出提示,询问你是否要插入poco初始代码到当前输入光标的位置,点击Yes
,以下图所示。
此时,B区自动插入了一段代码,以下图所示。
如今,点击E区的锁形图标,以下图所示。
锁形图标激活之后,你再操做D区的屏幕,点击知乎
App下面的知乎
两个字,会发现屏幕上被点击的App并不会打开。但E区和C区却发生了变化,以下图所示。
其中E区显示的树状结构就是当前屏幕的布局信息,这与Chrome开发者工具里面显示的HTML结构一模一样。C区显示的是当前被我点中的元素的信息。
请注意在这些元素信息中,有一个text
属性,它的值为知乎
。那么,这个属性就能够做为一个定位元素,因而能够在B区编写代码:
poco(text="知乎").click()
写完代码之后运行程序,能够看到知乎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操做基本上已经介绍完毕。
在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,只要电脑和手机链接同一个WIFI就能控制:
若是你们对如何开启无线模式有兴趣,请留言,我就会继续写。
一台电脑能够链接三十台手机,那么若是有不少电脑和不少手机,就能够实现手机爬虫集群,其运行效果以下图所示。
关于如何搭建爬虫集群,已经超出本文的范围了。若是你们有兴趣,能够阅读个人书:Python爬虫开发 从入门到实战第十章对于如何搭建手机爬虫集群有详细的说明和注意事项。
若是对个人书有兴趣,请关注个人微信公众号与我交流。