利用Java+JSoup实现页面资源的下载
作者:强哥   类别:Java开发    日期:2018-12-13 14:52:24    阅读:3569 次   消耗积分:0 分

实验简介



在进行性能测试的时候,有一个问题必须引起我们的重视,那就是浏览器渲染页面的过程是需要有很多辅助资源的支撑的。

简单理解就是浏览器不可能单纯靠一个HTML源文件就可以将页面渲染出来,一个HTML页面通常是由多个资源文件构成的。

比如常见的资源文件通常有图片,CSS样式文件,JavaScript文件等,所以为了模拟真实的用户使用场景,我们也需要对页面中的所有资源文件进行下载处理,这样才是一个完整的请求,统计到的响应时间才是更为准确的。


在上一实验中,我们统计到的请求的响应时间,其实是存在严重问题的,因为这只是针对一个HTML响应的持续时间,但是一个页面中通常不止一个请求(JSON数据除外)。就像我们在使用协议监控工具监控一个页面的请求时一样,我们可以看到,虽然用户只提供了一个网址或者点击了一次提交按钮,但是请求数量却是非常多的。


本实验主要为大家介绍如何利用Java原生代码及JSoup完成对页面资源文件的下载,以及针对一个页面的所有请求,统计出相对准确的响应时间及响应的内容大小。


 

实验目的



1.理解性能测试的真实场景设计并进行应用。

2.熟练运用Java完成页面资源的获取与下载。

3.熟练运用JSoup完成页面资源的解析与获取。

 


实验流程



1.利用Java获取页面资源。


在接口测试部分,我们已经理解了如何利用正则表达式完成页面资源的URL地址的获取。在性能测试的脚本开发过程中,这也是一个必不可少的部分,否则我们统计到的性能测试数据将会与实际的用户体验有较大的差距。我们先来看看,当我们在URL地址栏输入一个URL地址后,整个过程发生了什么。


(1).首先将整个页面的HTML响应内容下载回来。

(2).解析整个HTML页面中的资源文件。

(3).通过解析获取到整个页面中的图片资源。

(4).继续解析获取整个页面中的CSS资源文件。

(5).利用CSS资源文件结合HTML标记内容对整个页面进行渲染。

(6).继续解析整个页面中的JavaScript文件,并获取到其内容。

(7).如果页面中还有其它资源文件,如Flash,视频,音频等,继续下载处理。

(8).通常针对Flash,视频,音频等额外的资源文件,我们可以进行额外的处理。

 

那么,如何利用Java结合正则表达式针对Phpwind的首页进行页面资源的URL地址的获取呢?基本思路就是根据每一种资源文件在响应中的相对固定的特征,通过设置左右边界的方式,获取到某个资源的URL地址,并对其进行组装处理,变为一个完整的以http://开头的绝对URL地址。

代码如下:


// 设置类成员变量

private HttpRequestor hr = new HttpRequestor();

private String baseUrl = "http://localhost/phpwind/";

private FileUpDownload updown = new FileUpDownload();

 

// 获取所有CSS资源

public List<String> getAllCss(String response) {

List<String> list = hr.findListData(response, "text/css\" href=\"", "\"");

List<String> urls = new ArrayList<String>();

for (int i=0; i<list.size(); i++) {

if (!list.get(i).startsWith("http://")) {

urls.add("http://localhost/phpwin/" + list.get(i));

}

else {

urls.add(list.get(i));

}

}

return urls;

}

 

// 获取所有图片资源

public List<String> getAllImage(String response) {

List<String> list = hr.findListData(response, "img src=\"", "\"");

List<String> urls = new ArrayList<String>();

for (int i=0; i<list.size(); i++) {

if (!list.get(i).startsWith("http://")) {

urls.add(baseUrl + list.get(i));

}

else {

urls.add(list.get(i));

}

}

return urls;

}

 

// 获取所有JS资源

public List<String> getAllJs(String response) {

List<String> list = hr.findListData(response, "JavaScript\" src=\"", "\"");

List<String> urls = new ArrayList<String>();

for (int i=0; i<list.size(); i++) {

if (!list.get(i).startsWith("http://")) {

urls.add(baseUrl + list.get(i));

}

else {

urls.add(list.get(i));

}

}

return urls;

}

 

当我们获取到页面的所有资源文件后,我们需要对其进行下载。此时通常有两种处理方式,一种是直接保存到内存中,这是一种无缓存的方式,下一次访问同样继续进行一次下载操作。另外一种就是利用浏览器缓存,将资源文件保存到硬盘的临时文件中,下一次只需要从硬盘直接加载即可。其实无论哪一种情况,我们都需要先将这些资源文件从服务器获取到客户端环境中来,无非就是是否要保存到硬盘的问题而已。


通常情况下,浏览器都会利用缓存机制将资源文件保存到客户端硬盘中,而且保存到硬盘这一步骤并不会太多影响响应时间。我们在接口测试部分已经实现了文件下载的操作类FileUpDownload,直接重用即可。具体的测试代码如下:


// 统计下载一个页面的响应时间

public void doTest() {

long startTime = System.currentTimeMillis();

String getUrl = "http://localhost/phpwind/";

String response = hr.sendGet(getUrl);

this. downloadResources(response);

long endTime = System.currentTimeMillis();

int duration = (int)(endTime - startTime);

System.out.println(duration);

}

 

// 下载页面中的所有资源文件

public void downloadResources(String response) {

List<String> cssList = this.getAllCss(response);

List<String> jsList = this.getAllJs(response);

List<String> imgList = this.getAllImage(response);


for (int i=0; i<cssList.size(); i++) {

updown.doDownload(cssList.get(i), "D:\\TestUD");

}

for (int i=0; i<jsList.size(); i++) {

updown.doDownload(jsList.get(i), "D:\\TestUD");

}

for (int i=0; i<imgList.size(); i++) {

updown.doDownload(imgList.get(i), "D:\\TestUD");

}

}

 

上述代码我们直接调用了已经实现的方法“doDownload()”,成功地从页面响应内容中识别出了页面资源并完成了下载。但是这里面仍然存在三个问题需要解决:


(1).页面中可能会存在一些资源文件是解析错误的。比如在对Phpwind首页的过程中,我们看到了这样一个页面资源“init.php?sitehash=10VgMHBFJQB1YBDFAHCQcDVFNVAFQKDAhXBgVdUQRXXAw&v= 7.3.2&c=0”,由于这个资源文件也是一个JavaScript脚本,但是却存在两个问题。一是本身无法下载,连接到的服务器已经失效。二是无法将该文件按原文件名在硬盘上新建文件,因为操作系统文件名命名规范的原因。虽然这种情况不一定每个系统都存在,但是通常针对我们自己的系统的性能测试开发过程中,我们需要对其进行特殊处理,比如在getAllJs()方法中将其排除。当然,另外一种解决方案是直接提交一个Bug,将其问题修复。


(2).关于资源文件的下载问题,由于我们在性能测试过程中,会模拟大量用户并发操作。所以我们应该要为每一个用户创建一个用于保存资源文件的单独的目录,这样才是一种真实的模拟。否则,极有可能导致多个线程同时操作同一个文件夹下面的同一样的文件,则很有可能引起资源冲突。当然,其解决方案比较简单,我们可以利用线程名称来创建一个子目录。重构后的downloadResource()方法的代码如下:


public void downloadResources(String response) {

List<String> cssList = this.getAllCss(response);

List<String> jsList = this.getAllJs(response);

List<String> imgList = this.getAllImage(response);


// 为每一个线程单独创建一个目录

String threadName = Thread.currentThread().getName();

String folder = "D:\\TestUD\\" + threadName;

File file = new File(folder);

if (!file.exists()) {

file.mkdirs();

}


for (int i=0; i<cssList.size(); i++) {

updown.doDownload(cssList.get(i), folder);

}

for (int i=0; i<jsList.size(); i++) {

updown.doDownload(jsList.get(i), folder);

}

for (int i=0; i<imgList.size(); i++) {

updown.doDownload(imgList.get(i), folder);

}

}

 

(3).最后一个,也是非常重要的一个问题,必须引起我们足够的重视。由于通常情况下,由于浏览器缓存的原因,当用户第一次访问了某一个页面后,资源文件会被保存到本地硬盘。但是下一次用户再访问同一个页面时,浏览器将优先读取本地硬盘的资源,如果存在,则直接从本地硬盘获取资源进行页面渲染,而不需要再次发送请求给服务器获取。这个过程将节省大量的服务器处理资源和带宽占用。所以我们在性能测试的执行过程中,也必须严格模拟这个过程,否则测试数据也将失去其准确性。思路清楚了,解决方案其实相对简单,就是每一个线程的每一次运行,我们都对其目录下的文件进行一次判断,如果存在,则可以直接跳过下载这一步。我们可以对FileUpDoenload类的代码重构如下:


public void doDownload(String getUrl, String folder) {

try {

URL url = new URL(getUrl);

HttpURLConnection urlConnection =

(HttpURLConnection) url.openConnection();

 

// 本次连接相关的参数设置

urlConnection.setConnectTimeout(30000);

urlConnection.setReadTimeout(30000);

urlConnection.setUseCaches(true);

urlConnection.setRequestMethod("GET");


// 建立与服务器的连接

urlConnection.connect();


// 通过URL地址中的最后一个"/"作为分隔符,分离出原始文件名,此处可自定义文件名

int posLast = getUrl.lastIndexOf("/") + 1;

String fileName = getUrl.substring(posLast);


// 定义一个文件输出流,用于将下载的文件保存到硬盘

File outfile = new File(folder + "\\" + fileName);


// 模拟浏览器缓存,如果文件已经存在,则跳过下载这一步

if (outfile.exists()) {

return;

}


OutputStream os = new FileOutputStream(outfile, false);


// 定义一个输入流,用于从服务器端获取到该文件字节流

InputStream is = urlConnection.getInputStream();


// 新建一个字节数组,用于缓存从服务器端读取来的内容

byte[] buf = new byte[1024];

int bufLen = 0; // 定义每一次循环读取到的字节数组的长度

while ((bufLen = is.read(buf)) != -1) {

byte[] temp = new byte[bufLen];

// 将buf中的内容复制到temp中

System.arraycopy(buf, 0, temp, 0, bufLen);

// 将字节数组写入到outfile中

os.write(temp);

}


// 释放资源

urlConnection.disconnect();

os.close();

is.close();

System.out.println("文件下载完成.");

}

catch (Exception e) {

e.printStackTrace();

}

}

 

针对上述下载资源的代码,通常情况下,我们还可以为该方法设定一个开关参数,当调用时我们可以指定是否需要模拟缓存。代码重构的关键部分如下所示:


public void doDownload(String getUrl, String folder, boolean useCache) {

// 此处省略一批代码

// 当文件存在并且指定使用缓存时,才跳过本次下载

if (outfile.exists() && useCache) {

return;

}

// 此处省略一批代码

}

 

当然,进行了这样的重构以后,我们在方法downloadResources()中调用doDownload()时必须额外再多加一个参数,指示其是否使用缓存。

 

2.利用JSoup获取页面资源。


JSoup是一个可以在内存中直接解析HTML内容的工具,提供了类似于DOM操作HTML页面的方式直接对页面中的各种内容进行处理,比较适用于对页面进行更加完成的资源文件的加载和处理。

要使用JSoup,当然我们首先需要去其官网:“https://jsoup.org/”或者Maven的阿里云镜像“http://maven.aliyun.com/nexus/#welcome”下载并导入到当前项目中即可使用。


利用JSoup获取页面的资源将会更加的方便,代码如下:


public List<String> getResources(String url) {

List<String> urls = new ArrayList<String>();

try {

Document doc = Jsoup.connect(url).get();

// 获取页面中的所有超链接

Elements links = doc.select("a[href]");

//Elements pngs = doc.select("img[src$=.png]");

//Elements images = doc.getElementsByTag("img");

// 获取页面中的所有图片资源

Elements images = doc.select("img[src]");

// 获取页面中的所有导入的资源

Elements imports = doc.select("link[href]");

for (Element e: links) {

urls.add(e.absUrl("href"));

}

for (Element e: images) {

urls.add(e.absUrl("src"));

}

for (Element e: imports) {

urls.add(e.absUrl("href"));

}

} catch (IOException e) {

e.printStackTrace();

}

return urls;

}

 

上述的代码可以将页面中常见的资源的绝对URL地址全部获取到,剩下的资源下载的部分直接利用前面的Java代码即可,JSoup定位元素的方式与JavaScript操作页面元素或者是Selenium定位页面元素的方式非常类似。关于JSoup操作HTML页面响应的具体使用方法,各位读者可以直接参考网址:“http://www.open-open.com/jsoup/”。


当我们获取到页面的所有资源进行下载后,整个过程才是相对完整地模拟了性能测试过程。其实,对于页面资源文件的处理,一方面是为了模拟真实的浏览器访问情况,统计到相对准确的响应时间。更重要的一方面也是为了让服务器承担正常的负载,包括处理静态资源的负载,而不是单纯处理一些最基本的GET和POST请求。这个方面便是与基于接口的功能测试不太一样的地方,请各位读者一定引起重视。

 

思考练习



1.请使用原生Java的正则表达式或JSoup完成对Phpwind的其它页面进行资源解析和下载。

2.请对截止目前的关于接口测试和性能测试的代码进行重构,保持结构上的简洁。








为了答谢大家对蜗牛学院的支持,蜗牛学院将会定期对大家免费发放干货,敬请关注蜗牛学院的官方微信。


20181009_153045_341.jpg




   
版权所有,转载本站文章请注明出处:蜗牛笔记, http://www.woniunote.com/article/247
上一篇: Python 网络爬虫实战直播课,限额30人,先到先得~
下一篇: 双十二为了防止大家“剁手”,西安校区竟……
提示:登录后添加有效评论可享受积分哦!
2596433003   2021-03-09 21:19:52
       
66666
最新文章
    最多阅读
      特别推荐
      回到顶部