网络蜘蛛即Web Spider,是一个很形象的名字。把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛。网络蜘蛛是通过网页的链接地址来寻找网页,从 网站某一个页面(通常是首页)开始,读取网页的内容,找到在网页中的其它链接地址,然后通过这些链接地址寻找下一个网页,这样一直循环下去,直到把这个网 站所有的网页都抓取完为止。如果把整个互联网当成一个网站,那么网络蜘蛛就可以用这个原理把互联网上所有的网页都抓取下来。

  对于搜索引擎来说,要抓取互联网上所有的网页几乎是不可能的,从目前公布的数据来看,容量最大的搜索引擎也不过是抓取了整个网页数量的百分之 四十左右。这其中的原因一方面是抓取技术的瓶颈,无法遍历所有的网页,有许多网页无法从其它网页的链接中找到;另一个原因是存储技术和处理技术的问题,如 果按照每个页面的平均大小为20K计算(包含图片),100亿网页的容量是100×2000G字节,即使能够存储,下载也存在问题(按照一台机器每秒下载 20K计算,需要340台机器不停的下载一年时间,才能把所有网页下载完毕)。同时,由于数据量太大,在提供搜索时也会有效率方面的影响。因此,许多搜索 引擎的网络蜘蛛只是抓取那些重要的网页,而在抓取的时候评价重要性主要的依据是某个网页的链接深度。

  在抓取网页的时候,网络蜘蛛一般有两种策略:广度优先和深度优先。

   广度优先是指网络蜘蛛会先抓取起始网页中链接的所有网页,然后再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页。这是最常用的方式,因为这个 方法可以让网络蜘蛛并行处理,提高其抓取速度。深度优先是指网络蜘蛛会从起始页开始,一个链接一个链接跟踪下去,处理完这条线路之后再转入下一个起始页, 继续跟踪链接。这个方法有个优点是网络蜘蛛在设计的时候比较容易。两种策略的区别,下图的说明会更加明确。

  由于不可能抓取所有的网页,有些网络蜘蛛对一些不太重要的网站,设置了访问的层数。例如,在上图中,A为起始网页,属于0层,B、C、D、 E、F属于第1层,G、H属于第2层, I属于第3层。如果网络蜘蛛设置的访问层数为2的话,网页I是不会被访问到的。这也让有些网站上一部分网页能够在搜索引擎上搜索到,另外一部分不能被搜索 到。对于网站设计者来说,扁平化的网站结构设计有助于搜索引擎抓取其更多的网页。

  网络蜘蛛在访问网站网页的时候,经常会遇到加密数据和网页权限的问题,有些网页是需要会员权限才能访问。当然,网站的所有者可以通过协议让网 络蜘蛛不去抓取(下小节会介绍),但对于一些出售报告的网站,他们希望搜索引擎能搜索到他们的报告,但又不能完全**的让搜索者查看,这样就需要给网络蜘 蛛提供相应的用户名和密码。网络蜘蛛可以通过所给的权限对这些网页进行网页抓取,从而提供搜索。而当搜索者点击查看该网页的时候,同样需要搜索者提供相应 的权限验证。

 网站与网络蜘蛛

  网络蜘蛛需要抓取网页,不同于一般的访问,如果控制不好,则会引起网站服务器负担过重。去年4月,淘宝 http://www.taobao.com)就因为雅虎搜索引擎的网络蜘蛛抓取其数据引起淘宝网服务器的不稳定。网站是否就无法和网络蜘蛛交流呢?其实 不然,有多种方法可以让网站和网络蜘蛛进行交流。一方面让网站管理员了解网络蜘蛛都来自哪儿,做了些什么,另一方面也告诉网络蜘蛛哪些网页不应该抓取,哪 些网页应该更新。

  每个网络蜘蛛都有自己的名字,在抓取网页的时候,都会向网站标明自己的身份。网络蜘蛛在抓取网页的时候会发送一个请求,这个请求中就有一个字 段为User- agent,用于标识此网络蜘蛛的身份。例如Google网络蜘蛛的标识为GoogleBot,Baidu网络蜘蛛的标识为BaiDuSpider, Yahoo网络蜘蛛的标识为Inktomi Slurp。如果在网站上有访问日志记录,网站管理员就能知道,哪些搜索引擎的网络蜘蛛过来过,什么时候过来的,以及读了多少数据等等。如果网站管理员发 现某个蜘蛛有问题,就通过其标识来和其所有者联系。下面是博客中http://www.blogchina.com)2004年5月15日的搜索引擎访问 日志:

  网络蜘蛛进入一个网站,一般会访问一个特殊的文本文件Robots.txt,这个文件一般放在网站服务器的根目录 下,http://www.blogchina.com/robots.txt。网站管理员可以通过robots.txt来定义哪些目录网络蜘蛛不能访 问,或者哪些目录对于某些特定的网络蜘蛛不能访问。例如有些网站的可执行文件目录和临时文件目录不希望被搜索引擎搜索到,那么网站管理员就可以把这些目录 定义为拒绝访问目录。Robots.txt语法很简单,例如如果对目录没有任何限制,可以用以下两行来描述: 
  User-agent: * 
  Disallow:

  当然,Robots.txt只是一个协议,如果网络蜘蛛的设计者不遵循这个协议,网站管理员也无法阻止网络蜘蛛对于某些页面的访问,但一般的网络蜘蛛都会遵循这些协议,而且网站管理员还可以通过其它方式来拒绝网络蜘蛛对某些网页的抓取。

  网络蜘蛛在下载网页的时候,会去识别网页的HTML代码,在其代码的部分,会有META标识。通过这些标识,可以告诉网络蜘蛛本网页是否需要被抓取,还可以告诉网络蜘蛛本网页中的链接是否需要被继续跟踪。例如:表示本网页不需要被抓取,但是网页内的链接需要被跟踪。

  

  现在一般的网站都希望搜索引擎能更全面的抓取自己网站的网页,因为这样可以让更多的访问者能通过搜索引擎找到此网站。为了让本网站的网页更全 面被抓取到,网站管理员可以建立一个网站地图,即Site Map。许多网络蜘蛛会把sitemap.htm文件作为一个网站网页爬取的入口,网站管理员可以把网站内部所有网页的链接放在这个文件里面,那么网络蜘 蛛可以很方便的把整个网站抓取下来,避免遗漏某些网页,也会减小对网站服务器的负担。

  内容提取

  搜索引擎建立网页索引,处理的对象是文本文件。对于网络蜘蛛来说,抓取下来网页包括各种格式,包括html、图片、doc、pdf、多媒体、 动态网页及其它格式等。这些文件抓取下来后,需要把这些文件中的文本信息提取出来。准确提取这些文档的信息,一方面对搜索引擎的搜索准确性有重要作用,另 一方面对于网络蜘蛛正确跟踪其它链接有一定影响。

  对于doc、pdf等文档,这种由专业厂商提供的软件生成的文档,厂商都会提供相应的文本提取接口。网络蜘蛛只需要调用这些插件的接口,就可以轻松的提取文档中的文本信息和文件其它相关的信息。

  HTML等文档不一样,HTML有一套自己的语法,通过不同的命令标识符来表示不同的字体、颜色、位置等版式,如:、、等,提取文本信息时需 要把这些标识符都过滤掉。过滤标识符并非难事,因为这些标识符都有一定的规则,只要按照不同的标识符取得相应的信息即可。但在识别这些信息的时候,需要同 步记录许多版式信息,例如文字的字体大小、是否是标题、是否是加粗显示、是否是页面的关键词等,这些信息有助于计算单词在网页中的重要程度。同时,对于 HTML网页来说,除了标题和正文以外,会有许多广告链接以及公共的频道链接,这些链接和文本正文一点关系也没有,在提取网页内容的时候,也需要过滤这些 无用的链接。例如某个网站有“产品介绍”频道,因为导航条在网站内每个网页都有,若不过滤导航条链接,在搜索“产品介绍”的时候,则网站内每个网页都会搜 索到,无疑会带来大量垃圾信息。过滤这些无效链接需要统计大量的网页结构规律,抽取一些共性,统一过滤;对于一些重要而结果特殊的网站,还需要个别处理。 这就需要网络蜘蛛的设计有一定的扩展性。

  对于多媒体、图片等文件,一般是通过链接的锚文本(即,链接文本)和相关的文件注释来判断这些文件的内容。例如有一个链接文字为“张曼玉照片 ”,其链接指向一张bmp格式的图片,那么网络蜘蛛就知道这张图片的内容是“张曼玉的照片”。这样,在搜索“张曼玉”和“照片”的时候都能让搜索引擎找到 这张图片。另外,许多多媒体文件中有文件属性,考虑这些属性也可以更好的了解文件的内容。

  动态网页一直是网络蜘蛛面临的难题。所谓动态网页,是相对于静态网页而言,是由程序自动生成的页面,这样的好处是可以快速统一更改网页风格, 也可以减少网页所占服务器的空间,但同样给网络蜘蛛的抓取带来一些麻烦。由于开发语言不断的增多,动态网页的类型也越来越多,如:asp、jsp、php 等。这些类型的网页对于网络蜘蛛来说,可能还稍微容易一些。网络蜘蛛比较难于处理的是一些脚本语言(如VBScript和javascript)生成的网 页,如果要完善的处理好这些网页,网络蜘蛛需要有自己的脚本解释程序。对于许多数据是放在数据库的网站,需要通过本网站的数据库搜索才能获得信息,这些给 网络蜘蛛的抓取带来很大的困难。对于这类网站,如果网站设计者希望这些数据能被搜索引擎搜索,则需要提供一种可以遍历整个数据库内容的方法。

  对于网页内容的提取,一直是网络蜘蛛中重要的技术。整个系统一般采用插件的形式,通过一个插件管理服务程序,遇到不同格式的网页采用不同的插件处理。这种方式的好处在于扩充性好,以后每发现一种新的类型,就可以把其处理方式做成一个插件补充到插件管理服务程序之中。

  更新周期

  由于网站的内容经常在变化,因此网络蜘蛛也需不断的更新其抓取网页的内容,这就需要网络蜘蛛按照一定的周期去扫描网站,查看哪些页面是需要更新的页面,哪些页面是新增页面,哪些页面是已经过期的死链接。

  搜索引擎的更新周期对搜索引擎搜索的查全率有很大影响。如果更新周期太长,则总会有一部分新生成的网页搜索不到;周期过短,技术实现会有一定 难度,而且会对带宽、服务器的资源都有浪费。搜索引擎的网络蜘蛛并不是所有的网站都采用同一个周期进行更新,对于一些重要的更新量大的网站,更新的周期 短,如有些新闻网站,几个小时就更新一次;相反对于一些不重要的网站,更新的周期就长,可能一两个月才更新一次。

  一般来说,网络蜘蛛在更新网站内容的时候,不用把网站网页重新抓取一遍,对于大部分的网页,只需要判断网页的属性(主要是日期),把得到的属性和上次抓取的属性相比较,如果一样则不用更新。


        Spider的实现细节

a. URL 的组织和管理考虑到系统自身的资源和时间有限,Spider程序应尽可能的对链接进行筛选,以保证获取信息的质量和效率。Spider程序对新URL 的选择往往与搜索引擎的类型、目标集合、能够处理信息的类型、资源的限制和是否支持Robots限制协议有关。概括为以下几点: 访问过的和重复的URI排除 文件类型必须被系统处理,不能处理的URL排除 不在目标集合中的排除 被Rohots. txt限制的排除 URL排序也是减轻系统负担的重要手段之一。这就要求计算URL的重要性,如果评估新URI的重要性较高,则会冲掉旧的URL。无论任何情况下,对 Spider而言,一首先访问目标集合中的重要站点都是意义和重要的。但是一个页面的重要性的准确评估只能在分析其内容之后进行。可以根据一个页面链接数 量的多少来评估此页面是否重要;或者对uRL 地址进行解析其中的内容例如以“. com", ". edu. c;n”就较为重要一些;或,或者可以根据页而标题与当前的热点问题是否相近或相关来评定其页面的重要性。决定网站或页面的重要性的因素很多,也根据各个 搜索引擎的侧重点不同而各异,最终的评估方法都依赖于该搜索引擎对于资源获取的要求来决定。影响Spider速度的一种重要因素是DNS查询,为此每个 Spider都要维护一个自己的DNS缓冲。这样每个链接都处于不同的状态,包括:DNS 查询、连接到主机、发送请求、得到响应。这些因素综合起来使得Spider变成一个非常复杂的系统。

b. Spider的遍历规则 页面的遍历主要有两种方式:深度遍历和广度遍历。深度遍历算法可以获得的信息较为集中,信息比较完整,但覆盖面就比较有限,广度遍历算法则刚好相反。

c. Spider实现中的主要问题 虽然Spider的功能很强,但也存在不少的问题:

(1)如果一组URL地址没有被组外URL所链接到,那么Spider就找不到它们。由于spi der不能更新过快(因为网络带宽是有限的,更新过快就会影响其他用户的正常使用),难免有不能及时加入的新网站或新页面。

(2)spider程序在遍历Web时也存在危险,很可能遇到一个环链接而陷入死循环 中。简单的避免方法就是忽略已访问过的URL,或限制网站的遍历深度。

(3) Spider程序时大型搜索引擎中很脆弱的部分,因为它与很多的Wcb报务器、不同的域名服务器打交道,而这些服务完全在系统的控制之外。由于网络上包含 了大量的垃圾信息,Spider很可能会收取这些垃圾信息。一个页而出现问题也很可能引至Spider程序中止、崩溃或其他不可预料的行为。囚此访问 Internet的Spider程序应该设计得非常强壮,充分考虑各种可能遇到的情况,让Spider在遇到各种情况时可以采取相应的处理行为,而不至于 获得一些垃圾信息或者直接就对程序本身造成危害。

Spider构架

发现、搜集网页信息需要有高性能的“网络蜘蛛”程序〔Spider〕去自动地在互联网中搜索信息。一个典型的网络蜘蛛工作的方式:查看一个页面, 并从中找到相关信息,然后它再从该页面的所有链接中出发,继续寻找相关的信息,以此类推。网络蜘蛛在搜索引擎整体结构中的位置如下图所示: 初始化时,网络蜘蛛一般指向一个URL ( Uniform Resource Locator)池。在遍历Internet的过程中,按照深度优先或广度优先或其他启发式算法从URL池中取出若干URL进行处理,同时将未访问的 URL放入URL池中,这样处理直到URL池空为止。对Web文档的索引则根据文档的标题、首段落甚至整个页面内容进行,这取决于搜索服务的数据收集策略。

网络蜘蛛在漫游的过程中,根据页面的标题、头、链接等生成摘要放在索引数据库中。如果是全文搜索,还需要将整个页面的内容保存到本地数据库。网络 蜘蛛为实现其快速地浏览整个互联网,通常在技术上采用抢先式多线程技术实现在网上搜索信息。通过抢先式多线程的使用,你能索引一个基于URL链接的 Web页面,启动一个新的线程跟随每个新的URL链接,索引一个新的URL起点。当然在服务器上所开的线程也不能无限膨胀,需要在服务器的正常运转和快速 收集网页之间找一个平衡点。

在整个搜索引擎工作过程中,整个蜘蛛的数据入口是URL地址,数据出口是Web页仓库。Spide:程序发现URL链接以后,经过Stor处理模 块,将我们所需要的网页数据存储在Web页仓库中,为以后的形成网页快照、网页分析提供基础数据。在Spider程序工作的过程中,发现新的链接,对该链 接进行分析,形成新的搜索地址,作为下一次Spider程序的数据输入。这个过程的实现就是Spider程序的队列管理。

Spider程序的工作过程,简单来讲,就是不断发现新的链接,并对该链接对应的页面分析存储的工程。如下图所示,

一、索引器: 索引器的功能是理解搜索器所搜集的信息,从中抽取出索引项,用于表示文档以及生成文档库的索引表。索引项有客观索引项和内容索引项两种: 客观项:与文档的语意内容无关,如作者名、URL、更新时间、编码、长度、链接流行度(Link Popularity)等等; 内容索引项:是用来反映文档内容的,如关键词及其权重、短语、词、字等等。内容索引项可以分为单索引项和多索引项(或称短语索引项)两种。单索引项对于英 文来讲是英语单词,比较容易提取,因为单词之间有天然的分隔符(空格);对于中文等连续书写的语言,必须采用多索引项,进行词语的切分。索引器可以使用集 中式索引算法或分布式索引算法。当数据量很大时,必须实现实时索引(Real-time Lndexing),否则不能够跟上信息量急剧增加的速度。索引算法对索引器的性能(如大规模峰值查询时的响应速度)有很大的影响。一个搜索引擎的有效性 在很大程度取决于索引的质量。 由于汉文字符多,处理复杂,中文词的处理不容易。索引器中的中文分词技术: 一个分词系统=分词程序+分词词典 (1)最大匹配法MM (2)反向最大匹配法RMM (1)最佳匹配法OM (1)双向扫描法[百度的分词就采用了双向扫描法] 系统关键是:分词精度和分词速度

二、建立索引的方法: 为了加快检索速度,搜索引擎要对Snider程序搜集到的信,、建众倒排索引。 (1)全文索引和部分索引有些搜索引擎对于信息库中的贞面建立全文索引,有些只建立摘要部分索引或者每个段落前面部分的索引。还有些搜索引擎在建立索引 时,要同时考虑超文本的不同标记所表示的含义,如粗体、大字体显示的东西往往比较重要。有些搜索引擎还在建立索引的过程中收集页面中的超链接。这些超链接 反映了收集到的信息之间的空间结构。利用这些结果信息可以提高页面相关度判别时的准确度。 (2)是否过滤无用词由于网页中存在这许多无用(无实际意义)单词,例如“啊”、“的”等。这此词往往不能明确表达该网页信息,所以有些搜索引擎保存一个 无用词汇表,在建立索引时将不对这些词汇建立索引。 (3)是否使用Meta标记中的信息网页中的Meta标记用来标注一些非常显示性的信息。有些网页将页面的关键词等信息放在其中。便于在建立索引的过程中 提高这些词汇的相关度。(4)是否对图像标记中的替换文本(ALT text)或页面中的注解建立索引由于现有的搜索引擎对图像的检索技术还不太成熟,大多数搜索引擎不支持图像的检索。在超文木的结构页面中,图像标记中往 往存放着图像的替换信息。这些信息说明了该图像对应的图像的基本信息。(5)是否支持词干提取技术

三、建立索引的过程: 分析过程对文档进行索引并存储到存储桶中排序过程

Spider处理流程

当一个URL被加入到等待队列中时Spider程序就会开始运行。只要等待队列中有一个网页或Spider程序正在处理一个网页,Spider程序就会继续它的工作。当等待队列为空并且当前没有处理任何网页,Spider程序就会停止它的工作。

Spider程序实现初探

Spider 程序是从网上下载Web页面再对其进行处理,为了提高效率,很显然要采用多线程的方法,几个Spider线程同时并行工作,访问不同的链接。构造 Spider程序有两种方式。第一种是将它设计为递归程序,第二种是将它编写成非递归的程序。递归是在一个方法中调用它本身的程序设计技术。当需要重复做 同样的基本仟务或在处理先前任务时可展现将来的任务信息时,递归是相当实用的。例如下面的代码:

void RecursiveSpider?(String url) {

download URL……

parse URL……

while found each URL

call RecursiveSpider?(found URL) ……

process the page just downloaded……

这段代码查看单独的一个Web页的任务放在一个RecursiveSpide?:方法中。在此,调用RecursiveSiper?方法来访问URL.。 当它发现链接时,该方法调用它自己。递归方法在访问很少的网页时,可以使用。因为当一个递归程序运行时要把每次递归压入堆栈(堆栈是个程序结构,每次调用 一个方法时,将返回地址存入其中)。如果递归程序要运行很多次,堆栈会变得非常大,它可能会耗尽整个堆栈内存而导致程序中止。递归还有个问题是多线程和递 归是不兼容的,因为在这一过程中每一个线程都是自己的堆栈。当一个方法调用它自身时,它们需要使用同一个堆栈。这就是说递归的Spider程序不能使用多 线程。 非递归程序不调用自身,而是采用队列的方法。队列就是排队,要得到程序的处理就必须在队列中排队等待。我们在构造造Spider时就采用该方式。使用非递 归的方法时,给定Spider程序一个要访问的页面,它会将其加入到要访问的站点的队列中去。当Spider发现新的链接时,也会将它们加入到该队列中。 Spider程序会顺序处理队列中的每一个网页。 实际在Spider程序中使用了四个队列; 在Spider程序的构造过程中,有两种方法用于访问队列的管理。一种方法就是基于内存的队列管理。

第二种方法就是基于SQL的队列管理。基于SQL的队列和基于内存的队列都是有效的,在校园网上做实验的结果表明,在系统运行过程中间,如果 Spider 的访问任务随着网页数量增加,基于内存的Spider程序效率会下降。因而,选择基于SQL的队列管理方案来构造本Spider程序。

等待队列: 在这个队列中,URL.等待被Spider程序处理。新发现的URL 被加入到该处理队列:当Spider开始处理URL时,它们被传送到这一队列。当一个 URL被处理后它被移送到错误队列或完成队列: 错误队列: 如果下载某一页面时出现错误,它的URL将被加入该队列。该队列的URL不会再移动到其他队列。被列入该队列的URL将不再会被Spider程序处理。

完成队列: 如果页面的下载没有出现任何错误,则该页面将会被加入完成队列。加入该队列的URL不会再移动到其他队列。同一时刻一个URL只能在一个队列中。其实通俗 的讲就是该URL处于什么状态,URL 状态的变化过程就是程序处理URL的过程。下图说明的一个URL状态的变化过程。 Spider程序会遇到三种连接:内部连接外部连接其他连接一个示例Spider类:

 

Java代码 

 1 import java.awt.*; 
 2 
 3 import java.net.*; 
 4 import java.io.*; 
 5 import java.lang.*; 
 6 import java.util.*; 
 7 
 8 
 9 class node{ 
10 private Object data; 
11 private node next; 
12 private node prev; 
13 public node(Object o){ 
14 data = o; 
15 prev = next = null; 
16 } 
17 public String toString(){ 
18 if(next!=null)return data.toString() + "\n"+ next.toString(); 
19 return data.toString(); 
20 } 
21 public node getNext(){return next;} 
22 public void setNext(node n){next = n;} 
23 public node getPrev(){return prev;} 
24 public void setPrev(node n){prev = n;} 
25 public Object getData(){return data;} 
26 } 

 

 1 class linkedlist{ 
 2 node head; 
 3 node tail; 
 4 public linkedlist(){ 
 5 tail = head = null; 
 6 } 
 7 public String toString(){ 
 8 if(head==null)return "Empty list"; 
 9 return head.toString(); 
10 } 
11 public void insert(Object o){ 
12 if(tail==null){ 
13 head = tail = new node(o); 
14 }else{ 
15 node nn = new node(o); 
16 tail.setNext(nn); 
17 tail=nn; 
18 } 
19 } 
20 public boolean contains(Object o){ 
21 for(node n = head;n!=null;n=n.getNext()){ 
22 if(o.equals(n.getData()))return true; 
23 } 
24 return false; 
25 } 
26 public Object pop(){ 
27 if(head==null)return null; 
28 Object ret = head.getData(); 
29 head = head.getNext(); 
30 if(head==null)tail = null; 
31 return ret; 
32 } 
33 public boolean isEmpty(){ 
34 return head==null; 
35 } 
36 } 

 

 1 class list{ 
 2 protected node tail; 
 3 protected node ptr; 
 4 private boolean stop; 
 5 public list(){ 
 6 ptr=tail=null; 
 7 stop=false; 
 8 } 
 9 public boolean isEmpty(){return tail==null;} 
10 public void reset(){ 
11 stop=false; 
12 ptr=tail; 
13 } 
14 public String toString(){ 
15 if(tail==null)return "Empty list"; 
16 String ret=""; 
17 for(node n = tail.getNext();n!=tail;n=n.getNext())ret+=n.getData().toString()+"\n"; 
18 ret+=tail.getData().toString(); 
19 return ret; 
20 } 
21 public Object get(){ 
22 if(ptr==null)return null; 
23 ptr = ptr.getNext(); 
24 if(ptr==tail.getNext()){ 
25 if(stop)return null; 
26 stop=true; 
27 return tail.getNext().getData(); 
28 } 
29 return ptr.getData(); 
30 } 
31 public void insert(Object o, boolean attail){ 
32 node nn = new node(o); 
33 if(tail==null){ 
34 nn.setNext(nn); 
35    nn.setPrev(nn); 
36    ptr=tail=nn; 
37    return; 
38 } 
39 if(attail){ 
40 tail.getNext().setPrev(nn); 
41    nn.setNext(tail.getNext()); 
42    tail.setNext(nn); 
43    nn.setPrev(tail); 
44    tail=nn; 
45 }else{ 
46    nn.setNext(tail.getNext()); 
47    nn.setPrev(tail); 
48    tail.setNext(nn); 
49    nn.getNext().setPrev(nn); 
50 } 
51 } 
52 public void insert(Object o){} 
53 } 
 1    
 2 class stack extends list{ 
 3 public stack(){super();} 
 4 public void insert(Object o){insert(o, false);} 
 5 } 
 6 class queue extends list{ 
 7 public queue(){super();} 
 8 public void insert(Object o){insert(o, true);} 
 9 public String peek(){ 
10    if(tail==null)return ""; 
11    return tail.getNext().getData().toString(); 
12 } 
13 public Object pop(){ 
14 if(tail==null)return null; 
15 Object ret = tail.getNext().getData(); 
16 if(tail.getNext()==tail){ 
17    tail=ptr=null; 
18 }else{ 
19    if(tail.getNext()==ptr)ptr=ptr.getNext(); 
20    tail.setNext(tail.getNext().getNext()); 
21 } 
22 return ret; 
23 } 
24 } 
 1    
 2    
 3 class hashtable{ 
 4    private Vector table; 
 5    private int size; 
 6    public hashtable(){ 
 7 size = 991; 
 8 table = new Vector(); 
 9 for(int i=0;i<size;i++){ 
10    table.add(new linkedlist()); 
11 } 
12    } 
13    public void insert(Object o){ 
14 int index = o.hashCode(); 
15 index = index % size; 
16 if(index<0)index+=size; 
17 linkedlist ol = (linkedlist)table.get(index); 
18 ol.insert(o); 
19    } 
20    public boolean contains(Object o){ 
21 int index = o.hashCode(); 
22 index = index % size; 
23 if(index<0)index+=size; 
24 return ((linkedlist)(table.get(index))).contains(o); 
25    } 
26    public String toString(){ 
27 String ret =""; 
28 for(int i=0;i<size;i++){ 
29    if(!((linkedlist)(table.get(i))).isEmpty()){ 
30 ret+="\n"; 
31 ret+=table.get(i).toString(); 
32    } 
33 } 
34 return ret; 
35    } 
36 } 
  1 class spider implements Runnable{ 
  2 public queue todo; 
  3 public stack done; 
  4 public stack errors; 
  5 public stack omittions; 
  6 private hashtable allsites; 
  7 private String last=""; 
  8    int maxsites; 
  9    int visitedsites; 
 10    int TIMEOUT; 
 11    String base; 
 12    String []badEndings2 = {"ps", "gz"}; 
 13    String []badEndings3 = {"pdf", "txt", "zip", "jpg", "mpg", "gif", "mov", "tut", "req", "abs", "swf", "tex", "dvi", "bin", "exe", "rpm"}; 
 14    String []badEndings4 = {"jpeg", "mpeg"}; 
 15    
 16    public spider(String starturl, int max, String b){ 
 17 TIMEOUT = 5000; 
 18 base = b; 
 19 allsites = new hashtable(); 
 20 todo = new queue(); 
 21 done = new stack(); 
 22 errors = new stack(); 
 23 omittions = new stack(); 
 24 try{ 
 25    URL u = new URL(starturl); 
 26    todo.insert(u); 
 27 }catch(Exception e){ 
 28    System.out.println(e); 
 29    errors.insert("bad starting url "+starturl+", "+e.toString()); 
 30 } 
 31 maxsites = max; 
 32 visitedsites = 0; 
 33    } 
 34    
 35    /* 
 36    * how many millisec to wait for each page 
 37    */ 
 38    public void setTimer(int amount){ 
 39 TIMEOUT = amount; 
 40    } 
 41    
 42    /* 
 43    * strips the '#' anchor off a url 
 44    */ 
 45    private URL stripRef(URL u){ 
 46 try{ 
 47    return new URL(u.getProtocol(), u.getHost(), u.getPort(), u.getFile()); 
 48 }catch(Exception e){return u;} 
 49    } 
 50    
 51    /* 
 52    * adds a url for future processing 
 53    */ 
 54    public void addSite(URL toadd){ 
 55 if(null!=toadd.getRef())toadd = stripRef(toadd); 
 56 if(!allsites.contains(toadd)){ 
 57    allsites.insert(toadd); 
 58    if(!toadd.toString().startsWith(base)){ 
 59 omittions.insert("foreign URL: "+toadd.toString()); 
 60 return; 
 61    } 
 62    if(!toadd.toString().startsWith("http") && !toadd.toString().startsWith("HTTP")){ 
 63 omittions.insert("ignoring URL: "+toadd.toString()); 
 64 return; 
 65    } 
 66    
 67    String s = toadd.getFile(); 
 68    String last=""; 
 69    String []comp={}; 
 70    if(s.charAt(s.length()-3)=='.'){ 
 71 last = s.substring(s.length()-2); 
 72 comp = badEndings2; 
 73    }else if(s.charAt(s.length()-4)=='.'){ 
 74 last = s.substring(s.length()-3); 
 75 comp = badEndings3; 
 76    }else if(s.charAt(s.length()-5)=='.'){ 
 77 last = s.substring(s.length()-4); 
 78 comp = badEndings4; 
 79    } 
 80    for(int i=0;i<comp.length;i++){ 
 81 if(last.equalsIgnoreCase(comp[i])){//loop through all bad extensions 
 82      omittions.insert("ignoring URL: "+toadd.toString()); 
 83      return; 
 84 } 
 85    } 
 86      
 87    todo.insert(toadd); 
 88 } 
 89    } 
 90    
 91    /* 
 92    * true if there are pending urls and the maximum hasn't been reached 
 93    */ 
 94    public boolean hasMore(){ 
 95 return !todo.isEmpty() && visitedsites<maxsites; 
 96    } 
 97    
 98    /* 
 99    * returns the next site, works like enumeration, will return new values each time 
100    */ 
101    private URL getNextSite(){ 
102 last = todo.peek(); 
103 visitedsites++; 
104 return (URL)todo.pop(); 
105    } 
106    
107    /* 
108    * Just to see what we are doing now... 
109    */ 
110    public String getCurrent(){ 
111 return last; 
112    } 
113    
114    /* 
115    * process the next site 
116    */ 
117    public void doNextSite(){ 
118 URL current = getNextSite(); 
119 if(current==null)return; 
120 try{ 
121    //System.err.println("Processing #"+visitedsites+": "+current); 
122    parse(current); 
123    done.insert(current); 
124 } 
125 catch(Exception e){ 
126    errors.insert("Bad site: "+current.toString()+", "+e.toString()); 
127 } 
128    } 
129    
130    public void run(){ 
131 while(hasMore())doNextSite(); 
132    } 
133    
134    /* 
135    * to print out the internal data structures 
136    */ 
137    public String toString(){return getCompleted()+getErrors();} 
138    private String getErrors(){ 
139 if(errors.isEmpty())return "No errors\n"; 
140 else return "Errors:\n"+errors.toString()+"\nEnd of errors\n"; 
141    } 
142    private String getCompleted(){ 
143 return "Completed Sites:\n"+done.toString()+"\nEnd of completed sites\n"; 
144    } 
145    
146    /* 
147    * Parses a web page at (site) and adds all the urls it sees 
148    */ 
149    private void parse(URL site) throws Exception{ 
150 String source=getText(site); 
151 String title=getTitle(source); 
152 if(title.indexOf("404")!=-1 || 
153    title.indexOf("Error")!=-1 || 
154    title.indexOf("Not Found")!=-1){ 
155    throw new Exception (("404, Not Found: "+site)); 
156 } 
157 int loc, beg; 
158 boolean hasLT=false; 
159 boolean hasSp=false; 
160 boolean hasF=false; 
161 boolean hasR=false; 
162 boolean hasA=false; 
163 boolean hasM=false; 
164 boolean hasE=false; 
165 for(loc=0;loc<source.length();loc++){ 
166    char c = source.charAt(loc); 
167    if(!hasLT){ 
168 hasLT = (c=='<'); 
169    } 
170    
171    //search for "<a " 
172    else if(hasLT && !hasA && !hasF){ 
173 if(c=='a' || c=='A')hasA=true; 
174 else if(c=='f' || c=='F')hasF=true; 
175 else hasLT=false; 
176    }else if(hasLT && hasA && !hasF && !hasSp){ 
177 if(c==' ' || c=='\t' || c=='\n')hasSp=true; 
178 else hasLT = hasA = false; 
179    } 
180    
181    //search for "<frame " 
182    else if(hasLT && hasF && !hasA && !hasR){ 
183 if(c=='r' || c=='R')hasR=true; 
184 else hasLT = hasF = false; 
185    }else if(hasLT && hasF && hasR && !hasA){ 
186 if(c=='a' || c=='A')hasA=true; 
187 else hasLT = hasF = hasR = false; 
188    }else if(hasLT && hasF && hasR && hasA && !hasM){ 
189 if(c=='m' || c=='M')hasM=true; 
190 else hasLT = hasF = hasR = hasA = false; 
191    }else if(hasLT && hasF && hasR && hasA && hasM && !hasE){ 
192 if(c=='e' || c=='E')hasE=true; 
193 else hasLT = hasF = hasR = hasA = hasM = false; 
194    }else if(hasLT && hasF && hasR && hasA && hasM && hasE && !hasSp){ 
195 if(c==' ' || c=='\t' || c=='\n')hasSp=true; 
196 else hasLT = hasF = hasR = hasA = hasM = hasE = false; 
197    } 
198      
199    //found "<frame " 
200    else if(hasLT && hasF && hasR && hasA && hasM && hasE && hasSp){ 
201 hasLT = hasF = hasR = hasA = hasM = hasE = hasSp = false; 
202 beg = loc; 
203 loc = source.indexOf(">", loc); 
204 if(loc==-1){ 
205      errors.insert("malformed frame at "+site.toString()); 
206      loc = beg; 
207 } 
208 else{ 
209      try{ 
210    parseFrame(site, source.substring(beg, loc)); 
211      } 
212      catch(Exception e){ 
213    errors.insert("while parsing "+site.toString()+", error parsing frame: "+e.toString()); 
214      } 
215 } 
216    } 
217    
218    //found "<a " 
219    else if(hasLT && hasA && hasSp && !hasF){ 
220 hasLT = hasA = hasSp = false; 
221 beg = loc; 
222 loc = source.indexOf(">", loc); 
223 if(loc==-1){ 
224      errors.insert("malformed linked at "+site.toString()); 
225      loc = beg; 
226 } 
227 else{ 
228      try{ 
229    parseLink(site, source.substring(beg, loc)); 
230      } 
231      catch(Exception e){ 
232    errors.insert("while parsing "+site.toString()+", error parsing link: "+e.toString()); 
233      } 
234 } 
235    } 
236 } 
237    } 
238      
239    /* 
240    * parses a frame 
241    */ 
242    private void parseFrame(URL at_page, String s) throws Exception{ 
243 int beg=s.indexOf("src"); 
244 if(beg==-1)beg=s.indexOf("SRC"); 
245 if(beg==-1)return;//doesn't have a src, ignore 
246 beg = s.indexOf("=", beg); 
247 if(beg==-1)throw new Exception("while parsing "+at_page.toString()+", bad frame, missing \'=\' after src: "+s); 
248 int start = beg; 
249 for(;beg<s.length();beg++){ 
250    if(s.charAt(beg)=='\'')break; 
251    if(s.charAt(beg)=='\"')break; 
252 } 
253 int end=beg+1; 
254 for(;end<s.length();end++){ 
255    if(s.charAt(beg)==s.charAt(end))break; 
256 } 
257 beg++; 
258 if(beg>=end){//missing quotes... just take the first token after "src=" 
259    for(beg=start+1;beg<s.length() && (s.charAt(beg)==' ');beg++){} 
260    for(end=beg+1;end<s.length() && (s.charAt(beg)!=' ') && (s.charAt(beg)!='>');end++){} 
261 } 
262    
263 if(beg>=end){ 
264    errors.insert("while parsing "+at_page.toString()+", bad frame: "+s); 
265    return; 
266 } 
267    
268 String linkto=s.substring(beg,end); 
269 if(linkto.startsWith("mailto:")||linkto.startsWith("Mailto:"))return; 
270 if(linkto.startsWith("javascript:")||linkto.startsWith("Javascript:"))return; 
271 if(linkto.startsWith("news:")||linkto.startsWith("Javascript:"))return; 
272 try{ 
273    addSite(new URL(at_page, linkto)); 
274    return; 
275 }catch(Exception e1){} 
276 try{ 
277    addSite(new URL(linkto)); 
278    return; 
279 }catch(Exception e2){} 
280 try{ 
281    URL cp = new URL(at_page.toString()+"/index.html"); 
282    System.out.println("attemping to use "+cp); 
283    addSite(new URL(cp, linkto)); 
284    return; 
285 }catch(Exception e3){} 
286 errors.insert("while parsing "+at_page.toString()+", bad frame: "+linkto+", formed from: "+s); 
287    } 
288    
289    /* 
290    * given a link at a URL, will parse it and add it to the list of sites to do 
291    */ 
292    private void parseLink(URL at_page, String s) throws Exception{ 
293 //System.out.println("parsing link "+s); 
294 int beg=s.indexOf("href"); 
295 if(beg==-1)beg=s.indexOf("HREF"); 
296 if(beg==-1)return;//doesn't have a href, must be an anchor 
297 beg = s.indexOf("=", beg); 
298 if(beg==-1)throw new Exception("while parsing "+at_page.toString()+", bad link, missing \'=\' after href: "+s); 
299 int start = beg; 
300 for(;beg<s.length();beg++){ 
301    if(s.charAt(beg)=='\'')break; 
302    if(s.charAt(beg)=='\"')break; 
303 } 
304 int end=beg+1; 
305 for(;end<s.length();end++){ 
306    if(s.charAt(beg)==s.charAt(end))break; 
307 } 
308 beg++; 
309 if(beg>=end){//missing quotes... just take the first token after "href=" 
310    for(beg=start+1;beg<s.length() && (s.charAt(beg)==' ');beg++){} 
311    for(end=beg+1;end<s.length() && (s.charAt(beg)!=' ') && (s.charAt(beg)!='>');end++){} 
312 } 
313    
314 if(beg>=end){ 
315    errors.insert("while parsing "+at_page.toString()+", bad href: "+s); 
316    return; 
317 } 
318    
319 String linkto=s.substring(beg,end); 
320 if(linkto.startsWith("mailto:")||linkto.startsWith("Mailto:"))return; 
321 if(linkto.startsWith("javascript:")||linkto.startsWith("Javascript:"))return; 
322 if(linkto.startsWith("news:")||linkto.startsWith("Javascript:"))return; 
323    
324 try{ 
325    addSite(new URL(at_page, linkto)); 
326    return; 
327 }catch(Exception e1){} 
328 try{ 
329    addSite(new URL(linkto)); 
330    return; 
331 }catch(Exception e2){} 
332 try{ 
333    addSite(new URL(new URL(at_page.toString()+"/index.html"), linkto)); 
334    return; 
335 }catch(Exception e3){} 
336 errors.insert("while parsing "+at_page.toString()+", bad link: "+linkto+", formed from: "+s); 
337    } 
338    
339    /* 
340    * gets the title of a web page with content s 
341    */ 
342    private String getTitle(String s){ 
343 try{ 
344    int beg=s.indexOf("<title>"); 
345    if(beg==-1)beg=s.indexOf("<TITLE>"); 
346    int end=s.indexOf("</title>"); 
347    if(end==-1)end=s.indexOf("</TITLE>"); 
348    return s.substring(beg,end); 
349 } 
350 catch(Exception e){return "";} 
351    } 
352    
353    /* 
354    * gets the text of a web page, times out after 10s 
355    */ 
356    private String getText(URL site) throws Exception 
357    { 
358 urlReader u = new urlReader(site); 
359 Thread t = new Thread(u); 
360 t.setDaemon(true); 
361 t.start(); 
362 t.join(TIMEOUT); 
363 String ret = u.poll(); 
364 if(ret==null){ 
365 throw new Exception("connection timed out"); 
366 }else if(ret.equals("Not html")){ 
367 throw new Exception("Not an HTML document"); 
368 } 
369 return ret; 
370    } 
371    
372    /* 
373    * returns how many sites have been visited so far 
374    */ 
375    public int Visited(){return visitedsites;} 
376 } 
 1 class urlReader implements Runnable{ 
 2    URL site; 
 3    String s; 
 4    public urlReader(URL u){ 
 5 site = u; 
 6 s=null; 
 7    } 
 8    public void run(){ 
 9 try{ 
10    String ret=new String(); 
11    URLConnection u = site.openConnection(); 
12    String type = u.getContentType(); 
13    if(type.indexOf("text")==-1 &&   
14      type.indexOf("txt")==-1 &&   
15      type.indexOf("HTM")==-1 &&   
16      type.indexOf("htm")==-1){ 
17 //System.err.println("bad content type "+type+" at site "+site); 
18 System.out.println("bad content type "+type+" at site "+site); 
19 ret = "Not html"; 
20 return; 
21    } 
22    InputStream in = u.getInputStream(); 
23    BufferedInputStream bufIn = new BufferedInputStream(in); 
24    int data; 
25    while(true){ 
26 data = bufIn.read(); 
27 // Check for EOF 
28 if (data == -1) break; 
29 else ret+= ( (char) data); 
30    } 
31    s = ret; 
32 }catch(Exception e){s=null;} 
33    } 
34    public String poll(){return s;} 
35 } 

 

  1 public class spidergui extends Frame{ 
  2    
  3 private spider s; 
  4 private Color txtColor; 
  5 private Color errColor; 
  6 private Color topColor; 
  7 private Color numColor; 
  8 private Color curColor; 
  9    
 10 public spidergui(spider spi, String title){ 
 11 super(title); 
 12 curColor = new Color(40, 40, 200); 
 13 txtColor = new Color(0, 0, 0); 
 14 errColor = new Color(255, 0, 0); 
 15 topColor = new Color(40, 40, 100); 
 16 numColor = new Color(50, 150, 50); 
 17 s=spi; 
 18 setBounds(0, 0, 800, 600); 
 19 show(); 
 20 toFront(); 
 21 repaint(); 
 22 } 
 23 public void endShow(){ 
 24 System.out.println(s); 
 25 hide(); 
 26 dispose(); 
 27 } 
 28 public void paint(Graphics g){ 
 29 super.paint(g); 
 30 s.todo.reset(); 
 31 s.done.reset(); 
 32 s.errors.reset(); 
 33 s.omittions.reset(); 
 34 String txt; 
 35 Object o; 
 36 g.setColor(curColor); 
 37 g.setFont(new Font("arial", Font.PLAIN, 18)); 
 38 String cur = s.getCurrent(); 
 39 if(cur.length()>80)g.drawString( 
 40    cur.substring(0, 40)+ 
 41    " . . . "+ 
 42    cur.substring(cur.length()-30, cur.length()), 
 43 50, 50); 
 44 else g.drawString(cur, 50, 50); 
 45    
 46 g.setColor(numColor); 
 47 g.setFont(new Font("arial", Font.BOLD, 24)); 
 48 g.drawString(Integer.toString(s.Visited()), 350, 80); 
 49    
 50 g.setFont(new Font("arial", Font.PLAIN, 14)); 
 51 g.setColor(topColor); 
 52 g.drawString("To Do:", 100, 80); 
 53 g.drawString("Completed:", 500, 80); 
 54 g.drawString("Ignored:", 500, 250); 
 55 g.drawString("Errors:", 100, 420); 
 56    
 57 g.setColor(txtColor); 
 58 g.setFont(new Font("arial", Font.PLAIN, 12)); 
 59 for(int i=0;i<23 && (o=s.todo.get())!=null;i++){ 
 60 txt = Integer.toString(i+1) + ": "+o.toString(); 
 61 if(txt.length()>65)g.drawString( 
 62    txt.substring(0, 38) + 
 63    " . . . " + 
 64    txt.substring(txt.length()-18, txt.length()), 
 65 20, 100+13*i); 
 66 else g.drawString(txt, 20, 100+13*i); 
 67 } 
 68 for(int i=0;i<10 && (o=s.done.get())!=null;i++){ 
 69 txt = Integer.toString(i+1) + ": "+o.toString(); 
 70 if(txt.length()>60)g.drawString(txt.substring(0, 57)+"...", 400, 100+13*i); 
 71 else g.drawString(txt, 400, 100+13*i); 
 72 } 
 73 for(int i=0;i<10 && (o=s.omittions.get())!=null;i++){ 
 74 txt = Integer.toString(i+1) + ": "+o.toString(); 
 75 if(txt.length()>60)g.drawString(txt.substring(0, 57)+"...", 400, 270+13*i); 
 76 else g.drawString(txt, 400, 270+13*i); 
 77 } 
 78 g.setColor(errColor); 
 79 for(int i=0;i<10 && (o=s.errors.get())!=null;i++){ 
 80 txt = Integer.toString(i+1) + ": "+o.toString(); 
 81 g.drawString(txt, 20, 440+13*i); 
 82 } 
 83    
 84 } 
 85 public void run(){ 
 86 repaint(); 
 87 while(s.hasMore()){ 
 88 repaint(); 
 89 s.doNextSite(); 
 90 } 
 91 repaint(); 
 92 } 
 93    
 94 public static void main(String []args){ 
 95 int max = 5; 
 96 String site=""; 
 97 String base=""; 
 98 int time=0; 
 99 for(int i=0;i<args.length;i++){ 
100    if(args[i].startsWith("-max=")){ 
101 max=Integer.parseInt(args[i].substring(5,args[i].length())); 
102    } 
103    else if(args[i].startsWith("-time=")){ 
104 time=Integer.parseInt(args[i].substring(6,args[i].length())); 
105    } 
106    else if(args[i].startsWith("-init=")){ 
107 site=args[i].substring(6,args[i].length()); 
108    } 
109    else if(args[i].startsWith("-base=")){ 
110 base=args[i].substring(6,args[i].length()); 
111    } 
112    else if(args[i].startsWith("-help")||args[i].startsWith("-?")){ 
113 System.out.println("additional command line switches:"); 
114 System.out.println("-max=N     : to limit to N sites, default 5"); 
115 System.out.println("-init=URL   : to set the initial site, REQUIRED"); 
116 System.out.println("-base=URL   : only follow url's that start with this"); 
117 System.out.println("         default \"\" (matches all URLs)"); 
118 System.out.println("-time=N   : how many millisec to wait for each page"); 
119 System.out.println("         default 5000 (5 seconds)"); 
120 System.exit(0); 
121    } 
122    else System.err.println("unrecognized switch: "+args[i]+", continuing"); 
123 } 
124 if(site==""){ 
125    System.err.println("No initial site parameter!"); 
126    System.err.println("Use -init=<site> switch to set, or -help for more info."); 
127    System.exit(1); 
128 } 
129    
130 spider spi=new spider(site, max, base); 
131    
132 if(time>0)spi.setTimer(time); 
133    
134 spidergui s = new spidergui(spi, "Spider: "+site); 
135 s.run(); 
136 System.out.println(spi); 
137 } 
138 }

 

另一个实现:

这是一个web搜索的基本程序,从命令行输入搜索条件(起始的URL、处理url的最大数、要搜索的字符串), 
它就会逐个对Internet上的URL进行实时搜索,查找并输出匹配搜索条件的页面。 这个程序的原型来自《java编程艺术》, 
为了更好的分析,站长去掉了其中的GUI部分,并稍作修改以适用jdk1.5。以这个程序为基础,可以写出在互联网上搜索 
诸如图像、邮件、网页下载之类的“爬虫”。 


先请看程序运行的过程:
D:\java>javac SearchCrawler.java(编译)

D:\java>java   SearchCrawler http://127.0.0.1:8080/zz3zcwbwebhome/index.jsp 20 java

Start searching... 
result: 
searchString=java 
http://127.0.0.1:8080/zz3zcwbwebhome/index.jsp 
http://127.0.0.1:8080/zz3zcwbwebhome/reply.jsp 
http://127.0.0.1:8080/zz3zcwbwebhome/learn.jsp 
http://127.0.0.1:8080/zz3zcwbwebhome/download.jsp 
http://127.0.0.1:8080/zz3zcwbwebhome/article.jsp 
http://127.0.0.1:8080/zz3zcwbwebhome/myexample/jlGUIOverview.htm 
http://127.0.0.1:8080/zz3zcwbwebhome/myexample/Proxooldoc/index.html 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=301 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=297 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=291 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=286 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=285 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=284 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=276 
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=272  

又如: 
D:\java>java    SearchCrawler http://www.sina.com 20 java 
Start searching... 
result: 
searchString=java 
http://sina.com 
http://redirect.sina.com/WWW/sinaCN/www.sina.com.cn class=a2 
http://redirect.sina.com/WWW/sinaCN/www.sina.com.cn class=a8 
http://redirect.sina.com/WWW/sinaHK/www.sina.com.hk class=a2 
http://redirect.sina.com/WWW/sinaTW/www.sina.com.tw class=a8 
http://redirect.sina.com/WWW/sinaUS/home.sina.com class=a8 
http://redirect.sina.com/WWW/smsCN/sms.sina.com.cn/ class=a2 
http://redirect.sina.com/WWW/smsCN/sms.sina.com.cn/ class=a3 
http://redirect.sina.com/WWW/sinaNet/www.sina.net/ class=a3


D:\java> 
下面是这个程序的源码 
Java代码 

  1 import java.util.*; 
  2 import java.net.*; 
  3 import java.io.*; 
  4 import java.util.regex.*; 
  5 
  6 // 搜索Web爬行者 
  7 public class SearchCrawler implements Runnable{ 
  8    
  9 /* disallowListCache缓存robot不允许搜索的URL。 Robot协议在Web站点的根目录下设置一个robots.txt文件, 
 10 *规定站点上的哪些页面是限制搜索的。 搜索程序应该在搜索过程中跳过这些区域,下面是robots.txt的一个例子: 
 11 # robots.txt for http://somehost.com/ 
 12    User-agent: * 
 13    Disallow: /cgi-bin/ 
 14    Disallow: /registration # /Disallow robots on registration page 
 15    Disallow: /login 
 16 */ 
 17 
 18 
 19 private HashMap< String,ArrayList< String>> disallowListCache = new HashMap< String,ArrayList< String>>();   
 20 ArrayList< String> errorList= new ArrayList< String>();//错误信息   
 21 ArrayList< String> result=new ArrayList< String>(); //搜索到的结果   
 22 String startUrl;//开始搜索的起点 
 23 int maxUrl;//最大处理的url数 
 24 String searchString;//要搜索的字符串(英文) 
 25 boolean caseSensitive=false;//是否区分大小写 
 26 boolean limitHost=false;//是否在限制的主机内搜索 
 27     
 28 public SearchCrawler(String startUrl,int maxUrl,String searchString){ 
 29    this.startUrl=startUrl; 
 30    this.maxUrl=maxUrl; 
 31    this.searchString=searchString; 
 32 } 
 33 
 34    public ArrayList< String> getResult(){ 
 35        return result; 
 36    } 
 37 
 38 public void run(){//启动搜索线程 
 39         
 40        crawl(startUrl,maxUrl, searchString,limitHost,caseSensitive); 
 41 } 
 42      
 43 
 44     //检测URL格式 
 45 private URL verifyUrl(String url) { 
 46     // 只处理HTTP URLs. 
 47     if (!url.toLowerCase().startsWith("http://")) 
 48       return null; 
 49 
 50     URL verifiedUrl = null; 
 51     try { 
 52       verifiedUrl = new URL(url); 
 53     } catch (Exception e) { 
 54       return null; 
 55     } 
 56 
 57     return verifiedUrl; 
 58 } 
 59 
 60 // 检测robot是否允许访问给出的URL. 
 61 private boolean isRobotAllowed(URL urlToCheck) {   
 62     String host = urlToCheck.getHost().toLowerCase();//获取给出RUL的主机   
 63     //System.out.println("主机="+host); 
 64 
 65     // 获取主机不允许搜索的URL缓存   
 66     ArrayList< String> disallowList =disallowListCache.get(host);   
 67 
 68     // 如果还没有缓存,下载并缓存。   
 69     if (disallowList == null) {   
 70       disallowList = new ArrayList< String>();   
 71       try {   
 72         URL robotsFileUrl =new URL("http://" + host + "/robots.txt");   
 73         BufferedReader reader =new BufferedReader(new InputStreamReader(robotsFileUrl.openStream()));   
 74 
 75         // 读robot文件,创建不允许访问的路径列表。   
 76         String line;   
 77         while ((line = reader.readLine()) != null) {   
 78           if (line.indexOf("Disallow:") == 0) {//是否包含"Disallow:"   
 79             String disallowPath =line.substring("Disallow:".length());//获取不允许访问路径   
 80 
 81             // 检查是否有注释。   
 82             int commentIndex = disallowPath.indexOf("#");   
 83             if (commentIndex != - 1) {   
 84               disallowPath =disallowPath.substring(0, commentIndex);//去掉注释   
 85             }   
 86                
 87             disallowPath = disallowPath.trim();   
 88             disallowList.add(disallowPath);   
 89            }   
 90          }   
 91 
 92         // 缓存此主机不允许访问的路径。   
 93         disallowListCache.put(host, disallowList);   
 94       } catch (Exception e) {   
 95               return true; //web站点根目录下没有robots.txt文件,返回真 
 96       }   
 97     }   
 98 
 99        
100      String file = urlToCheck.getFile();   
101      //System.out.println("文件getFile()="+file); 
102      for (int i = 0; i < disallowList.size(); i++) {   
103        String disallow = disallowList.get(i);   
104        if (file.startsWith(disallow)) {   
105          return false;   
106        }   
107      }   
108    
109      return true;   
110    }   
111    
112    
113    
114     
115    private String downloadPage(URL pageUrl) { 
116       try { 
117          // Open connection to URL for reading. 
118          BufferedReader reader = 
119            new BufferedReader(new InputStreamReader(pageUrl.openStream())); 
120    
121          // Read page into buffer. 
122          String line; 
123          StringBuffer pageBuffer = new StringBuffer(); 
124          while ((line = reader.readLine()) != null) { 
125            pageBuffer.append(line); 
126          } 
127            
128          return pageBuffer.toString(); 
129       } catch (Exception e) { 
130       } 
131    
132       return null; 
133    } 
134    
135    // 从URL中去掉"www" 
136    private String removeWwwFromUrl(String url) { 
137      int index = url.indexOf("://www."); 
138      if (index != -1) { 
139        return url.substring(0, index + 3) + 
140          url.substring(index + 7); 
141      } 
142    
143      return (url); 
144    } 
145    
146    // 解析页面并找出链接 
147    private ArrayList< String> retrieveLinks(URL pageUrl, String pageContents, HashSet crawledList, 
148      boolean limitHost) 
149    { 
150      // 用正则表达式编译链接的匹配模式。 
151 Pattern p=     Pattern.compile("<a\\s+href\\s*=\\s*\"?(.*?)[\"|>]",Pattern.CASE_INSENSITIVE); 
152      Matcher m = p.matcher(pageContents); 
153    
154        
155      ArrayList< String> linkList = new ArrayList< String>(); 
156      while (m.find()) { 
157        String link = m.group(1).trim(); 
158          
159        if (link.length() < 1) { 
160          continue; 
161        } 
162    
163        // 跳过链到本页面内链接。 
164        if (link.charAt(0) == '#') { 
165          continue; 
166        } 
167    
168          
169        if (link.indexOf("mailto:") != -1) { 
170          continue; 
171        } 
172         
173        if (link.toLowerCase().indexOf("javascript") != -1) { 
174          continue; 
175        } 
176    
177        if (link.indexOf("://") == -1){ 
178          if (link.charAt(0) == '/') {//处理绝对地    
179            link = "http://" + pageUrl.getHost()+":"+pageUrl.getPort()+ link; 
180          } else {           
181            String file = pageUrl.getFile(); 
182            if (file.indexOf('/') == -1) {//处理相对地址 
183              link = "http://" + pageUrl.getHost()+":"+pageUrl.getPort() + "/" + link; 
184            } else { 
185              String path =file.substring(0, file.lastIndexOf('/') + 1); 
186              link = "http://" + pageUrl.getHost() +":"+pageUrl.getPort()+ path + link; 
187            } 
188          } 
189        } 
190    
191        int index = link.indexOf('#'); 
192        if (index != -1) { 
193          link = link.substring(0, index); 
194        } 
195    
196        link = removeWwwFromUrl(link); 
197    
198        URL verifiedLink = verifyUrl(link); 
199        if (verifiedLink == null) { 
200          continue; 
201        } 
202    
203        /* 如果限定主机,排除那些不合条件的URL*/ 
204        if (limitHost && 
205            !pageUrl.getHost().toLowerCase().equals( 
206              verifiedLink.getHost().toLowerCase())) 
207        { 
208          continue; 
209        } 
210    
211        // 跳过那些已经处理的链接. 
212        if (crawledList.contains(link)) { 
213          continue; 
214        } 
215    
216         linkList.add(link); 
217      } 
218    
219     return (linkList); 
220    } 
221    
222 // 搜索下载Web页面的内容,判断在该页面内有没有指定的搜索字符串 
223    
224    private boolean searchStringMatches(String pageContents, String searchString, boolean caseSensitive){ 
225         String searchContents = pageContents;   
226         if (!caseSensitive) {//如果不区分大小写 
227            searchContents = pageContents.toLowerCase(); 
228         } 
229    
230        
231      Pattern p = Pattern.compile("[\\s]+"); 
232      String[] terms = p.split(searchString); 
233      for (int i = 0; i < terms.length; i++) { 
234        if (caseSensitive) { 
235          if (searchContents.indexOf(terms[i]) == -1) { 
236            return false; 
237          } 
238        } else { 
239          if (searchContents.indexOf(terms[i].toLowerCase()) == -1) { 
240            return false; 
241          } 
242        }     } 
243    
244      return true; 
245    } 
246    
247      
248    //执行实际的搜索操作 
249    public ArrayList< String> crawl(String startUrl, int maxUrls, String searchString,boolean limithost,boolean caseSensitive ) 
250    {   
251        
252      System.out.println("searchString="+searchString); 
253      HashSet< String> crawledList = new HashSet< String>(); 
254      LinkedHashSet< String> toCrawlList = new LinkedHashSet< String>(); 
255    
256       if (maxUrls < 1) { 
257          errorList.add("Invalid Max URLs value."); 
258          System.out.println("Invalid Max URLs value."); 
259        } 
260      
261        
262      if (searchString.length() < 1) { 
263        errorList.add("Missing Search String."); 
264        System.out.println("Missing search String"); 
265      } 
266    
267        
268      if (errorList.size() > 0) { 
269        System.out.println("err!!!"); 
270        return errorList; 
271        } 
272    
273        
274      // 从开始URL中移出www 
275      startUrl = removeWwwFromUrl(startUrl); 
276    
277        
278      toCrawlList.add(startUrl); 
279      while (toCrawlList.size() > 0) { 
280          
281        if (maxUrls != -1) { 
282          if (crawledList.size() == maxUrls) { 
283            break; 
284          } 
285        } 
286    
287        // Get URL at bottom of the list. 
288        String url = toCrawlList.iterator().next(); 
289    
290        // Remove URL from the to crawl list. 
291        toCrawlList.remove(url); 
292    
293        // Convert string url to URL object. 
294        URL verifiedUrl = verifyUrl(url); 
295    
296        // Skip URL if robots are not allowed to access it. 
297        if (!isRobotAllowed(verifiedUrl)) { 
298          continue; 
299        } 
300    
301        
302        // 增加已处理的URL到crawledList 
303        crawledList.add(url); 
304        String pageContents = downloadPage(verifiedUrl); 
305    
306          
307        if (pageContents != null && pageContents.length() > 0){ 
308          // 从页面中获取有效的链接 
309          ArrayList< String> links =retrieveLinks(verifiedUrl, pageContents, crawledList,limitHost); 
310         
311          toCrawlList.addAll(links); 
312    
313          if (searchStringMatches(pageContents, searchString,caseSensitive)) 
314          { 
315            result.add(url); 
316            System.out.println(url); 
317          } 
318       } 
319    
320        
321      } 
322     return result; 
323    } 
324    
325    // 主函数 
326    public static void main(String[] args) { 
327       if(args.length!=3){ 
328          System.out.println("Usage:java SearchCrawler startUrl maxUrl searchString"); 
329          return; 
330       } 
331      int max=Integer.parseInt(args[1]); 
332      SearchCrawler crawler = new SearchCrawler(args[0],max,args[2]); 
333      Thread search=new Thread(crawler); 
334      System.out.println("Start searching..."); 
335      System.out.println("result:"); 
336      search.start(); 
337       
338    } 
339 }

 

 

posted on 2013-02-03 14:52  烤德  阅读(1843)  评论(0编辑  收藏  举报