微信公众号文章爬取之:微信自动化 | 珊瑚贝

本文转载自:陈文管的博客 – 微信公众号文章爬取之:微信自动化 本文内容详细介绍微信公众号历史文章自动化浏览脚本的实现,配合服务端对公众号文章数据爬取来实现微信公众号文章数据的采集。服务端爬取实现见:微信公众号文章爬取之:服务端数据采集。 背景:在团队的学习方面需要每周收集开发方面的博客文章,汇总输出每周的技术周报。周报小组成员收集的文章大多数是来自微信公众号,公众号的内容相对网页博客内容质量还是比较高的。既然数据的来源是确定的,收集汇总的流程是确定的,那么就把这个流程自动化,把人工成本降低到 0。

一、方案选取

1、数据源选取

主要是爬取的数据来源选取,网上资料看的较多是爬取搜狗微信的内容,但是第三方平台(包括新榜、清博等 )的公众号文章数据更新做不到实时,而且数据也不全,还要和各种反爬措施斗智斗勇,浪费时间精力的事情划不来。最直接的方式当然是直接爬取微信公众号历史文章里面的内容。 在前期预研主要参考的资料是知乎专栏:微信公众号内容的批量采集与应用 。 上面的方案是借助阿里巴巴开源的 AnyProxy工具,AnyProxy 作为一个中间人在微信客户端和服务器之间的交互过程中做数据截获和转发。获取到公众号文章的实际链接地址之后转发到自己的服务器进行保存,整个数据采集的自动化程度较大取决于微信客户端的自动化浏览实现。

2、自动化方案选取

如果是比较简单的安卓应用自动化操作的实现,一般直接使用 AccessibilityService 就行,UIAutomator 也是基于 AccessibilityService 来实现的,但是 AccessibilityService 不支持 WebView 的操作,因为微信公众号历史文章页面是用 WebView 来加载的,要实现自动化必须同时支持安卓原生和 WebView 两个上下文环境的操作。 经过现有的几个自动化方案实现对比,最便利又具备极佳扩展性的方案就是使用 Appium

  • Appium 是开源的移动端自动化测试框架;
  • 支持 Native App、Hybird App、Web App;
  • 支持 Android、iOS、Firefox OS;
  • 跨平台,可以在 Mac,Windows 以及 Linux 系统上;
  • 用 Appium 自动化测试不需要重新编译 App;
  • 支持 Java、python、ruby、C#、Objective C、PHP 等主流语言;

更多资料参考:Android 自动化测试框架

公众号文章爬取系统架构图

公众号文章爬取系统架构图

二、Appium 安装配置(Mac)

Appium 程序的安装,我这边不是使用 brew 命令安装的方式,直接从 BitBucket下载 Appium 安装包,也可以从 Github上下载。这边使用 BitBucket 1.5.3 版本。 Appium1.5.0 之后的版本,需要在终端安装 doctor,在终端输入命令:npm install -g appium-doctor,安装完毕之后,在终端输入命令:appium-doctor,查看所需的各个配置是否都已经安装配置完毕。下面是我这边在终端输出得到的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
info AppiumDoctor Appium Doctor v.1.4.3
info AppiumDoctor ### Diagnostic starting ###
info AppiumDoctor  ✔ The Node.js binary was found at: /Users/chenwenguan/.nvm/versions/node/v8.9.3/bin/node
info AppiumDoctor  ✔ Node version is 8.9.3
info AppiumDoctor  ✔ Xcode is installed at: /Library/Developer/CommandLineTools
info AppiumDoctor  ✔ Xcode Command Line Tools are installed.
info AppiumDoctor  ✔ DevToolsSecurity is enabled.
info AppiumDoctor  ✔ The Authorization DB is set up properly.
WARN AppiumDoctor  ✖ Carthage was NOT found!
info AppiumDoctor  ✔ HOME is set to: /Users/chenwenguan
WARN AppiumDoctor  ✖ ANDROID_HOME is NOT set!
WARN AppiumDoctor  ✖ JAVA_HOME is NOT set!
WARN AppiumDoctor  ✖ adb could not be found because ANDROID_HOME is NOT set!
WARN AppiumDoctor  ✖ android could not be found because ANDROID_HOME is NOT set!
WARN AppiumDoctor  ✖ emulator could not be found because ANDROID_HOME is NOT set!
WARN AppiumDoctor  ✖ Bin directory for $JAVA_HOME is not set
info AppiumDoctor ### Diagnostic completed, 7 fixes needed. ###
info AppiumDoctor 
info AppiumDoctor ### Manual Fixes Needed ###
info AppiumDoctor The configuration cannot be automatically fixed, please do the following first:
WARN AppiumDoctor - Please install Carthage. Visit https://github.com/Carthage/Carthage#installing-carthage for more information.
WARN AppiumDoctor - Manually configure ANDROID_HOME.
WARN AppiumDoctor - Manually configure JAVA_HOME.
WARN AppiumDoctor - Manually configure ANDROID_HOME and run appium-doctor again.
WARN AppiumDoctor - Add '$JAVA_HOME/bin' to your PATH environment
info AppiumDoctor ###
info AppiumDoctor 
info AppiumDoctor Bye! Run appium-doctor again when all manual fixes have been applied!
info AppiumDoctor

上面打叉的都是没配置好的,在终端输入命令安装 Carthage :brew install carthage

输入命令查看 JDK 安装路径:/usr/libexec/java_home -V

1
2
1.8.0_60, x86_64: "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home

需要把上面的路径配置到环境变量中,ANDROID_HOME 就是 Android SDK 的安装路径。

输入命令打开配置文件: open ~/.bash_profile,在文件中添加如下内容:

1
2
3
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home  
export PATH=$JAVA_HOME/bin:$PATH  
export ANDROID_HOME=/Users/chenwenguan/Library/Android/sdk

输入命令让配置立即生效:source ~/.bash_profile

更多安装配置资料可参考:Mac 上搭建 Appium 环境过程以及遇到的问题

TIPS

在首次使用 Appium 时可能会出现一个错误:

1
Could not detect Mac OS X Version from sw_vers output: '10.13.2

在终端输入命令:

1
grep -rl "Could not detect Mac OS X Version from sw_vers output" /Applications/Appium.app/

得到如下结果:

1
2
3
4
/Applications/Appium.app//Contents/Resources/node_modules/appium-support/lib/system.js
/Applications/Appium.app//Contents/Resources/node_modules/appium-support/build/lib/system.js
/Applications/Appium.app//Contents/Resources/node_modules/appium/node_modules/appium-support/lib/system.js
/Applications/Appium.app//Contents/Resources/node_modules/appium/node_modules/appium-support/build/lib/system.js

打开上面四个路径下的文件,添加当前的 Appium 版本参数,具体内容可参考:在 Mac OS 10.12 上安装配置 appium

三、具体代码实现

预研资料主要参考这篇博文:Appium 微信 webview 的自动化技术 自动化实现的原理就是通过 ID 或者模糊匹配找到相应的控件,之后对这个控件做点击、滑动等操作。如果要对微信 WebView 做自动化,必须能够获取到 WebView 里面的对象,如果是 Android 原生的控件可以通过 AndroidStudio 里面的 Android Device Monitor 来查看控件的 id、类名等各种属性。

1、Android 原生控件属性参数值的获取

在 AndroidStudio 打开 Monitor 工具:Tools->Android->Android Device Monitor 按照下图的步骤查看控件的 ID 等属性,后续在代码实现中会用到。

Android Device Monitor

Android Device Monitor

2、WebView 属性参数值的获取

如果是在安卓真机上,需要打开 WebView 的调试模式才能读取到 WebView 的各个属性,在微信里面可以在任意聊天窗口输入 debugx5.qq.com,这是微信 x5 内核调试页面,在信息模块中勾选打开 TBS 内核 Inspector 调试功能。

微信X5内核调试页面

微信 X5 内核调试页面

之后还要在真机上安装 Chrome 浏览器,如果是在虚拟机上无需做此操作。 接下来在 Chrome 浏览器中输入:chrome://inspect ,我这边使用的是虚拟机,真机上也一样,进入到公众号历史文章页面,这边就会显示相应可检视的 WebView 页面,点击 inspect,进入到 Developer Tools 页面。

chrome inspect页面

chrome inspect 页面

如果进入到 Developer Tools 页面显示一片空白,是因为 chrome inspect 需要加载 https://chrome-devtools-frontend.appspot.com 上的资源,所以需要翻墙,把 appstop.com 加入翻墙代理白名单,或者直接全局应用翻墙 VPN,具体可参考:使用 chrome remote debug 时打开 inspect 时出现一片空白 下面是美团技术团队历史文章列表的详细结构信息,具体的文章列表项在 weui-panel->weui-panel__bd appmsg_history_container->js_profile_history_container->weui_msg_card_list 路径下。

Chrome inspect查看WebView详细内容

Chrome inspect 查看 WebView 详细内容

继续展开节点查看文章详细结构信息,这边可以看到每篇文章的 ID 都是以 “WXAPPMSG100″开头的,类名都是 “weui_media_box” 开头,一开始的实现是通过模糊匹配 ID 来查找历史文章列表项数据,但在测试过程中出现来一个异常,后来发现,如果是纯文本类型的文章,也就是只有一段话的文章,它是没有 ID 的,所以不能通过 ID 来模糊匹配。

公众号历史文章列表项详细结构

公众号历史文章列表项详细结构

之后就把现有的四种公众号文章类型都找来出来,找它们的共性,虽然 ID 不一定有,但是 class 类型值一定有,四种类型值如下,这样就可以通过 class 类型值来匹配查找数据了。

1
2
3
4
 * weui_media_box appmsg js_appmsg : 文章
* weui_media_box text js_appmsg : 文本
* weui_media_box img js_appmsg : 图片
* weui_media_box appmsg audio_msg_primary js_appmsg playing : 语音

3、具体代码实现

整体自动化是按照如下顺序:通讯录页面 -> 点击公众号进入公众号列表页面 -> 公众号列表项选择一个点击 -> 公众号页面 -> 公众号消息页面 -> 点击 “全部消息” 进入公众号历史文章页面 -> 根据设置的时间类型(一周之内、一个月之内、一年之内或者全部)逐个点击历史文章列表项,完毕之后返回公众号列表页面,继续下一个公众号浏览的操作;

1)初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private static AndroidDriver getDriver() throws MalformedURLException {
        DesiredCapabilities capability = new DesiredCapabilities();
        capability.setCapability("platformName", "emulator-5554");
        capability.setCapability("platformVersion", "4.4.4");
        capability.setCapability("deviceName", "MuMu");
        /**
         * 真机上platformName使用"Android"
         */
        /*
        capability.setCapability("platformName", "Android");
        capability.setCapability("platformVersion", "6.0");
        capability.setCapability("deviceName", "FRD-AL00");
        */
        capability.setCapability("unicodeKeyboard","True");
        capability.setCapability("resetKeyboard","True");
        capability.setCapability("app", "");
        capability.setCapability("appPackage", "com.tencent.mm");
        capability.setCapability("appActivity", ".ui.LauncherUI");
        capability.setCapability("fastReset", false);
        capability.setCapability("fullReset", false);
        capability.setCapability("noReset", true);
        capability.setCapability("newCommandTimeout", 2000);
        /**
         * 必须加这句,否则webView和native来回切换会有问题
         */
        capability.setCapability("recreateChromeDriverSessions", true);
        /**
         * 关键是加上这段
         */
        ChromeOptions options = new ChromeOptions();
        options.setExperimentalOption("androidProcess", "com.tencent.mm:tools");
        capability.setCapability(ChromeOptions.CAPABILITY, options);
        String url = "http://127.0.0.1:4723/wd/hub";
        mDriver = new AndroidDriver<>(new URL(url), capability);
        return mDriver;
    }

如果是虚拟机则 platformName 使用具体的虚拟机名称,如果是真机使用 “Android”,platformVersion 和 deviceName 可以使用工程安装 APK 之后查看详细信息,对应的参数就是显示的系统版本和设备名称。

设备信息

设备信息

URL 参数是在 Appium 里面设置的,确保”http://127.0.0.1:4723/wd/hub” 字符串中的服务器地址和端口与 Appium 设置一致。

Appium URL参数设置

Appium URL 参数设置

2)列表滑动和元素获取

不管是 WebView 还是 Android 原生 ListView 的滑动都需要在 Android 原生上下文环境下操作 driver.context (“NATIVE_APP”); 滑动操作都可以通过如下代码实现,通过滑动前后的 PageSource 对比可以知道列表是否已经滑动到底部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
     /**
     * 滑动列表加载下一页数据
     *
     * @param driver
     * @return
     * @throws InterruptedException
     */
    private static boolean isScrollToBottom(AndroidDriver driver) throws InterruptedException {
        int width = driver.manage().window().getSize().width;
        int height = driver.manage().window().getSize().height;
        String beforeswipe = driver.getPageSource();
        driver.swipe(width / 2, height * 3 / 4, width / 2, height / 4, 1000);
        /**
         * 设置8s超时,网络较差情况下时间过短加载不出内容
         */
        mDriver.manage().timeouts().implicitlyWait(8000, TimeUnit.MILLISECONDS);
        String afterswipe = driver.getPageSource();
        /**
         * 已经到底部
         */
        if (beforeswipe.equals(afterswipe)) {
            return true;
        }
        return false;
    }

TIPS: 如果是 Android 原生的 ListView 读取到的数据是在屏幕上显示的数据,超过屏幕的数据是获取不到的,如果是 WebView 的列表获取的数据是所有已加载的数据,不管是否在屏幕显示范围内。 获取公众号列表数据逻辑代码如下,”com.tencent.mm:id/a0y” 是具体的公众号名称 TextView 的 ID。

1
List<WebElement> elementList = mDriver.findElementsById("com.tencent.mm:id/a0y");

获取历史文章列表数据逻辑代码如下,div 是节点,上面说到公众号四种类型的文章都是以’weui_media_box’类名开头的,通过模糊匹配 class 类名以’weui_media_box’开始的元素来过滤出所有的公众号文章列表项。

1
List<WebElement> msgHistory = driver.findElements(By.xpath("//div[starts-with(@class,'weui_media_box')]"));
3)元素定位方式

如果一定需要模糊匹配就使用 By.xpath () 的方式,因为 Android APK 应用如果有增加或减少了布局字符串资源或者控件,编译之后生成的 ID 可能会不一样,这边说的 ID 是指通过 Android Device Monitor 查看的布局 ID,不是实际的布局代码控件 id,布局控件 id 除非命名改动,否则不会变化。所以不同版本的微信客户端生成的 ID 很可能会不一样,如果要批量实现自动化最好使用模糊匹配的方式,但 By.xpath () 方式查找定位元素是遍历页面的所有元素,会比较耗时,也容易出现异常。 在测试过程中执行

1
driver.findElement(By.xpath("//android.widget.ImageView[@content-desc='返回']")).click();

时候经常出现如下错误,改为

1
driver.findElementById("com.tencent.mm:id/ht").click();

异常消失,猜测原因就是因为 By.xpath () 方法查找比较耗时导致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
org.openqa.selenium.WebDriverException: An unknown server-side error occurred while processing the command. (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 1.41 seconds
Build info: version: '2.44.0', revision: '76d78cf', time: '2014-10-23 20:02:37'
System info: host: 'wenguanchen-MacBook-Pro.local', ip: '30.85.214.6', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.13.2', java.version: '1.8.0_112-release'
Driver info: io.appium.java_client.android.AndroidDriver
Capabilities [{appPackage=com.tencent.mm, noReset=true, dontStopAppOnReset=true, deviceName=emulator-5554, fullReset=false, platform=LINUX, deviceUDID=emulator-5554, desired={app=, appPackage=com.tencent.mm, recreateChromeDriverSessions=true, noReset=true, dontStopAppOnReset=true, deviceName=MuMu, fullReset=false, appActivity=.ui.LauncherUI, platformVersion=4.4.4, automationName=Appium, unicodeKeyboard=true, fastReset=false, chromeOptions={args=[], extensions=[], androidProcess=com.tencent.mm:tools}, platformName=Android, resetKeyboard=true}, platformVersion=4.4.4, webStorageEnabled=false, automationName=Appium, takesScreenshot=true, javascriptEnabled=true, unicodeKeyboard=true, platformName=Android, resetKeyboard=true, app=, networkConnectionEnabled=true, recreateChromeDriverSessions=true, warnings={}, databaseEnabled=false, appActivity=.ui.LauncherUI, locationContextEnabled=false, fastReset=false, chromeOptions={args=[], extensions=[], androidProcess=com.tencent.mm:tools}}]
Session ID: 592813d6-7c6e-4a3c-8183-e5f93d1d3bf0
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:204)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:156)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:599)
at io.appium.java_client.DefaultGenericMobileDriver.execute(DefaultGenericMobileDriver.java:27)
at io.appium.java_client.AppiumDriver.execute(AppiumDriver.java:1)
at io.appium.java_client.android.AndroidDriver.execute(AndroidDriver.java:1)
at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:352)
at org.openqa.selenium.remote.RemoteWebDriver.findElementByXPath(RemoteWebDriver.java:449)
at io.appium.java_client.DefaultGenericMobileDriver.findElementByXPath(DefaultGenericMobileDriver.java:99)
at io.appium.java_client.AppiumDriver.findElementByXPath(AppiumDriver.java:1)
at io.appium.java_client.android.AndroidDriver.findElementByXPath(AndroidDriver.java:1)
at org.openqa.selenium.By$ByXPath.findElement(By.java:357)
at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:344)
at io.appium.java_client.DefaultGenericMobileDriver.findElement(DefaultGenericMobileDriver.java:37)
at io.appium.java_client.AppiumDriver.findElement(AppiumDriver.java:1)
at io.appium.java_client.android.AndroidDriver.findElement(AndroidDriver.java:1)
at com.example.AppiumAutoScan.getArticleDetail(AppiumAutoScan.java:335)
at com.example.AppiumAutoScan.launchBrowser(AppiumAutoScan.java:96)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

如果容易出现如下异常,则是因为页面的内容还未加载完毕,可以通过

1
mDriver.manage().timeouts().implicitlyWait(8000, TimeUnit.MILLISECONDS);

方法设置下超时等待时间,等待页面内容加载完毕,具体超时时间可自己调试看看设置一个合适的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document
  (Session info: webview=33.0.0.0)
  (Driver info: chromedriver=2.20.353124 (035346203162d32c80f1dce587c8154a1efa0c3b),platform=Mac OS X 10.13.2 x86_64) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 2.41 seconds
For documentation on this error, please visit: http://seleniumhq.org/exceptions/stale_element_reference.html
Build info: version: '2.44.0', revision: '76d78cf', time: '2014-10-23 20:02:37'
System info: host: 'wenguanchen-MacBook-Pro.local', ip: '30.85.214.81', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.13.2', java.version: '1.8.0_112-release'
Driver info: io.appium.java_client.android.AndroidDriver
Capabilities [{appPackage=com.tencent.mm, noReset=true, dontStopAppOnReset=true, deviceName=emulator-5554, fullReset=false, platform=LINUX, deviceUDID=emulator-5554, desired={app=, appPackage=com.tencent.mm, recreateChromeDriverSessions=true, noReset=true, dontStopAppOnReset=true, deviceName=MuMu, fullReset=false, appActivity=.ui.LauncherUI, platformVersion=4.4.4, automationName=Appium, unicodeKeyboard=true, fastReset=false, chromeOptions={args=[], extensions=[], androidProcess=com.tencent.mm:tools}, platformName=Android, resetKeyboard=true}, platformVersion=4.4.4, webStorageEnabled=false, automationName=Appium, takesScreenshot=true, javascriptEnabled=true, unicodeKeyboard=true, platformName=Android, resetKeyboard=true, app=, networkConnectionEnabled=true, recreateChromeDriverSessions=true, warnings={}, databaseEnabled=false, appActivity=.ui.LauncherUI, locationContextEnabled=false, fastReset=false, chromeOptions={args=[], extensions=[], androidProcess=com.tencent.mm:tools}}]
Session ID: b5e933e1-0ddf-421d-9144-e423a7bb25b1
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:204)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:156)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:599)
at io.appium.java_client.DefaultGenericMobileDriver.execute(DefaultGenericMobileDriver.java:27)
at io.appium.java_client.AppiumDriver.execute(AppiumDriver.java:1)
at io.appium.java_client.android.AndroidDriver.execute(AndroidDriver.java:1)
at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:268)
at io.appium.java_client.DefaultGenericMobileElement.execute(DefaultGenericMobileElement.java:27)
at io.appium.java_client.MobileElement.execute(MobileElement.java:1)
at io.appium.java_client.android.AndroidElement.execute(AndroidElement.java:1)
at org.openqa.selenium.remote.RemoteWebElement.getText(RemoteWebElement.java:152)
at com.example.AppiumAutoScan.getArticleDetail(AppiumAutoScan.java:294)
at com.example.AppiumAutoScan.launchBrowser(AppiumAutoScan.java:110)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

更多元素定位方法可参考官网:

1
[http://selenium-python.readthedocs.io/locating-elements.html#locating-by-id](http://selenium-python.readthedocs.io/locating-elements.html#locating-by-id)
4)chromedriver 相关问题

在 2017 年 6 月微信热更新升级了 X5 内核之后,真机上切换到 WebView 上下文环境就出问题了,具体见这篇博文的评论 Appium 微信 webview 的自动化技术Appium 微信小程序,driver.context (“WEBVIEW_com.tencent.mm:tools”) 切换 webview 报错 看评论是通过降低 chromedriver 版本的方式来避免异常,但是在试过降低版本到 20 之后还是不行,更新到最新的版本也不行,于是放弃在真机上实现自动化,在模拟器中跑起来的速度也还可以接受。 在真机上跑的时候,切换到 WebView 上下文环境,程序控制台输出 no such session 异常,异常信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
org.openqa.selenium.remote.SessionNotFoundException: no such session
  (Driver info: chromedriver=2.21.371459 (36d3d07f660ff2bc1bf28a75d1cdabed0983e7c4),platform=Mac OS X 10.13.2 x86_64) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 14 milliseconds
Build info: version: '2.44.0', revision: '76d78cf', time: '2014-10-23 20:02:37'
System info: host: 'wenguanchen-MacBook-Pro.local', ip: '192.168.1.102', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.13.2', java.version: '1.8.0_112-release'
Driver info: io.appium.java_client.android.AndroidDriver
Capabilities [{appPackage=com.tencent.mm, noReset=true, dontStopAppOnReset=true, deviceName=55CDU16C07009329, fullReset=false, platform=LINUX, deviceUDID=55CDU16C07009329, desired={app=, appPackage=com.tencent.mm, recreateChromeDriverSessions=True, noReset=true, dontStopAppOnReset=true, deviceName=FRD-AL00, fullReset=false, appActivity=.ui.LauncherUI, platformVersion=6.0, automationName=Appium, unicodeKeyboard=true, fastReset=false, chromeOptions={args=[], extensions=[], androidProcess=com.tencent.mm:tools}, platformName=Android, resetKeyboard=true}, platformVersion=6.0, webStorageEnabled=false, automationName=Appium, takesScreenshot=true, javascriptEnabled=true, unicodeKeyboard=true, platformName=Android, resetKeyboard=true, app=, networkConnectionEnabled=true, recreateChromeDriverSessions=True, warnings={}, databaseEnabled=false, appActivity=.ui.LauncherUI, locationContextEnabled=false, fastReset=false, chromeOptions={args=[], extensions=[], androidProcess=com.tencent.mm:tools}}]
Session ID: e2e50190-398b-4fa2-bc66-db1097201e3f
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:204)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:162)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:599)
at io.appium.java_client.DefaultGenericMobileDriver.execute(DefaultGenericMobileDriver.java:27)
at io.appium.java_client.AppiumDriver.execute(AppiumDriver.java:272)
at org.openqa.selenium.remote.RemoteWebDriver.getPageSource(RemoteWebDriver.java:459)
at com.example.AppiumAutoScan.getArticleDetail(AppiumAutoScan.java:238)
at com.example.AppiumAutoScan.launchBrowser(AppiumAutoScan.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

在 Appium 端输出的异常信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[debug] [AndroidDriver] Found webviews: ["WEBVIEW_com.tencent.mm:tools","WEBVIEW_com.tencent.mm"]
[debug] [AndroidDriver] Available contexts: ["NATIVE_APP","WEBVIEW_com.tencent.mm:tools","WEBVIEW_com.tencent.mm"]
[debug] [AndroidDriver] Connecting to chrome-backed webview context 'WEBVIEW_com.tencent.mm:tools'
[debug] [Chromedriver] Changed state to 'starting'
[Chromedriver] Set chromedriver binary as: /Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-android-driver/node_modules/appium-chromedriver/chromedriver/mac/chromedriver
[Chromedriver] Killing any old chromedrivers, running: pkill -15 -f "/Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-android-driver/node_modules/appium-chromedriver/chromedriver/mac/chromedriver.*--port=9515"
[Chromedriver] No old chromedrivers seemed to exist
[Chromedriver] Spawning chromedriver with: /Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-android-driver/node_modules/appium-chromedriver/chromedriver/mac/chromedriver --url-base=wd/hub --port=9515 --adb-port=5037
[Chromedriver] [STDOUT] Starting ChromeDriver 2.21.371459 (36d3d07f660ff2bc1bf28a75d1cdabed0983e7c4) on port 9515
Only local connections are allowed.
[JSONWP Proxy] Proxying [GET /status] to [GET http://127.0.0.1:9515/wd/hub/status] with no body
[Chromedriver] [STDERR] [warn] kq_init: detected broken kqueue; not using.: Undefined error: 0
[JSONWP Proxy] Got response with status 200: "{\"sessionId\":\"\",\"stat...
[JSONWP Proxy] Proxying [POST /session] to [POST http://127.0.0.1:9515/wd/hub/session] with body: {"desiredCapabilities":{"ch...
[JSONWP Proxy] Got response with status 200: {"sessionId":"166cee263fc87...
[debug] [Chromedriver] Changed state to 'online'
[MJSONWP] Responding to client with driver.setContext() result: null
[HTTP] <-- POST /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/context 200 903 ms - 76 
[HTTP] --> GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/context {}
[MJSONWP] Calling AppiumDriver.getCurrentContext() with args: ["82b9d81c-f725-473d-8d55-d...
[MJSONWP] Responding to client with driver.getCurrentContext() result: "WEBVIEW_com.tencent.mm:tools"
[HTTP] <-- GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/context 200 2 ms - 102 
[HTTP] --> GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/source {}
[MJSONWP] Driver proxy active, passing request on via HTTP proxy
[JSONWP Proxy] Proxying [GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/source] to [GET http://127.0.0.1:9515/wd/hub/session/166cee263fc8757cbcb5576a52f7229e/source] with body: {}
[JSONWP Proxy] Got response with status 200: "{\"sessionId\":\"166cee263...
[JSONWP Proxy] Replacing sessionId 166cee263fc8757cbcb5576a52f7229e with 82b9d81c-f725-473d-8d55-ddbc1f92c100
[HTTP] <-- GET /wd/hub/session/82b9d81c-f725-473d-8d55-ddbc1f92c100/source 200 8 ms - 220

如果要替换 chromedriver 的版本,可以从 Appium 上输出的 Log 信息找到 chromedriver 的路径,在终端依次执行如下命令打开 chromedriver 所在的文件夹。

1
2
cd /Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-android-driver/node_modules/appium-chromedriver/chromedriver/mac/
open .

相应的 chromedriver 和 Chrome 版本对应信息和下载地址可以参考: selenium 之 chromedriver 与 chrome 版本映射表

5)程序使用的 JAR 包

自动化脚本程序要跑起来需要两个压缩包,java-client-3.1.0.jar 和 selenium-server-standalone-2.44.0.jar ,试过使用这两个 JAR 包的最新版本,会有一些奇奇怪怪的问题,这两个版本的 JAR 包够用了。 java-client-3.1.0.jar 可以从 Appium 官网下载:

1
[http://appium.io/downloads.html](http://appium.io/downloads.html)

selenium-server-standalone-2.44.0.jar 可以从 selenium 官网下载:

1
[http://selenium-release.storage.googleapis.com/index.html](http://selenium-release.storage.googleapis.com/index.html)
6)虚拟机

我这边使用的是网易 MuMu 虚拟机,基于 Android 4.4.4 平台,在我自己的 Mac 上跑着没问题,同一个版本安装到公司的 Mac 上就跑不起来,一打开就崩。后面虚拟机自动升级到了 Android6.0.1,脚本跑了就有异常,而且每次打开的时候经常卡死在加载页面,system so 库报异常。所以最好还是基于 Android4.X 的版本上运行脚本,Mac 上没有一个通用稳定的虚拟机,自己下几个看看是否能用,个人测试各类型的虚拟机结果如下: 1)网易 MuMu:在 Mac 上还是比较好用的,但是最新的版本是 6.0.1,初始化经常卡死,无法回退到 4.4.4 平台版本,脚本在 Android6.0 平台上切换到 WebView 的上下文环境异常,升级 ChromeDriver 版本和 Appium 版本也无法解决此问题。 2)GenyMotion:微信安装之后无法打开,一直闪退,页面滑动在 Mac 上巨难操作。 3)天天模拟器:下载的 DMG 安装文件根本无法打开。 4)夜神模拟器:还是比较好用的,但是 Appium adb 无法连上虚拟机,从 Log 来看一直在重启 adb, 最后程序中断。 5)逍遥安卓:没有 Mac 版本。 6)BlueStack:无法安装,安装过程中异常退出,多次重试还是一样。 综上,如果是在 Mac 上运行虚拟机,目前测试有效的是网易 MuMu 基于 Android 4.4.4 平台的版本,其他版本和虚拟机都有各种问题。 另:附上 Android WebView 历史版本下载地址(需要翻墙):

1
[https://cn.apkhere.com/app/com.google.android.webview](https://cn.apkhere.com/app/com.google.android.webview)

WebView 和对应的 ChromeDriver 版本见 Appium GitHub chromedriver 说明文档:

1
[https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/web/chromedriver.md](https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/web/chromedriver.md)
7)编译 IDE

不做 Android 开发的可以下载 Eclipse IDE,在 Eclipse 下运行 Java 程序还比较方便,拷贝工程源码中的三份文件即可

1
2
3
java-client-3.1.0.jar 
selenium-server-standalone-2.44.0.jar  
AppiumWeChatAuto/appiumauto/src/main/java/com/example/AppiumAutoScan.java

Eclipse IDE 下载地址:

1
[http://www.eclipse.org/downloads/packages/](http://www.eclipse.org/downloads/packages/)

Java 版本和对应的 Eclipse IDE 版本参考:

1
[http://wiki.eclipse.org/Eclipse/Installation](http://wiki.eclipse.org/Eclipse/Installation)
8)GitHub 工程源码

源码 GitHub 地址:

1
[https://github.com/wenguan0927/AppiumWeChatAuto](https://github.com/wenguan0927/AppiumWeChatAuto)

运行 Android 工程查看设备信息的时候 Edit Configurations 切换到 app,运行自动化脚本的时候切换到 AppiumAutoScan。支持按最近一周,一个月,一年或爬取所有历史文章,checkTimeLimit () 传入不同限制时间类型的参数即可。

四、参考资料

Appium 官方文档:http://appium.io/docs/cn/about-appium/intro/

Appium 常用 API

Appium 自动化测试–使用 Chrome 调试模式获取 App 混合应用 H5 界面元素

Appium 微信 webview 的自动化技术

Appium Girls 学习手册

Appium:轻松玩转 app+webview 混合应用自动化测试

Appium 微信小程序,driver.context (“WEBVIEW_com.tencent.mm:tools”) 切换 webview 报错

Appium 事件监听

妙用 AccessibilityService 黑科技实现微信自动加好友拉人进群聊

Appium 自动化测试 Android

Windows 下部署 Appium 教程(Android App 自动化测试框架搭建)

微信、手 Q、Qzone 之 x5 内核 inspect 调试解决方案

selenium 之 chromedriver 与 chrome 版本映射表

(Android 开发自测)在 Mac OS 10.12 上安装配置 appium

辅助功能 AccessibilityService 笔记

来源:https://cuiqingcai.com/6763.html

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?