网络蜘蛛即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 }