解析HTML文件
这里有两个为了查找A HREF来解析HTML文件方法——一个麻烦的方法和一个简单的方法。
如果你选择麻烦的方法,你将使用Java的StreamTokenizer类创建你自己的解析规则。使用这些技术,你必须为StreamTokenizer对象指定单词和空格,接着去掉<和>符号来查找标签,属性,在标签之间分割文字。太多的工作要做。
简单的方法是使用内置的ParserDelegator类,一个HTMLEditorKit.Parser抽象类的子类。这些类在Java文档中没有完善的文档。使用ParserDelegator有三个步骤:首先为你的URL创建一个InputStreamReader对象,接着创建一个ParserCallback对象的实例,最后创建一个ParserDelegator对象的实例并调用它的public方法parse():
UrlTreeNode newnode = new UrlTreeNode(url); // Create the data node InputStream in = url.openStream(); // Ask the URL object to create an input stream InputStreamReader isr = new InputStreamReader(in); // Convert the stream to a reader DefaultMutableTreeNode treenode = addNode(parentnode, newnode); SpiderParserCallback cb = new SpiderParserCallback(treenode); // Create a callback object ParserDelegator pd = new ParserDelegator(); // Create the delegator pd.parse(isr,cb,true); // Parse the stream isr.close(); // Close the stream parse()接受一个InputStreamReader,一个ParseCallback对象实例和一个指定CharSet标签是否忽略的标志。parse()方法接着读和解码HTML文件,每次完成解码一个标签或者HTML元素后调用ParserCallback对象的方法。
在示例代码中,我实现了ParserCallback作为Spider的一个内部类,这样就允许ParseCallback访问Spider的方法和属性。基于ParserCallback的类可以覆盖下面的方法:
■ handleStartTag():当遇到起始HTML标签时调用,比如>A <
■ handleEndTag():当遇到结束HTML标签时调用,比如>/A<
■ handleSimpleTag():当遇到没有匹配结束标签时调用
■ handleText():当遇到标签之间的文字时调用
在示例代码中,我覆盖了handleSimpleTag()以便我的代码可以处理HTML的BASE和IMG标签。BASE标签告诉当处理相关的URL引用时使用什么URL。如果没有BASE标签出现,那么当前URL就用来处理相关的引用。HandleSimpleTag()接受三个参数,一个HTML.Tag对象,一个包含所有标签属性的MutableAttributeSet,和在文件中的相应位置。我的代码检查标签来判断它是否是一个BASE对象实例,如果是则HREF属性被提取出来并保存在页面的数据节点中。这个属性以后在处理链接站点的URL地址中被用到。每次遇到IMG标签,页面图片数就被更新。
我覆盖了handleStartTag以便程序可以处理HTML的A和TITLE标签。方法检查t参数是否是一个事实上的A标签,如果是则HREF属性将被提取出来。
fixHref()被用作清理大量的引用(改变反斜线为斜线,添加缺少的结束斜线),链接的URL通过使用基础URL和引用创建URL对象来处理。接着递归调用searchWeb()来处理链接。如果方法遇到TITLE标签,它就清除存储最后遇到文字的变量以便标题的结束标记具有正确的值(有时网页的title标签之间没有标题)。
我覆盖了handleEndTag()以便HTML的TITLE结束标记可以被处理。这个结束标记指出前面的文字(存在lastText中)是页面的标题文字。这个文字接着存在页面的数据节点中。因为添加标题信息到数据节点中将改变树中数据节点的显示,nodeChanged()方法必须被调用以便树可以更新。
我覆盖了handleText()方法以便HTML页面的文字可以根据被搜索的任意关键字或者短语来检查。HandleText()接受一个包含一个子符数组和该字符在文件中位置作为参数。HandleText()首先将字符数组转换成一个String对象,在这个过程中全部转换为大写。接着在搜索列表中的每个关键字/短语根据String对象的indexof()方法来检查。如果indexof()返回一个非负结果,则关键字/短语在页面的文字中显示。如果关键字/短语被显示,匹配被记录在匹配列表的节点中,统计数据被更新:
public class SpiderParserCallback extends HTMLEditorKit.ParserCallback {
/**
* Inner class used to html handle parser callbacks
*/
public class SpiderParserCallback extends HTMLEditorKit.ParserCallback {
/** URL node being parsed */
private UrlTreeNode node;
/** Tree node */
private DefaultMutableTreeNode treenode;
/** Contents of last text element */
private String lastText = "";
/**
* Creates a new instance of SpiderParserCallback
* @param atreenode search tree node that is being parsed */
public SpiderParserCallback(DefaultMutableTreeNode atreenode) {
treenode = atreenode; node = (UrlTreeNode)treenode.getUserObject();
}
/** * Handle HTML tags that don‘t have a start and end tag * @param t HTML tag * @param a HTML attributes * @param pos Position within file */ public void handleSimpleTag(HTML.Tag t,
MutableAttributeSet a, int pos)
{ if(t.equals(HTML.Tag.IMG))
{ node.addImages(1); return; }
if(t.equals(HTML.Tag.BASE)) { Object value = a.getAttribute(HTML.Attribute.HREF);
if(value != null) node.setBase(fixHref(value.toString())); } }
/**
* Take care of start tags
* @param t HTML tag
* @param a HTML attributes
* @param pos Position within file */ public void handleStartTag(HTML.Tag t,
MutableAttributeSet a,
int pos) { if(t.equals(HTML.Tag.TITLE)) {
lastText=""; return;
}
if(t.equals(HTML.Tag.A))
{
Object value = a.getAttribute(HTML.Attribute.HREF); if(value != null) { node.addLinks(1); String href = value.toString(); href = fixHref(href); try{ URL referencedURL = new URL(node.getBase(),href); searchWeb(treenode, referencedURL.getProtocol()+"://"+referencedURL.getHost()+referencedURL.getPath()); } catch (MalformedURLException e)
{ messageArea.append(" Bad URL encountered : "+href+"\n\n"); return; } } } } /** * Take care of start tags * @param t HTML tag * @param pos Position within file
*/ public void handleEndTag(HTML.Tag t, int pos)
{ if(t.equals(HTML.Tag.TITLE) && lastText != null) { node.setTitle(lastText.trim()); DefaultTreeModel tm = (DefaultTreeModel)searchTree.getModel();
tm.nodeChanged(treenode);
}
}
/**
* Take care of text between tags, check against keyword list for matches, if * match found, set the node match status to true * @param data Text between tags * @param pos position of text within Webpage */ public void handleText(char[] data, int pos) {
lastText = new String(data); node.addChars(lastText.length()); String text = lastText.toUpperCase(); for(int i = 0; i < keywordList.length; i++) { if(text.indexOf(keywordList) >= 0) { if(!node.isMatch()) { sitesFound++; updateStats(); } node.setMatch(keywordList); return; } } }
}
|
引用 报告 回复 |
admin
管理员    UID 1 精华 0 积分 0 帖子 418 阅读权限 200 注册 2007-5-8 状态 离线
|
|
2、处理和补全URL
当遇到相关页面的链接,你必须在它们基础URL上创建完整的链接。基础URL可能通过BASE标签在页面中明确的定义,或者暗含在当前页面的链接中。Java的URL对象为你解决这个问题提供了构造器,提供了根据它的链接结构创建相似的。 URL(URL context, String spec)接受spec参数的链接和context参数的基础链接。如果spec是一个相关链接,构建器将使用context来创建一个完整引用的URL对象。URL它推荐URL遵循严格的(Unix)格式。使用反斜线,在Microsoft Windows中,而不是斜线,将是错误的引用。如果spec或者context指向一个目录(包含index.html或default.html),而不是一个HTML文件,它必须有一个结束斜线。fixHref()方法检查这些引用并且修正它们:
public static String fixHref(String href) {
String newhref = href.replace(‘\\‘, ‘/‘); // Fix sloppy Web references
int lastdot = newhref.lastIndexOf(‘.‘);
int lastslash = newhref.lastIndexOf(‘/‘); if(lastslash > lastdot) { if(newhref.charAt(newhref.length()-1) != ‘/‘) newhref = newhref+"/"; // Add missing /
} return newhref;
}
3、 控制递归
searchWeb()开始是为了搜索用户指定的起始Web地址而被调用的。它接着在遇到HTML链接时调用自身。这形成了深度优先搜索的基础,也带来了两种问题。首先非常危险的内存/堆栈溢出问题将因为太多的递归调用而产生。如果出现环形的引用,这个问题就将发生,也就是说,一个页面链接另外一个链接回来的连接,这是WWW中常见的事情。为了预防这种现象,searchWeb()检查搜索树(通过urlHasBeenVisited()方法)来确定是否引用的页面已经存在。如果已经存在,这个链接将被忽略。如果你选择实现一个没有搜索树的蜘蛛,你仍然必须维护一个以访问站点的列表(在Vector或数组中)以便你可以判断是否你正在重复访问站点。
递归的第二个问题来自深度优先的搜索和WWW的结构。根据选择的入口,深度优先的搜索在初始页面的初始链接在完成处理以前造成大量的递归调用。这就造成了两种不需要的结果:首先内存/堆栈溢出可能发生,第二被搜索过的页面可能很久才被从初始入口众多的结果中删除。为了控制这些,我为蜘蛛添加了最大搜索深度设置。用户可以选择可以达到的深度等级(链接到链接到链接),当遇到每个链接时,当前深度通过调用depthLimitExceeded()方法进行检查。如果达到限制,链接就被忽略。测试仅仅检查JTree中节点的级别。
示例程序也增加了站点限制,用户来指定,可以在特定数目的URL被检查以后停止搜索,这样确保程序可以最后停止!站点限制通过一个简单的数字计数器sitesSearched来控制,这个数字每次调用searchWeb()后都被更新和检查。
4、UrlTreeNode和UrlNodeRenderer
UrlTreeNode和UrlNodeRenderer是用来在SpiderControl用户界面中创建JTree中个性化的树节点的类。UrlTreeNode包含每个搜索过的站点钟的URL信息和统计数据。UrlTreeNode以作为用户对象属性的标准DefaultMutableTreeNode对象形式存储在JTree中。数据包括节点中跟踪关键字出现的能力,节点的URL,节点的基础URL,链接的数量,图片的数量和字符的个数,以及节点是否符合搜索规则。
UrlTreeNodeRenderer是DefaultTreeCellRenderer界面的实现。UrlTreeNodeRenderer使节点包含匹配关键字显示为蓝色。UrlTreeNodeRenderer也为JtreeNodes加入了个性化的图标。个性化的显示通过覆盖getTreeCellRendererComponent()方法(如下)实现。这个方法在树中创建了一个Component对象。大部分的Component属性通过子类来进行设置,UrlTreeNodeRenderer改变了文字的颜色(前景色)和图标:
public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(
tree, value, sel, expanded, leaf, row, hasFocus); UrlTreeNode node = (UrlTreeNode)(((DefaultMutableTreeNode)value).getUserObject()); if (node.isMatch()) // Set color setForeground(Color.blue); else setForeground(Color.black);
if(icon != null) // Set a custom icon { setOpenIcon(icon); setClosedIcon(icon); setLeafIcon(icon); } return this; }
5、 总结
这篇文章向你展示了如何创建网络蜘蛛和控制它的用户界面。用户界面使用JTree来跟踪蜘蛛的进展和记录访问过的站点。当然,你也可以使用Vector来记录访问过的站点和使用一个简单的计数器来显示进展。其他增强可以包含通过数据库记录关键字和站点的接口,增加通过多个入口搜索的能力,用大量或者很少的文字内容来显现站点,以及为搜索引擎提供同义搜索的能力。
这篇文章中展示的Spider类使用递归调用搜索程序,当然,一个新蜘蛛的独立线程可以在遇到每个链接时开始。这样的好处是允许链接远程URL并发执行,提高速度。然而记住那些叫做DefaultMutableTreeNode的JTree对象,不是线程安全的,所以程序员必须自己实现同步。
|
|
|