首页
社区
课程
招聘
[原创] 使用 Appium 进行安卓应用自动化“爬虫”
2020-8-5 23:02 11428

[原创] 使用 Appium 进行安卓应用自动化“爬虫”

2020-8-5 23:02
11428

最近在做微信公众号采集的时候,延申出来一个需求,我要对公众号进行批量采集的话,势必需要先获取我自己关注的公众号列表,后面的自动化已经非常完整,所以只需要获取所有公众号的中文名称,整个流程就打通了。但问题来了,中文名称也不好搞哇。要不我手动一个一个输?列表拉下来一看,嗬,346个。我掐指一算,10秒一个,我得机械的在那儿打打打打字一个小时。不行不行,太要命了。

 

那就找找自动化的方法?微信现在PC端、移动端的接口都是铁板一块,像我这种工具小子,开个Fiddler毛都抓不到,毕竟人家也没用HTTP去通信。从通信角度肯定没办法了。一般这个时候我会想到Frida/Xposed,但现在的目标是微信,之前因为插件已经封过一个号了,需要获取数据的又是大号,不敢造次。因此只能去调研一下其他非侵入式的方法了。找来找去,在UI上做文章最为保险,本文就简单讲一下,如何利用应用自动化测试框架 Appium ,在非root环境的情况下获取任意安卓App界面上任意数据。

数据抓取方法汇总

读者会有疑问了,前面我们说获取App界面上的数据,这应该是爬虫呀,怎么就跑到自动化测试上去了?其实这与安卓的内部机制有关。我们现在在做的事情是,从一个进程内控制另一个进程的运行方式。想要做到这一点,必然需要安卓系统提供API来支持。安卓提供了两个组件(至少是我在调研时想到的两个思路)可以实现这个需求,一个是为残疾人准备的 Accessibility 组件(用于屏幕阅读器、语音助手等等),一个是为测试狗准备的 UIAutomator 组件(用于单元测试)。

 

基于 Accessibility 的方案呢,现成的没有,找了一圈,都得写代码,既然都是写代码,我干啥不写Python呢?在 Reddit 上,有一个帖子提到了可以用 Tasker 配合一些杂七杂八的插件读取界面的内容,看网友的回复中,有一个插件也确实可以读取界面中的文本。但我感觉完成度还是比较低,在手机上用 Tasker 那个垃圾 GUI 完成剩下的功能……想想都挺拉胯的。而且 Tasker 还要钱,即使弄好了也没有写出来的价值,经验也不能复用。

 

基于 UIAutomator 的呢,随便搜搜就能找到一堆,比如 UIAutomator、Selendroid、Espresso等,Appium甚至都没排到谷歌的第一页上,最后选了 Appium 仅仅是因为我搜索关键字 微信 自动化 的时候,Appium 出现在其中一篇文章里了。可能大家都和我一样菜,只会写Python吧。

 

选择 Appium 有两大原因,说出来不怕害臊。一是,服务端(控制手机的host)有图形界面,下载个 exe 双击就启动了。二就是,客户端(调用API控制手机)可以用Python脚本,当然官方还支持 Java、JS、Ruby、C#、PHP 等各种语言,本质都是对服务器 REST API 的封装,看文档 一目了然。当然啦,它还有其他优点,比如 Appium 是 UIAutomator 、Espresso 的上层封装,在客户端上可以用参数指定到底用 UIAutomator 还是用 Espresso 作为 Driver,这些就留给读者自行探索啦。

依赖安装

Appium的架构分为服务端和客户端,服务端是一个缝合怪,在电脑上运行(支持Win/Mac/Linux),负责与设备(如安卓手机、或模拟器)通信,并把UI自动化接口通过 Web API 暴露出来。服务端从官方Github下载解压即可。但安卓调试还需要安装额外的依赖。

 

我的环境是 Win10 且安装了 chocolatey (一个包管理器),用下面一句命令可以装好所有依赖。

 

说白了,就是 Android SDK、adb 和 JDK 。

choco install AndroidStudio adb adoptopenjdk11

同时,需要确保 ANDROID_SDK_ROOTJAVA_HOME 环境变量正确设置。

ANDROID_SDK_ROOT=%USERPROFILE%\AppData\Local\Android\Sdk
JAVA_HOME=C:\Program Files\AdoptOpenJDK\jdk-11.0.8.10-hotspot

上面步骤完成后,启动 Appium 程序,点蓝色大按钮启动服务就行了。

 

Appium主界面

编写客户端脚本

下面我们就要写脚本获取微信公众号列表中的内容了。安卓应用的界面,在数据层面也是一个树状结构,实现上用 XML 表示,就像我们在写 Web 爬虫时需要处理的 DOM 树一样,而我们现在要获取一个应用某个界面下、某个组件中的数据,也可以通过 xpath 定位对应的组件,然后从实例里提取我们所需的信息。在 Chromium 里面我们有开发者工具,在 Appium 里呢?我们也有!

 

现在我们来启动一下 Appium 的“开发者工具”。Appium 的配置比较晦涩,主要是它搞了一大堆不明所以的名词,比如启动配置参数,在这里叫 Desired Capabilities 。

 

Desired Capabilities配置

 

我们在主界面里打开一个 Session Window 后,在 Desired Capabilities 的 JSON Representation 里面,输入以下的配置参数并保存。记得将 device id 替换成 adb devices 里显示出来的设备ID。非常关键的一点是,在任何情况下不要漏掉 noReset 这个参数。如果不加这个参数,默认会在每次启动 Session 时清除掉目标应用的全部数据!我一个微信号的聊天记录就这么被清理没了……

{
  "platformName": "Android",
  "deviceName": "YOUR_DEVICE_ID",
  "appPackage": "com.tencent.mm",
  "appActivity": ".ui.LauncherUI",
  "noReset": true
}

配置好参数以后,一样点击蓝色大按钮启动。此时 Appium 会强行 kill 掉并重启微信客户端,然后就可以用类似 Chrome 开发者工具一样的方式,用鼠标点击确认目标组件所在的层级结构了。

 

Inspector界面

 

以微信为例,我在这里选中的是某个公众号的名字,这个组件是个 TextView ,它的 resource-id 是 com.tencent.mm:id/a71 。尝试过后发现,列表中所有相同类型元素的ID都是一样的(比如“阿里云”这个标签和“敖厂长”标签的 resource-id 字段都是相同的)。

 

但很明显这是个混淆过后的 ID,而且应该会随微信版本更新而变化,这么搞不优雅。我们可以通过 XPath 来定位这个元素的位置,再动态获取它的ID,这样写了脚本就不怕微信更新了,一样能用。

 

经过一些尝试,我发现通过已知公众号名字来定位最为高效。最终我采用的表达式是 //android.widget.TextView[@text="阿里云"] 。通过这个表达式获取到 element,再通过这个 element 的 resource-id 即可获取到可视范围内所有的标签字段。

 

但还有一个难点,我提到了 可视范围内 ,每次获取最多也就能获取到一页,怎么翻页呢?

 

这里是我最终没有解决的一个难点。因为我遇到了两个问题。

 

一个是翻页这个 API 似乎在 Python SDK 里面他就没实现(虽然文档里写的是实现了),这就很糟心。最终的这个 API 和文档里也不一样,和文档里贴的 selenium API 也不一样。不一样你写它干啥??翻页不行我就模拟点击呗,结果模拟点击的步骤执行之间存在很大的延迟,我想实现的效果是,按下、拖动、松手,结果调用的时候,按下和拖动之间隔了有一秒还多,触发了微信菜单里长按操作的 context menu,无论如何无法解决。想换个Driver,结果又碰到了问题二。

 

二就是Espresso的实现似乎是基于Instrumentation的,启动的时候,花半天编译一个专用的apk出来,结果运行时报错,提示说被插桩的应用需要和源应用相同的签名证书。对于我们来讲这当然不可能实现了,签名证书如果可以伪造,我就可以写一个假冒的微信了。当然,签名伪造从 Xposed 层或者在 framework 做一些修改都可以实现,但我的主力手机连 root 都没有,所以也不折腾这些有的没的了。

 

Image

 

因此最终的妥协就是,每次识别完成以后程序 sleep 两秒,然后我手动拖动一下界面,还是很low的样子……

 

不过总结下来,代码量还是很小的,浓缩下来的精华也就三四十行。运行服务端以后,再运行这个 python 脚本就行了。

import json
import time

import appium.webdriver
from appium.webdriver.common.touch_action import TouchAction

dc = dc_wechat = {
    "platformName": "Android",
    "deviceName": "DEVICE_ID",
    "appPackage": "com.tencent.mm",
    "appActivity": ".ui.LauncherUI",
    "noReset": True,
    "newCommandTimeout": 3600,
}

FIRST_ACCOUNT_NAME = "阿里云"

def main():
    driver = appium.webdriver.Remote("http://localhost:4723/wd/hub", dc)
    driver.implicitly_wait(3600)

    sample_element = driver.find_element_by_xpath(
        f'//android.widget.TextView[@text="{FIRST_ACCOUNT_NAME}"]')
    rid = sample_element.get_attribute("resourceId")  # 'com.tencent.mm:id/a71'

    accounts = set()
    prev_count = -1
    retry = 3

    while retry > 0:
        prev_count = len(accounts)
        elements = driver.find_elements_by_id(rid)
        for e in elements:
            accounts.add(e.text)

        if prev_count == len(accounts):
            retry -= 1
            print(f"about to stop, {retry}")
        else:
            print(f"retrieved {len(accounts) - prev_count} accounts")
            retry = 3
        time.sleep(2)

    print(list(accounts))
    with open("output.json", 'w') as f:
        json.dump(list(accounts), f)

Image

 

最终结果,能用

 

Image

结语

因为调研到实现的时间比较少,因此笔者对文中部分功能的实现原理还不是很了解,也就是能用。就这,我还是得说,从头到尾搞这玩意花了整整一晚上,还不如我一个一个输进去来得快呢。

参考资料

  1. Appium: Mobile App Automation Made Awesome. https://appium.io/
  2. Using Tasker to read text on a screen : tasker https://www.reddit.com/r/tasker/comments/99gheb/using_tasker_to_read_text_on_a_screen/
  3. Task Assist - Run a "UI Query" easily on ANY screen to grab all its info for AutoInput. | AutoApps Forums https://forum.joaoapps.com/index.php?resources/task-assist-run-a-ui-query-easily-on-any-screen-to-grab-all-its-info-for-autoinput.293/
  4. 谈谈微信自动化的几种方案 - 知乎 https://zhuanlan.zhihu.com/p/109342914
  5. Top 5 UI Frameworks For Android Automated Testing | Sauce Labs https://saucelabs.com/blog/the-top-5-android-ui-frameworks-for-automated-testing
  6. Installation via Desktop App Download - Getting Started - Appium http://appium.io/docs/en/about-appium/getting-started/?lang=zh#installation-via-desktop-app-download
  7. Status API - Appium http://appium.io/docs/en/commands/status/
  8. UIAutomator2 (Android) - Appium http://appium.io/docs/en/drivers/android-uiautomator2/
  9. Espresso (Android) - Appium http://appium.io/docs/en/drivers/android-espresso/
  10. Releases · appium/appium-desktop https://github.com/appium/appium-desktop/releases
  11. Chocolatey Software | Chocolatey - The package manager for Windows https://chocolatey.org/
  12. Scroll - Appium http://appium.io/docs/en/commands/interactions/touch/scroll/

原文首发于 个人博客 与公众号:rabyte


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-8-5 23:03 被ttimasdf编辑 ,原因: 忘记删除hexo的标记了
收藏
点赞5
打赏
分享
最新回复 (7)
雪    币: 19794
活跃值: (4867)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
浅笑不语 2020-8-6 06:57
2
0
感谢分享
雪    币: 181
活跃值: (2868)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huaerxiela 2020-8-6 09:13
3
0

自动关注,自动取关,自动点赞,自动聊天,自动xx
https://bbs.nightteam.cn/thread-86.htm

微X都还活着,没见怎么封,只是有一些【高风险】操作,别一直做就ok

最后于 2020-8-6 09:15 被huaerxiela编辑 ,原因:
雪    币: 1867
活跃值: (3703)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 1 2020-8-6 10:21
4
0
安利supperAppium项目
雪    币: 1631
活跃值: (1309)
能力值: ( LV7,RANK:117 )
在线值:
发帖
回帖
粉丝
ttimasdf 2 2020-8-6 14:17
5
0
virjar 安利supperAppium项目
 找到项目了,之前另一位大佬给我推荐了一个类似的项目,我定睛一看原来就是基于你的项目写的  不愧是大佬.jpg
雪    币: 209
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
asher.wu 2020-8-7 10:47
6
0
感谢分享
雪    币: 1636
活跃值: (653)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
琅環玉碎 2020-11-18 08:17
7
0
aujs如何?
雪    币: 453
活跃值: (129)
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
同志们好啊 2021-2-19 14:02
8
0
现在的微信,都没几个搞得好的公众号了.
有的人,可能都做累了.不想做了.
游客
登录 | 注册 方可回帖
返回