使用JRex来获取经过浏览器渲染的HTML_jrexhtl-程序员宅基地

技术标签: exception  浏览器  import  string  搜索  javascript  url  

http://www.benjysbrain.com/misc/Render/index.html
           
  我最近做一个网页数据挖掘的项目,这个项目在网络上寻找特定类型的图片。我过去使用HTML Parser来解析HTML并寻找IMG和OBJECT标签(tag)。但有时候,我发现使用原始的HTML并不合适。特别是img的src属性是用JavaScript运行时产生的:
    <IMG SRC="" name="image" id="image">
    <SCRIPT language="JavaScript" type="text/JavaScript">
      <!--
 document.getElementById('image').src = "abc.jpg" ;
      //-->
    </script> 
   我想找一款开源的工具包能够让我访问执行过JavaScript的渲染过的HTML。我没有找到合适的工具因此考虑在我自己的应用中嵌入Mozilla Gecko引擎。但是我后来放弃了这个方法因为为嵌入一个浏览器来创建一个开发环境并学习太费时间了。
   后来我发现了C. N. Medapp的JRex,Gecko的一个java封装。使用JRex,我写了一个java程序来访问页面并等待JavaScript被执行。然后遍历经过浏览器渲染和重构的HTML的DOM。在上面的例子,src属性的值是abc.jpg。
  
   上手

   我需要一个简单的例子来学习使用JRex。很幸运的找到了Dietrich Kappe的How to REALLY do Page Preview in Java with Embedded HTML Rendering
(http://blogs.pathf.com/agileajax/2007/01/how_to_really_d.html)
   这个例子让我知道怎么在java里加载Gecko。这之后,我使用JRex和org.w3c.dom的API,在加上一些实验,开发了一个抽取IMG和OBJECT标签的程序。
   我follow了Kappe的建议——使用JRE而不是JDK来运行JRex程序。当我使用JDK是,有些DLL找不到。我相信有办法来解决这个问题,但是用JRE来运行程序也没什么问题。
   JRex Mailing List(http://jrex.mozdev.org/list.html)是关于JRex的好地方。

   一个例子
   我创建了一个java类Render。pageParse()方法使用JRex来打开一个页眉并且等待它被加载。然后它调用一个递归的方法doTree()来遍历DOM。对于每一个标签,doElement()方法被调用,然后这个标签的子结点都递归的调研这个方法。当一个表情处理完了,doTagEnd方法被调用。

   在Render里,doElement()打印标签,忽略所有的属性,比如<IMG SRC="xyz.gif"> 导致<IMG>被打印。doTagEnd()方法只是打印</IMG>。

   考虑这个例子:
<html>
 <head>
 <TITLE>Simple Page</TITLE>
 </head>
   <body>
    <table>
      <tr><td></td><td></td></tr>
    </table>
   </body>
</html>

Render程序的输出是:
<HTML>
<HEAD>
<TITLE>
</TITLE>
</HEAD>
<BODY>
<TABLE>
<TBODY>
<TR>
<TD>
</TD>
<TD>
</TD>
</TR>
</TBODY>
</TABLE>
</BODY>
</HTML>

注意当DOM被构建,遗漏的<TBODY>被补上了。
*********************说明***********************
如果没有用过JRex,请看看http://www.pathf.com/blogs/2007/01/how_to_really_d/,对这篇文章也翻译了一部分,请参考我的博客文章。
************************************************
Render的源代码:
package com.benjysbrain.htmlgrab ;

/**
   Render - This object is a wrapper for JRex, the Java library that
   allows a Java application to embed the Mozilla Gecko browser.  It
   uses JRex to load a page and then act on the DOM that Gecko
   constructs.  The intent of Render is to access the DOM after a page
   is loaded and JavaScript has been applied for web data extraction
   projects.
   <p>
   Subclass this object and override the
   <i>doElement(org.w3c.dom.Element element)</i> and
   <i>doTagEnd(org.w3c.dom.Element element)</i> methods to do some real
   work.  In the base class, doElement() prints the tag name and
   doTagEnd() prints a closing version of the tag.
   <p>
   Thanks to Dietrich Kappe for his JRex
   <A HREF="http://blogs.pathf.com/agileajax/2007/01/how_to_really_d.html">
   article.</a>  See my <A HREF="http://www.benjysbrain.com/misc/Render">
   article</a> for more details.  Thanks to Jason Baumgartner for the
   tip on how to disable JRex logging of debug information.
   <p>
   Copyright (c) 2007 by Ben E. Cline.  This code is presented as a teaching
   aid.  No warranty is expressed or implied.
   <p>
   http://www.benjysbrain.com/
   @author Benjy Cline
*/

import org.mozilla.jrex.* ;
import org.mozilla.jrex.ui.* ;
import org.mozilla.jrex.window.* ;
import org.mozilla.jrex.navigation.* ;
import org.mozilla.jrex.event.progress.* ;
import org.w3c.dom.* ;
import java.lang.Exception.* ;
import javax.swing.*;
import java.net.*;

public class Render
   implements org.mozilla.jrex.event.progress.ProgressListener {

   String url ;  // The page to be processed.

   // These variables can be used in subclasses and are created from
   // url.  baseURL can be used to construct the absolute URL of the
   // relative URL's in the page.  hostBase is just the http://host.com/
   // part of the URL and can be used to construct the full URL of
   // URLs in the page that are site relative, e.g., "/xyzzy.jpg".
   // Variable host is set to the host part of url, e.g., host.com.

   String baseURL ;
   String hostBase ;
   String host ;

   // The JRexCanvas is the main browser component.  The WebNavigator
   // is used to access the DOM.

   JRexCanvas canvas = null ;
   WebNavigation navigation = null ;

   // An event handler sets "done" to true when the document is loaded.

   boolean done = false ;

   /**
      Create a Render object with a target URL.
   */

   public Render(String URL) {
      url = URL ;
   }

   /**  Load the given URL in Gecko.  When the page is loaded,
 recurse on the DOM and call doElement()/doTagEnd() for
 each Element node.  Execution can hang if the page causes a
 window to be popped up.  Return false on error.
   */

   public boolean parsePage() {

      // Parse the URL and build baseURL and hostURL for use by doElement()
      // and doTagEnd().

      URI uri = null ;
      try {
  uri = new URI(url) ;
      }
      catch(Exception e) {
  System.out.println(e) ;
  return false ;
      }

      String path = uri.getPath() ;
      baseURL = "http://" + uri.getHost() + path + "/" ;
      hostBase = "http://" + uri.getHost() ;
      host = uri.getHost() ;

      // Start up JRex/Gecko.

      try {
  JRexFactory.getInstance().startEngine();
      }
      catch (Exception e) {
  System.err.println("Unable to start up JRex Engine.");
  e.printStackTrace();
  return false ;
      }

      // Get a window manager and put the browser in a Swing frame.
      // Based on Dietrich Kappe's code.

      JRexWindowManager winManager=(JRexWindowManager)
  JRexFactory.getInstance().getImplInstance(JRexFactory.WINDOW_MANAGER);
      winManager.create(JRexWindowManager.SINGLE_WINDOW_MODE);
      JPanel panel = new JPanel();
      JFrame frame = new JFrame();
      frame.getContentPane().add(panel);
      winManager.init(panel);

      // Get the JRexCanvas, set Render to handle progress events so
      // we can determine when the page is loaded, and get the
      // WebNavigator object.

      canvas = (JRexCanvas) winManager.getBrowserForParent(panel);
      canvas.addProgressListener(this) ;
      navigation = canvas.getNavigator() ;

      // Load and process the page.

      try {
  navigation.loadURI(url, WebNavigationConstants.LOAD_FLAGS_NONE,
       null, null, null);

  // Swing magic.
 
  frame.setSize(640, 480);
  frame.setVisible(false);

  // Check if the DOM has loaded every two seconds.

  while(!done) {
     Thread.sleep(2000) ;
  }
 
  // Get the DOM and recurse on its nodes.

  Document doc = navigation.getDocument() ;
  Element ex = doc.getDocumentElement() ;
  doTree((Node) ex) ;
      }
      catch(Exception e) {
  System.out.println("Trouble walking DOM:  " + e) ;
  return false ;
      }

      return true ;
   }

   /**
      Recurse the DOM starting with Node node.  For each Node of
      type Element, call doElement() with it and recurse over its
      children.  The Elements refer to the HTML tags, and the children
      are tags contained inside the parent tag.
   */

   public void doTree(Node node) {
      if(node instanceof Element) {
  Element element = (Element) node ;

  // Visit tag.

  doElement(element) ;

  // Visit all the children, i.e., tags contained in this tag.

  NodeList nl = element.getChildNodes() ;
  if(nl == null) return ;
  int num = nl.getLength() ;
  for(int i=0; i<num; i++)
     doTree(nl.item(i)) ;

  // Process the end of this tag.

  doTagEnd(element) ;
      }
   }

   /**
      Simple doElement() to print the tag name of the Element.  Override
      to do something real.
   */

   public void doElement(Element element) {
      System.out.println("<" + element.getTagName() + ">") ;
   }

   /**
      Simple doTagEnd() to print the closing tag of the Element.
      Override to do something real.
   */

   public void doTagEnd(Element element) {
      System.out.println("</" + element.getTagName() + ">") ;     
   }

   // org.mozilla.jrex.event.progress.ProgressListener methods.
   // onStateChange() seems the best place to watch for the
   // completion of the loading of the DOM.

   /** Noop */
   public void onLinkStatusChange(ProgressEvent event) {   }
   /** Noop */
   public void onLocationChange(ProgressEvent event) {   }
   /** Noop */
   public void onProgressChange(ProgressEvent event) {   }
   /** Noop */
   public void onSecurityChange(ProgressEvent event) {   }

   /** onStateChange is invoked several times when DOM loading is
       complete.  Set the done flag the first time.
   */

   public void onStateChange(ProgressEvent event) {
      if(!event.isLoadingDocument()) {
  if(done) return ;
  done = true ;
      }
   }

   /** Noop */
   public void onStatusChange(ProgressEvent event) {   }

   /**
      Main:  java com.benjysbrain.htmlgrab.Render [url].  Run
      JRex on the given page, wait for the page to load, and
      traverse the DOM, printing tag names only.
   */

    public static void main(String[] args) {
       String url ="http://www.cnn.com" ;
       if(args.length == 1) url = args[0] ;
       Render p = new Render(url) ;
       p.parsePage() ;
       System.exit(0) ;
    }
}

为了在windows运行main函数,需要把JRex.jar放到classpath下,并设置另个-D
-Djrex.dom.enable=true
-Djrex.gre.path=%JREX_GRE_PATH%  (说明,比如我的是-Djrex.gre.path=C:/jrex/jrex_gre)
这里%JREX_GRE_PATH%变量指向JRex GRE。如果你想访问cnn之外的页面,可以设置第一个参数为url

实际应用中,你可以重写doElement和doT方法来提取信息。为了取得标签的属性,首先运行boolean方法Element对象的hasAttributes()方法。如果这个标签有属性,则返回true。你可以使用getAttributes方法来获得一个NamedNodeMap对象,然后访问标签的属性。被NamedNodeMap引用的对象包括属性/值的对。getNodeName得到名字,getNodeValue得到属性值。

不足和问题
Render是使用JRex的一个简单例子,但不是全部。我在挖掘网页时使用Render的一个子类,它工作的很好,但是我测试的例子都是很正常的网页。
我使用一个事件监听器来判断页面是否加载完毕。Render的parsePage方法每过两秒就检测一下doneflag。如果页眉不能加载,就会死循环。
还有当它加载嵌入的浏览器时,浏览器窗口会显示出来,直到加载成功。我没有考虑这个问题因为在我的挖掘任务中不需要浏览器窗口。

 

 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fancyerII/article/details/4190632

智能推荐

JavaScript学习笔记_curry函数未定义-程序员宅基地

文章浏览阅读343次。五种原始的变量类型1.Undefined--未定义类型 例:var v;2.String -- ' '或" "3.Boolean4.Number5.Null--空类型 例: var v=null;Number中:NaN -- not a number非数本身是一个数字,但是它和任何数字都不相等,代表非数,它和自己都不相等判断是不是NaN不能用=_curry函数未定义

兑换码编码方案实践_优惠券编码规则-程序员宅基地

文章浏览阅读1.2w次,点赞2次,收藏17次。兑换码编码设计当前各个业务系统,只要涉及到产品销售,就离不开大大小小的运营活动需求,其中最普遍的就是兑换码需求,无论是线下活动或者是线上活动,都能起到良好的宣传效果。兑换码:由一系列字符组成,每一个兑换码对应系统中的一组信息,可以是优惠信息(优惠券),也可以是相关奖品信息。在实际的运营活动中,要求兑换码是唯一的,每一个兑换码对应一个优惠信息,而且需求量往往比较大(实际上的需求只有预期_优惠券编码规则

c语言周林答案,C语言程序设计实训教程教学课件作者周林ch04结构化程序设计课件.ppt...-程序员宅基地

文章浏览阅读45次。C语言程序设计实训教程教学课件作者周林ch04结构化程序设计课件.ppt* * 4.1 选择结构程序设计 4.2 循环结构程序设计 4.3 辅助控制语句 第四章 结构化程序设计 4.1 选择结构程序设计 在现实生活中,需要进行判断和选择的情况是很多的: 如果你在家,我去拜访你 如果考试不及格,要补考 如果遇到红灯,要停车等待 第四章 结构化程序设计 在现实生活中,需要进行判断和选择的情况..._在现实生活中遇到过条件判断的问

幻数使用说明_ioctl-number.txt幻数说明-程序员宅基地

文章浏览阅读999次。幻数使用说明 在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情。 因为设备都是特定的,这里也没法说。关键在于怎样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径 。 命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,利_ioctl-number.txt幻数说明

ORB-SLAM3 + VScode:检测到 #include 错误。请更新 includePath。已为此翻译单元禁用波浪曲线_orb-slam3 include <system.h> 报错-程序员宅基地

文章浏览阅读399次。键盘按下“Shift+Ctrl+p” 输入: C++Configurations,选择JSON界面做如下改动:1.首先把 “/usr/include”,放在最前2.查看C++路径,终端输入gcc -v -E -x c++ - /usr/include/c++/5 /usr/include/x86_64-linux-gnu/c++/5 /usr/include/c++/5/backward /usr/lib/gcc/x86_64-linux-gnu/5/include /usr/local/_orb-slam3 include 报错

「Sqlserver」数据分析师有理由爱Sqlserver之十-Sqlserver自动化篇-程序员宅基地

文章浏览阅读129次。本系列的最后一篇,因未有精力写更多的入门教程,上篇已经抛出书单,有兴趣的朋友可阅读好书来成长,此系列主讲有理由爱Sqlserver的论证性文章,希望读者们看完后,可自行做出判断,Sqlserver是否真的合适自己,目的已达成。渴望自动化及使用场景笔者所最能接触到的群体为Excel、PowerBI用户群体,在Excel中,我们知道可以使用VBA、VSTO来给Excel带来自动化操作..._sqlsever 数据分析

随便推点

智慧校园智慧教育大数据平台(教育大脑)项目建设方案PPT_高校智慧大脑-程序员宅基地

文章浏览阅读294次,点赞6次,收藏4次。教育智脑)建立学校的全连接中台,对学校运营过程中的数据进行处理和标准化管理,挖掘数据的价值。能:一、原先孤立的系统聚合到一个统一的平台,实现单点登录,统一身份认证,方便管理;三、数据共享,盘活了教育大数据资源,通过对外提供数。的方式构建教育的通用服务能力平台,支撑教育核心服务能力的沉淀和共享。物联网将学校的各要素(人、机、料、法、环、测)全面互联,数据实时。智慧校园解决方案,赋能教学、管理和服务升级,智慧教育体系,该数据平台具有以下几大功。教育大数据平台底座:教育智脑。教育大数据平台,以中国联通。_高校智慧大脑

编程5大算法总结--概念加实例_算法概念实例-程序员宅基地

文章浏览阅读9.5k次,点赞2次,收藏27次。分治法,动态规划法,贪心算法这三者之间有类似之处,比如都需要将问题划分为一个个子问题,然后通过解决这些子问题来解决最终问题。但其实这三者之间的区别还是蛮大的。贪心是则可看成是链式结构回溯和分支界限为穷举式的搜索,其思想的差异是深度优先和广度优先一:分治算法一、基本概念在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两_算法概念实例

随笔—醒悟篇之考研调剂_考研调剂抑郁-程序员宅基地

文章浏览阅读5.6k次。考研篇emmmmm,这是我随笔篇章的第二更,原本计划是在中秋放假期间写好的,但是放假的时候被安排写一下单例模式,做了俩机试题目,还刷了下PAT的东西,emmmmm,最主要的还是因为我浪的很开心,没空出时间来写写东西。  距离我考研结束已经快两年了,距离今年的考研还有90天左右。  趁着这个机会回忆一下青春,这一篇会写的比较有趣,好玩,纯粹是为了记录一下当年考研中发生的有趣的事。  首先介绍..._考研调剂抑郁

SpringMVC_class org.springframework.web.filter.characterenco-程序员宅基地

文章浏览阅读438次。SpringMVC文章目录SpringMVC1、SpringMVC简介1.1 什么是MVC1.2 什么是SpringMVC1.3 SpringMVC的特点2、HelloWorld2.1 开发环境2.2 创建maven工程a>添加web模块b>打包方式:warc>引入依赖2.3 配置web.xml2.4 创建请求控制器2.5 创建SpringMVC的配置文件2.6 测试Helloworld2.7 总结3、@RequestMapping注解3.1 @RequestMapping注解的功能3._class org.springframework.web.filter.characterencodingfilter is not a jakart

gdb: Don‘t know how to run. Try “help target“._don't know how to run. try "help target".-程序员宅基地

文章浏览阅读4.9k次。gdb 远程调试的一个问题:Don't know how to run. Try "help target".它在抱怨不知道怎么跑,目标是什么. 你需要为它指定target remote 或target extended-remote例如:target extended-remote 192.168.1.136:1234指明target 是某IP的某端口完整示例如下:targ..._don't know how to run. try "help target".

c语言程序设计教程 郭浩志,C语言程序设计教程答案杨路明郭浩志-程序员宅基地

文章浏览阅读85次。习题 11、算法描述主要是用两种基本方法:第一是自然语言描述,第二是使用专用工具进行算法描述2、c 语言程序的结构如下:1、c 语言程序由函数组成,每个程序必须具有一个 main 函数作为程序的主控函数。2、“/*“与“*/“之间的内容构成 c 语言程序的注释部分。3、用预处理命令#include 可以包含有关文件的信息。4、大小写字母在 c 语言中是有区别的。5、除 main 函数和标准库函数以..._c语言语法0x1e