htmlunit 执行 javascript 时,不下载整个页面只返回url

来源:互联网 发布:mac怎么用校园网 编辑:程序博客网 时间:2024/06/07 07:06
htmlunit 简介:
htmlunit 是一款开源的 java 页面分析工具,启动 htmlunit 之后,底层会启动一个无界面浏览器,用户可以指定浏览器类型:firefox、ie 等,如果不指定,默认采用 INTERNET_EXPLORER_7:
WebClient webClient = new WebClient(BrowserVersion.FIREFOX_3_6);

通过简单的调用:
HtmlPage page = webClient.getPage(url);
即可得到页面的 HtmlPage 表示,然后通过:
InputStream is = targetPage.getWebResponse().getContentAsStream()
即可得到页面的输入流,从而得到页面的源码,这对做网络爬虫的项目来说,很有用。
当然,也可以从 page 中得更多的页面元素。

很重要的一点是,HtmlUnit 提供对执行 javascript 的支持:
page.executeJavaScript(javascript)
执行 js 之后,返回一个 ScriptResult 对象,通过该对象可以拿到执行 js 之后的页面等信息。默认情况下,内部浏览器在执行 js 之后,将做页面跳转,跳转到执行 js 之后生成的新页面,如果执行 js 失败,将不执行页面跳转。

htmlunit 执行 js 的大致过程如下:


从图中可以看出,htmlunit 执行js时,会将整个页面 download 下来,而很多时候,我们执行 js,只是因为需要执行后后生成的 url,不必要的频繁页面 download 不但会增加程序运行时长,也会加重网络负载。有下面两种方案可以完成这个需求:

1). 第一种方法是拿到这个 url 之后,将其返回,但代码的调用层次较深,如果修改源码的话,需要修改的地方可能较多,实现起来可能有一定的复杂性和难度。
2). 第二种方法是,生成一个伪 response,而不是去真正获取页面的 response,用来构造所有的新 page。该方法具有代码改动小,实现方便的特点。

因第一种方法对源码的修改大,实现起来也比较困难,这里给出第二种方法的实现:

查看源码可以发现,在:

com.gargoylesoftware.htmlunit.javascript.host.location.java 类中,有这样一个方法:

[java] view plain copy
  1. public void jsxSet_href(final String newLocation) throws IOException {  
  2.     final HtmlPage page = (HtmlPage) getWindow(getStartingScope()).getWebWindow().getEnclosedPage();  
  3.     if (newLocation.startsWith(JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {  
  4.         final String script = newLocation.substring(11);  
  5.         page.executeJavaScriptIfPossible(script, "new location value"1);  
  6.         return;  
  7.     }  
  8.     try {  
  9.         final URL url = page.getFullyQualifiedUrl(newLocation);  
  10.         final URL oldUrl = page.getWebResponse().getWebRequest().getUrl();  
  11.         if (url.sameFile(oldUrl) && !StringUtils.equals(url.getRef(), oldUrl.getRef())) {  
  12.             // If we're just setting or modifying the hash, avoid a server hit.  
  13.             jsxSet_hash(newLocation);  
  14.             return;  
  15.         }  
  16.   
  17.         final WebWindow webWindow = getWindow().getWebWindow();  
  18.         webWindow.getWebClient().download(webWindow, ""new WebRequest(url), "JS set location");              
  19.     }  
  20.     catch (final MalformedURLException e) {  
  21.         LOG.error("jsxSet_location('" + newLocation + "') Got MalformedURLException", e);  
  22.         throw e;  
  23.     }  
  24. }  
第 18 行,就是去 download 页面,download 方法如下:

[java] view plain copy
  1. public void download(final WebWindow requestingWindow, final String target,  
  2.     final WebRequest request, final String description) {  
  3.     final WebWindow win = resolveWindow(requestingWindow, target);  
  4.     final URL url = request.getUrl();  
  5.     boolean justHashJump = false;  
  6.     if (win != null) {  
  7.         final Page page = win.getEnclosedPage();  
  8.         if (page instanceof HtmlPage && !((HtmlPage) page).isOnbeforeunloadAccepted()) {  
  9.             return;  
  10.         }  
  11.        <strong> final URL current = page.getWebResponse().getWebRequest().getUrl();</strong>  
  12.         if (url.sameFile(current) && !StringUtils.equals(current.getRef(), url.getRef())) {  
  13.             justHashJump = true;  
  14.         }  
  15.     }  
  16.   
  17.     // verify if this load job doesn't already exist  
  18.     for (final LoadJob loadJob : loadQueue_) {  
  19.         if (loadJob.response_ == null) {  
  20.             continue;  
  21.         }  
  22.         final WebRequest otherRequest = loadJob.response_.getWebRequest();  
  23.         final URL otherUrl = otherRequest.getUrl();  
  24.         // TODO: investigate but it seems that IE considers query string too but not FF  
  25.         if (url.getPath().equals(otherUrl.getPath())  
  26.             && url.getHost().equals(otherUrl.getHost())  
  27.             && url.getProtocol().equals(otherUrl.getProtocol())  
  28.             && url.getPort() == otherUrl.getPort()  
  29.             && request.getHttpMethod() == otherRequest.getHttpMethod()) {  
  30.             return// skip it;  
  31.         }  
  32.     }  
  33.   
  34.     final LoadJob loadJob;  
  35.     if (justHashJump) {  
  36.         loadJob = new LoadJob(win, target, url);  
  37.     }  
  38.     else {  
  39.         try {  
  40.           final WebResponse response = loadWebResponse(request);  
  41.           loadJob = new LoadJob(requestingWindow, target, response);  
  42.         }  
  43.         catch (final IOException e) {  
  44.             throw new RuntimeException(e);  
  45.         }  
  46.     }  
  47.     loadQueue_.add(loadJob);  
  48. }  
第40, 41行,拿到一个页面的 response,然后根据该 response 生成一个 LoadJob 对象,放入loadQueue_ 队列,后续将从队列中取出该 LoadJob 对象,完成生成新页面并加载至浏览器的工作。我们只要修改这里 response 的生成方式,思路如下:

如果当前线程是第一次执行该 download 方法,就不对代码做修改,让其生成一个真正的 response,然后,将该 response 对象保存起来,待该线程后续再执行 js 进入该方法,不再生成 response 对象,而是将之前保存起来的 response 拿出来直接使用,并修改对应的 url 为执行 js 之后生成的 url 即可:

response.getWebRequest().setUrl(request.getUrl());

js 执行完成之后,返回的 ScriptResult  对应的 url ,就是执行 js 之后生成的 url 了,但如果去拿页面的源码的话,会得到 ”错误“ 的数据,这是因为我们每次都用了同一个 response,而不是 url 页面对应的 url 。因为我们的初衷就是得到正确的 url ,而不去 download 整个页面,所以这种 ”错误“ 不会影响我们的程序。

0 0