目录
CPU: Intel(R) Core?2 DUO P8600 @ 2.40GHz
Memory: Dual DDR3 1066 3.25G
OS: Windows XP
JDK: jdk1.6.0_12
IDE: Eclipse Java EE IDE for Web Developers
Main Plug-in: Maven 2.2.0, jUnit 4.5
Performance Test Tools: jConsole, jvisualvm
该开源项目是利用Maven2来做项目管理,所以使用该插件的IDE来研究这个项目更高效。但由于在安装Maven2的过程中遇到一些麻烦,故在这里赘述一下该插件的大致安装流程。
包结构以及对应含义如下:
src
其子目录中包含了源文件的所有类型
src/main
最终应用的主体部分,代码只是其中的一部分
src/main/java
组成应用的java代码部分,其下的子目录将按照包结构排列
src/main/resources
最终应用的其它组成部分,例如META-INF/MANIFEST.MF
src/test
最终应用的单元测试部分
src/test/java
测试代码的源文件
src/test/resources
测试所利用的资源文件
2.1利用该工具解析网页相关信息
例1:
页面地址: http://v.ku6.com/show/cynCKde5ptJQESf0.html
信息: 标题,发表者,播放次数,评论次数,收藏次数,引用次数,发布时间
例2:
页面地址:http://www.tudou.com/programs/view/A7IFeLUQ2LM/
信息: 标题,播放次数,评论次数,发布时间
2.2通用性评估
利用该工具能否对任意的网页信息进行解析,评估其信息提取功能的通用性。
2.3性能评估
对解析信息时对CPU占用,内存占用等性能指标进行检测,并对其进行性能评估。
测试通过,可以正确获取标题,发布者,播放次数,评论次数,发布时间等信息。具体测试代码见附件AjaxOfTudouTest.java。
Htmlunit获取信息的基本原理就是利用WebClient类去获取指定的页面,然后调用自己编写的java浏览器去解析整个页面,包括对js脚本的编译和执行,但对界面渲染的部分工作省略了,最后就可以对解析完的页面进行处理并提供大量页面元素处理的方法。
其中获取标题即是利用HtmlPage.getTitleText()方法直接获取;发布者和发布时间都是网页中已有的信息,经过指定信息位置即可提取;播放次数和评论次数都是利用ajax技术动态修改的,等到htmlunit将页面中的js脚本解析完即可正确提取。
非常遗憾,该页面测试失败,测试抛出异常如下:
EcmaError: lineNumber=[3] column=[0] lineSource=[] name=[TypeError] sourceName=[http://static.ku6.com/script/podcast/v200907031014/common.js] message=[TypeError: Cannot find function addEvent in object [object Object]. (http://static.ku6.com/script/podcast/v200907031014/common.js#3)]
com.gargoylesoftware.htmlunit.ScriptException: TypeError: Cannot find function addEvent in object [object Object]. (http://static.ku6.com/script/podcast/v200907031014/common.js#3)
经过查证,该问题是由NekoHTML包中的bug造成,由于htmlunit调用了该包所以造成此问题。开发人员表示将会修正这个bug,具体详情见下面页面:
目前解析ajax的方式是WebClient自动执行的,只需等待一定时间就可获得运行后的结果,调用方法是:
WebClient.waitForBackgroundJavaScript(long timeoutMillis)
其中参数timeoutMillis通过阅读源代码发现是运行JS文件的最长时间,如果超过这个指定时间,那么后面的JS解析工作也不再进行。该方法在API有以下注释Experimental API: May be changed in next release and may not yet work perfectly! ,所以在后续版本的htmlunit的使用时应注意该方法的改动。
经过对htmlunit API的查阅、源代码中提供的测试用例的阅读以及自己编写测试代码的过程中,发现对网页中的文本内容提取都可以实现,对于复选框等组件的信息提取也可以做到,只是在实际使用中很多元素的提取比较麻烦。这是由于htmlunit中希望利用ID或者Name的方法提取网页元素,而网页中往往都没有这样的标识,而是利用HtmlElement. getElementsByAttribute(StringelementName,StringattributeName,StringattributeValue)方法去提取信息。
结论:信息提取的功能非常强大,但由于网页的不规范,很多网页元素只能用稍微麻烦的方法来提取,当网页变动时的影响会比利用ID或者Name提取的方式要大。
在以上测试中就发现JS解析还存在部分问题,除了ku6页面解析报错以外,tudou页面的解析有存在问题,具体见5.1节。我之后又对部分其他站点进行测试,发现优酷网站也不能正确解析,报出以下错误:
ERROR 13:22:52,234 XMLHttpRequest: XMLHttpRequest.getResponseHeader() was called before the response was available.
但我又对JS文件非常多的门户站点的页面进行测试,测试都能正常运行,暂时没考虑JS脚本运行的结果正确与否。
结论:JS解析不太完美,但对规范的JS解析基本都能通过,但JS解析的正确性还有待提高,同时作者也在对JS解析的部分问题正在改进。另外鉴于目前能解析JS的工具都存在问题,而该工具能获取到我们想要的信息,这样结果基本令人满意。
测试用例是对同一页面循环的下载、抽取信息,分析工具是利用jConsole和jvisualvm,具体代码见PerformanceTest.java,方便起见以下多组实验都是通过该代码修改得到的。
3.3.1.1下载模块性能测试
图1 下载模块的内存和CPU使用情况
图2下载模块的CPU具体占用情况
从图1可以看出,在本测试用例中,htmlunit对内存的消耗约在13MB,在可接受的范围内。而htmlunit对CPU的占用还是比较多的,基本维持在17%的CPU占用率,从图2中可以看出,其中约65%的CPU计算都是用在对JS的解析上。
3.3.1.2信息抽取模块性能测试
图3 信息抽取模块的内存和CPU使用情况
从图3中可以看出,htmlunit在信息提取时对CPU的占用还是比较多的,基本维持在50%的CPU占用率。由于内存占用并未单独测试,考虑到是未释放WebClient下载页面所占用的内存,所以这部分工作对内存的额外开销是非常低的。
对于上面的实验结果,和我初步的预想类似,为了更直观的看出htmlunit的性能,我用Java标准库中的HttpURLConnection来下载,然后结合正则表达式提取信息做了一个简单的网页信息获取模型,并作了做了一下比较,具体的测试代码见ComparativePerformanceTest.java。比较结果见下表:
Htmlunit
简单模型
网页下载CPU占用率
17%
2%
信息提取CPU占用率
约50%
约50%
内存占用率
13MB
<1MB
下载用时
约3秒
约0.3秒
解析JS用时
约1秒
无解析JS功能
信息提取用时
<1ms
<1ms
注:htmlunit的下载部分包括对下载得到网页数据的预处理。
在本次测试中,发现htmlunit对CPU的资源占用较大,主要的CPU消耗集中在对获取到页面数据预处理的JS解析上。考虑到是模拟浏览器执行JS代码,这样比例的CPU占用其实并不高,在可接受范围,但暂时还不清楚再重负载的情况下htmlunit的表现如何。二这个问题在实际使用中是必须考虑的,只有接受这么高CPU的占用和时间消耗才有实用的可能。前者可以通过提升硬件来减小该问题带来的影响;而后者需要通过优化程序来加快处理速度。但htmlunit在实际使用中可以解决JS问题,这点对一些JS解析才能得到的数据确实是福音,只可惜通用性并不理想,有待进一步提高。
综合考虑,我建议那些JS解析后才能得到的重要数据可以使用htmlunit,但应当保证良好的硬件配置并优化htmlunit的解析过程。而对于其他页面如果不用到JS解析,就不要使用它。
作者明确表示会修正我们遇到的当对ku6页面做JS解析的bug,所以有待新的版本发布时,需要对这个问题进行测试。
另外我还在测试过程中发现对tudou站点解析的时候,使用WebClient.waitForBackgroundJavaScript(long timeoutMillis)方法的最大等待时间参数设置没有太好的依据,当设置成100ms时将无法获取我们需要的信息,而设置1000ms就可以,然而设置200,000ms居然真的会花费3分钟多的时间。经初步研究发现是由于有一个Job的工作一直在处于执行状态而不结束造成。测试了6间房、新浪视频等多个站点,都不同程度存在该问题。
由于时间仓促,以上所有实验都在在轻负载的情况下进行的,尚不知道在重负载下的结果。如果考虑实际使用htmlunit,需要补充重负载下的性能测试。
默认的一些解析对我们的应用是没有意义的,例如CSS解析就可以使用WebClient.setCSSEnable(false)关闭。对于这些细微的性能优化还有待进一步的研究。
在htmlunit的设想中,网页元素主要都是根据ID和Name来获取的,查看源码是发现是在解析过程中对ID和Name都建立了索引,可以方便快捷的提取想要的网页元素。对于没有这两个标识的信息可以通过HtmlElement.getElementsByAttribute()来获取,这是对网页重新扫描,必然会有更多的开销。我不推荐使用Xpath的方式去获取想要的元素,因为很多网页的代码书写并不标准,如果比较深层次的信息靠路径来指定书写就很麻烦,且当网页代码变动时很容易造成要做相应的变更。
在测试过程中,我发现特别是对DIV标签的处理上,由于网页中大量存在DIV标签,该标签可以使用id或者class来标识,由于class是表示一类属性而不是唯一标识,当网页元素使用class时htmlunit自带的HtmlElement获取方法则不能直接使用,利用HtmlElement. getElementsByAttribute(StringelementName,StringattributeName,StringattributeValue)的性能未知,如果有必要对DIV的class也可以建立索引以方便我们的实际应用。
在测试中发现该部分并不是系统的性能瓶颈,如果对性能没有苛刻要求的话,也可以暂不考虑。