创建智能网络蜘蛛
——如何使用Java网络对象和HTML对象(翻译)
作者:Mark O. Pendergast
原文:http://www.javaworld.com/javaworld/jw-11-2004/jw-1101-spider.html
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
摘要
你是否想过创建自己的符合特定标准的网站数据库呢?网络蜘蛛,有时也称为网络爬虫,是一些根据网络链接从一个网站到另外一个网站,检查内容和记录位置的程序。商业搜索站点使用网络蜘蛛丰富它们的数据库,研究人员可以使用蜘蛛获得相关的信息。创建自己的蜘蛛搜索的内容、主机和网页特征,比如文字密度和内置的多媒体内容。这篇文章将告诉你如何使用Java的HTML和网络类来创建你自己的功能强大的网络蜘蛛。
这篇文章将介绍如何在标准Java网络对象的基础上创建一个智能的网络蜘蛛。蜘蛛的核心是一个基于关键字/短语标准和网页特征进行深入网络搜索的递归程序。搜索过程在图形上类似于JTree结构。我主要介绍的问题,例如处理相关的URL,防止循环引用和监视内存/堆栈使用。另外,我将介绍再访问和分解远程网页中如何正确是用Java网络对象。
l 蜘蛛示例程序
示例程序包括用户界面类SpiderControl、网络搜索类Spider,两个用作创建JTree显示结果的类UrlTreeNode和UrlNodeRenderer,和两个帮助验证用户界面中数字输入的类IntegerVerifier和VerifierListener。文章末尾的资源中有完整代码和文档的琏接。
SpiderControl界面由三个属性页组成,一个用来设置搜索参数,另一个显示结果搜索树(JTree),第三个显示错误和状态信息,如图1
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype>
图1 搜索参数属性页
搜索参数包括访问网站的最大数量,搜索的最大深度(链接到链接到链接),关键字/短语列表,搜索的顶级主机,起始网站或者门户。一旦用户输入了搜索参数,并按下开始按钮,网络搜索将开始,第二个属性页将显示搜索的进度。
图2 搜索树
一个Spider类的实例以独立进程的方式执行网络搜索。独立进程的使用是为了SpiderControl模块可以不断更新搜索树显示和处理停止搜索按钮。当Spider运行时,它不断在第二个属性页中为JTree增加节点(UrlTreeNode)。包含关键字和短语的搜索树节点以蓝色显示(UrlNodeRenderer)。
当搜索完成以后,用户可以查看站点的统计,还可以用外部浏览器(默认是位于Program Files目录的Internet Explorer)查看站点。统计包括关键字出现次数,总字符数,总图片数和总链接数。
l Spider类
Spider类负责搜索给出起点(入口)的网络,一系列的关键字和主机,和搜索深度和大小的限制。Spider继承了Thread,所以可以以独立线程运行。这允许SpiderControl模块不断更新搜索树显示和处理停止搜索按钮。
构造方法接受包含对一个空的JTree和一个空的JtextArea引用的搜索参数。JTree被用作创建一个搜索过程中的分类站点记录。这样为用户提供了可见的反馈,帮助跟踪Spdier循环搜索的位置。JtextArea显示错误和过程信息。
构造器将参数存放在类变量中,使用UrlNodeRenderer类初始化显示节点的JTree。直到SpiderControl调用run()方法搜索才开始。
run()方法以独立的线程开始执行。它首先判断入口站点是否是一个Web引用(以http,ftp或者www开始)或是一个本地文件引用。它接着确认入口站点是否具有正确的符号,重置运行统计,接着调用searchWeb()开始搜索:
public void run()
{
DefaultTreeModel treeModel = (DefaultTreeModel)searchTree.getModel(); // get our model
DefaultMutableTreeNode root = (DefaultMutableTreeNode)treeModel.getRoot();
String urllc = startSite.toLowerCase();
if(!urllc.startsWith("http://") && !urllc.startsWith("ftp://") &&
!urllc.startsWith("www."))
{
startSite = "file:///"+startSite; // Note you must have 3 slashes !
}
else // Http missing ?
if(urllc.startsWith("www."))
{
startSite = "http://"+startSite; // Tack on http://
}
startSite = startSite.replace('\\', '/'); // Fix bad slashes
sitesFound = 0;
sitesSearched = 0;
updateStats();
searchWeb(root,startSite); // Search the Web
messageArea.append("Done!\n\n");
}
searchWeb()是一个接受搜索树父节点和搜索Web地址参数的递归方法。searchWeb()首先检查给出的站点是否已被访问和未被执行的搜索深度和站点。SearchWeb()接着允许SpiderControl运行(更新界面和检查停止搜索按钮是否按下)。如果所有正常,searchWeb()继续,否则返回。
在searchWeb()开始读和解析站点以前,它首先检验基于站点创建的URL对象是否具有正确的类型和主机。URL协议被检查来确认它是一个HTML地址或者一个文件地址(不必搜索mailto:和其他协议)。接着检查文件扩展名(如果当前有)来确认它是一个HTML文件(不必解析pdf或者gif文件)。一旦这些工作完成,通过isDomainOk()方法检查根据用户指定的列表检查主机:
...URL url = new URL(urlstr); // Create the URL object from a string.
String protocol = url.getProtocol(); // Ask the URL for its protocol
if(!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("file"))
{
messageArea.append(" Skipping : "+urlstr+" not a http site\n\n");
return;
}
String path = url.getPath(); // Ask the URL for its path
int lastdot = path.lastIndexOf("."); // Check for file extension
if(lastdot > 0)
{
String extension = path.substring(lastdot); // Just the file extension
if(!extension.equalsIgnoreCase(".html") && !extension.equalsIgnoreCase(".htm"))
return; // Skip everything but html files
}
if(!isDomainOk(url))
{
messageArea.append(" Skipping : "+urlstr+" not in domain list\n\n");
return;
}
这里,searchWeb()公平的确定它是否有值得搜索的URL,接着它为搜索树创建一个新节点,添加到树中,打开一个输入流解析文件。下面的章节涉及很多关于解析HTML文件,处理相关URL和控制递归的细节。
l 解析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的类可以覆盖下面的方法:
n handleStartTag():当遇到起始HTML标签时调用,比如>A <
n handleEndTag():当遇到结束HTML标签时调用,比如>/A<
n handleSimpleTag():当遇到没有匹配结束标签时调用
n 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[i]) >= 0)
{
if(!node.isMatch())
{
sitesFound++;
updateStats();
}
node.setMatch(keywordList[i]);
return;
}
}
}
}
l 处理和补全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;
}
l 控制递归
searchWeb()开始是为了搜索用户指定的起始Web地址而被调用的。它接着在遇到HTML链接时调用自身。这形成了深度优先搜索的基础,也带来了两种问题。首先非常危险的内存/堆栈溢出问题将因为太多的递归调用而产生。如果出现环形的引用,这个问题就将发生,也就是说,一个页面链接另外一个链接回来的连接,这是WWW中常见的事情。为了预防这种现象,searchWeb()检查搜索树(通过urlHasBeenVisited()方法)来确定是否引用的页面已经存在。如果已经存在,这个链接将被忽略。如果你选择实现一个没有搜索树的蜘蛛,你仍然必须维护一个以访问站点的列表(在Vector或数组中)以便你可以判断是否你正在重复访问站点。
递归的第二个问题来自深度优先的搜索和WWW的结构。根据选择的入口,深度优先的搜索在初始页面的初始链接在完成处理以前造成大量的递归调用。这就造成了两种不需要的结果:首先内存/堆栈溢出可能发生,第二被搜索过的页面可能很久才被从初始入口众多的结果中删除。为了控制这些,我为蜘蛛添加了最大搜索深度设置。用户可以选择可以达到的深度等级(链接到链接到链接),当遇到每个链接时,当前深度通过调用depthLimitExceeded()方法进行检查。如果达到限制,链接就被忽略。测试仅仅检查JTree中节点的级别。
示例程序也增加了站点限制,用户来指定,可以在特定数目的URL被检查以后停止搜索,这样确保程序可以最后停止!站点限制通过一个简单的数字计数器sitesSearched来控制,这个数字每次调用searchWeb()后都被更新和检查。
l UrlTreeNode和UrlNodeRenderer
UrlTreeNode和UrlNodeRenderer是用来在SpiderControl用户界面中创建JTree中个性化的树节点的类。UrlTreeNode包含每个搜索过的站点钟的URL信息和统计数据。UrlTreeNode以作为用户对象属性的标准DefaultMutableTreeNode对象形式存储在JTree中。数据包括节点中跟踪关键字出现的能力,节点的URL,节点的基础URL,链接的数量,图片的数量和字符的个数,以及节点是否符合搜索规则。
UrlTreeNodeRenderer是DefaultTreeCellRenderer界面的实现。UrlTreeNodeRenderer使节点包含匹配关键字显示为蓝色。UrlTreeNodeRenderer也为JtreeNodes加入了个性化的图标。个性化的显示通过覆盖getTreeCellRendererComponent()方法(如下)实现。这个方法在树中创建了一个Component对象。大部分的Component属性通过子类来进行设置,UrlTreeNodeRenderer改变了文字的颜色(前景色)</
分享到:
相关推荐
网络蜘蛛,有时也称为网络爬虫,是一些根据网络链接...商业搜索站点使用网络蜘蛛丰富它们的数据库,研究人员可以使用蜘蛛获得相关的信息。创建自己的蜘蛛搜索的内容、主机和网页特征,比如文字密度和内置的多媒体内容。
为了增强用户体验,大蜘蛛可能还设计了易于使用的界面和自定义设置。用户可以根据自己的需求调整扫描级别,选择全盘扫描、快速扫描或只针对特定区域进行扫描。同时,定期更新功能会自动下载最新的病毒库,无需用户...
Java网络爬虫(蜘蛛)源码 Java网络爬虫(蜘蛛)源码 Java网络爬虫(蜘蛛)源码 Java网络爬虫(蜘蛛)源码 Java网络爬虫(蜘蛛)源码 Java网络爬虫(蜘蛛)源码 Java网络爬虫(蜘蛛)源码 Java网络爬虫(蜘蛛)源码 Java网络爬虫...
【面向对象实验——蜘蛛纸牌】是一个以Java编程语言实现的桌面游戏项目,它基于面向对象的编程思想,旨在提供一个美观且功能完整的蜘蛛纸牌游戏体验。在本实验中,我们将深入探讨以下几个核心知识点: 1. **面向...
Java网络爬虫(蜘蛛)源码,Java网络爬虫(蜘蛛)源码Java网络爬虫(蜘蛛)源码Java网络爬虫(蜘蛛)源码Java网络爬虫(蜘蛛)源码Java网络爬虫(蜘蛛)源码Java网络爬虫(蜘蛛)源码Java网络爬虫(蜘蛛)源码Java网络爬虫(蜘蛛)源码...
本资源提供了一个基于Java实现的网络蜘蛛程序及其源码,适用于学习和研究网络爬虫技术。这个程序依赖于Apache Commons HttpClient v3.0库,这是一个强大的HTTP客户端库,为Java开发者提供了丰富的功能来执行HTTP请求...
Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+...
(Java毕业设计)Java网络爬虫(蜘蛛)(Java毕业设计)Java网络爬虫(蜘蛛)(Java毕业设计)Java网络爬虫(蜘蛛)(Java毕业设计)Java网络爬虫(蜘蛛)(Java毕业设计)Java网络爬虫(蜘蛛)(Java毕业设计)Java网络爬虫(蜘蛛)(Java...
1. **面向对象编程**:Java是一种面向对象的语言,因此在实现蜘蛛纸牌时,我们首先会创建不同的类来代表游戏中的各个元素。例如,`Card`类表示单张卡牌,包含卡的数值和花色;`Deck`类用于管理卡牌堆;`Tableau`类...
Java提供了内置的`Thread`类和`ExecutorService`接口来创建和管理线程。线程池(ThreadPool)是管理线程资源的有效方式,可以避免过多线程导致的系统资源浪费。 2. **URL类**:Java的`java.net.URL`类用于表示统一...
- 初读课文,概括故事的主要人物——蜘蛛和将军,整体感知故事情节。 - 精读课文,逐步引导学生理解威灵顿将军的心理变化和蜘蛛的顽强行为。如通过标记关键词,理解“几乎”、“失败”、“可怜”、“倒霉”等词语...
《蜘蛛纸牌——Java语言实现解析》 蜘蛛纸牌,这款经典的桌面游戏,以其独特的玩法和挑战性,深受广大玩家喜爱。如今,我们有机会通过Java编程语言来复现这一经典,深入理解游戏背后的逻辑和算法。本文将围绕“蜘蛛...
Java源码实现这样的网络爬虫可能使用到的库有Jsoup(用于HTML解析)、Jsoup-Connect(简化HTTP请求)和Apache Tika(用于多媒体内容识别)。此外,对于大规模爬取,可能会使用到线程池(如Java的ExecutorService)以...
《网络蜘蛛Java编程指南》是一本深入探讨网络爬虫技术的专业书籍,主要针对使用Java语言进行网络数据抓取的开发者。网络蜘蛛,也称为网络爬虫或网络机器人,是自动遍历互联网并抓取网页信息的程序。这些程序在大数据...
【Java课程设计报告——蜘蛛纸牌】是一份关于面向对象程序设计的实践项目,目标是使用Java语言实现蜘蛛纸牌游戏。蜘蛛纸牌是一款流行的单人纸牌游戏,其主要功能模块包括游戏界面的设计、难度等级设定、游戏主功能、...
——————————————————————————————————————— 这是一个使用java实现的简单小游戏,蜘蛛纸牌,模仿了Windows操作系统的经典游戏。 ————————————————————...
【标题】中的“JSP源码——[搜索链接]Java网络爬虫(蜘蛛)源码_zhizhu.zip”指的是一个包含JSP源代码的压缩文件,专门用于实现基于Java的网络爬虫程序。JSP(JavaServer Pages)是Java平台上的动态网页技术,它允许...
通过阅读和理解这些源代码,你可以了解到如何使用Java来实现网络爬虫的各个部分,以及如何将它们组合成一个完整的系统。同时,这也是一个很好的实践机会,可以让你深入理解HTTP协议、HTML解析和并发编程等概念。 ...