毕业设计论文Web资源自动获取技术研究与应用.doc
本 科 生 毕 业 论 文题目: Web资源自动获取技术研究与应用目 录1 绪论11.1研究的背景与意义11.2 本文研究的内容22 网络爬虫的基本介绍22.1 网络爬虫的概述22.2 网络爬虫的搜索策略22.3 分布式网络爬虫使用的关键技术32.3.1 多线程与线程同步32.3.2 Socket套接字协议43 系统功能需求分析53.1 HTTP/HTTPS 页面下载器53.2 页面链接的提取和URL的过滤53.3 URL管理器53.4 URL 转发器63.5 多线程网络爬虫63.6 服务器端与客户端的通信与协调64 系统的设计与实现过程74.1 开发环境与工具74.2 爬虫中服务器端的设计与实现74.2.1功能流程图和代码段及其界面图74.2.2 URL分发器的实现104.2.3 ID生成器的实现114.3 爬虫客户端的设计与实现114.3.1 功能流程图和代码段及其界面图114.3.2 URL转发器的实现134.3.3 URL接收器及其消重的实现144.3.4 恢复采集断点功能的实现154.4 数据库的设计与连接164.5 系统的运行结果和测试分析174.6 下载网页的查看205 总结21参考文献22致谢2222Web资源自动获取技术研究与应用作者:XXX 指导教师:XXX摘 要:Web资源自动获取技术是一种自动提取,分析并过滤网页的程序,也叫网络爬虫。本文通过JAVA实现了一个分布式爬虫程序。论文阐述了分布式网络爬虫实现中采用的关键技术:HTTP/HTTPS 页面下载器,页面链接的提取和URL的过滤,URL管理器URL 转发器,多线程网络爬虫,服务器端与客户端的通信与协调。通过实现这一爬虫程序,可以搜集某一站点的URLs,并将搜集到的URLs存入数据库。最后创建一个网页读取数据库的下载的URL,列表显示出来并有超链接,点击就可以打开下载的指定网页,并在该页面创建一个检索的功能,方便查找。关键词:网络爬虫;JAVA;分布式;多线程;URLResearch and application on Web resources automatic acquisition technologyAuthor: XXX Tutor:XXXAbstract:Automatic web resource acquisition technology is a program used to automaticly extract , analyse and filter web page ,namely the Web crawler. In this paper ,JAVA is used to implement a distributed crawler program. The paper describes key technologies applied in implementing the distributed Web crawler: HTTP/HTTPS page downloader, extract of page links and filter of URLs, URL manager, URL repeater, multi-thread web crawler, communication and negotiation between server and client. The crawler program could collect a website's URLs, and save those URLs to the database. Finally establish a web page to read URL which stored in database,show them in list and create a hypelink ,through clicking open the pages downloaded ,and create a searching function ,which can conveniently search. .Key phrase: Web crawler; JAVA; distribute ; multi-threads;URL1 绪论1.1研究的背景与意义随着互联网的飞速发展,网络上的信息呈爆炸式增长。这使得人们在网上找到所需的信息越来越困难,这种情况下搜索引擎应运而生。搜索引擎搜集互联网上数以亿计的网页,并为每个词建立索引。在建立搜索引擎的过程中,搜集网页是非常重要的一个环节。Web资源自动获取技术程序就是用来搜集网页的程序,也叫爬虫程序。目前已有的一些爬虫是在单机上运行的,信息搜索速度比较有限,由于Web资源的海量性,已无法在一个有限的时间范围内完成一次爬行互联网的任务。分布式的爬虫系统采用多级并行工作,能提高系统的工作效率,缩短爬行时间,具有良好的可扩展性。1.2 本文研究的内容本文着眼于中等规模,力求一个健壮性,可扩展性,效率各方面都很完善的一个高质量的网络爬虫。从 “多机”爬虫这方面详细探讨了分布式网络爬虫的设计与实现,在“多机”爬虫设计中重点讨论了服务器端与客户端爬虫的设计及实现,首先借助功能流程图从总体上对系统进行剖析,然后再对具体细节一一突破:URL分发器,ID生成器,URL转发器,URL接收器及界面动态显示器等。最后创建一个网页读取数据库的下载的URL,列表显示出来并有超链接,点击就可以打开下载的指定网页,并在该页面创建一个检索的功能,方便查找。2 网络爬虫的基本介绍 2.1 网络爬虫的概述网络爬虫 ,其定义有广义和狭义之分。狭义上指遵循标准的 http协议利用超链接和 Web文档检索的方法遍历万维网信息空间的软件程序 ,而广义的定义则是所有能遵循 http协议检索 Web文档的软件都称之为网络爬虫。1网络爬虫是一个功能很强的自动提取网页的程序 ,它为搜索引擎从万维网上下载网页 ,是搜索引擎的重要组成。它通过请求站点上的 HTML文档访问某一站点。它遍历 Web空间,不断从一个站点移动到另一个站点 ,自动建立索引 ,并加入到网页数据库中。网络爬虫进入某个超级文本时 ,它利用 HTML语言的标记结构来搜索信息及获取指向其他超级文本的 URL地址 ,可以完全不依赖用户干预实现网络上的自动爬行和搜索。22.2 网络爬虫的搜索策略在抓取网页的时候 ,目前网络爬虫一般有两种策略:无主题搜索与基于某特定主体的专业智能搜索。其中前者主要包括 :广度优先和深度优先。而本系统采用广度优先。广度优先是指网络爬虫会先抓取起始网页中链接的所有网页 ,然后再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页。这是最常用的方式,因为这个方法可以让网络爬虫并行处理 ,提高其抓取速度。2因为本论文实现的爬虫程序的初衷是尽可能遍历某一站点所有的页面。广度优先算法的实行理论是覆盖更多的节点,所以此爬虫程序选择了广度优先算法。实现的策略是:先获取初始URL对应HTML代码里所有的URLs。然后依次获取这些URLs对应的HTML里的URLs,当这一层所有的URLs都下载解析完后,在获取下一层的信息。通过这种循环的获取方式实现广度优先爬行。如下图1假如a代表初始URL,bcd为以a获取的3个URLs,efg为以b获取的URLs,以此类推。那么这些URLs获取的顺序就是abcdefghijklmnop这样一个顺序。当获取到b的URLs之后,并不会马上去解析这些URLs,而是先解析同b在同一层中的cd对应的URLs。当这一层URLs全部解析完后,再开始下一层URLs。图1 广度优先爬行策略图广度优先算法的等待队列设计如图2所示:图2 广度优先算法的等待队列设计图2.3 分布式网络爬虫使用的关键技术2.3.1 多线程与线程同步所谓多进程,就是让系统(好像)同时运行多个程序。比如,我在Microsoft Word编写本论文的时候,我还打开了一酷狗来播放音乐,偶尔我还会再编辑Word的同时让我的机器执行一个打印任务,而且我还喜欢通过IE从网上下载一个Flash动画。对于我来说,这些操作都是同步进行的,我不需要等一首歌曲放完了再来编辑我的论文。看起来,它们都同时在我的机器上给我工作。事实的真相是,对于一个CPU而言,它在某一个时间点上,只能执行一个程序。CPU不断的在这些程序之间“跳跃”执行。那么,为什么我们看不出任何的中断现象呢?这是因为,相对于我们的感觉,它的速度实在太快了。我们人的感知时间可能以秒来计算。而对于CPU而言,它的时间是以毫秒来计算的,从我们肉眼看来,它们就是一个连续的动作。因此,虽然我们看到的都是一些同步的操作,但实际上,对于计算机而言,它在某个时间点上只能执行一个程序,除非你的计算机是多CPU的。而多线程(Multi-Thread)扩展了多进程(multi-Process)操作的概念,将任务的划分下降到了程序级别,使得各个程序似乎可以在同一个时间内执行多个任务。每个任务称为一个线程,能够同时运行多个线程的程序称为多线程程序。当同时运行的相互独立的线程要共享数据并且要考虑其他线程的状态时,就需要使用一套机制使得这些线程同步,避免在争用资源时发生冲突,甚至发生死锁。JAVA提供了多种机制以实现线程同步。多数JAVA同步是以对象锁定为中心的。JAVA中从Object对象继承来的每个对象都有一个单独的锁。由于JAVA中的每个对象都是从Object继承来的。所以JAVA中的每个对象都有自己的锁。这样使它在共享的线程之间可以相互协调。在JAVA中实现线程同步的另一个方法是通过使用synchronized关键字。JAVA使用synchronized关键字来定义程序中要求线程同步的部分。synchronized关键字实现的基本操作是把每个需要线程同步的部分定义为一个临界区,在临界区中同一时刻只有一个线程被执行。32.3.2 Socket套接字协议“套接字”(Socket)是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。套接字不关心数据的格式,它和底层的TCP/IP协议都只需确保数据到达正确的目的地。套接字的工作很像邮政服务,它们所做的就是将信息分遣到世界各地的计算机系统。Java有非常简单的套接字编程,其中定义有两个类:Socket和ServerSocket,在套接字程序设计中特别重要。如果编写的程序是扮演服务器的角色,它就应该使用ServerSocket;如果程序是连接到服务器的,那么它扮演客户端的角色,就应该使用Socket类。无论是通过子类ServerSocket完成的服务器还是客户端,Socket类只用于最初的开始连接,一旦连接建立,就使用输入和输出流来促进客户端和服务器之间的通信。连接成功后,客户端和服务器之间的区别就完全没意义了。任意一端都可以往套接字读写数据。从套接字得到的结果是一个InputStream以及OutputStream(若使用恰当的转换器,则分别是Reader和Writer),以便将连接作为一个IO流对象对待。一旦客户(程序)申请建立一个套接字连接,ServerSocket就会返回(通过accept()方法)一个对应的服务器端套接字,以便进行直接通信。从此时起,我们就得到了真正的“套接字套接字”连接,可以用同样的方式对待连接的两端,因为它们本来就是相同的。此时可以利用getInputStream()以及getOutputStream()从每个套接字产生对应的InputStream和OutputStream对象,这些数据流必须封装到缓冲区内。3 系统功能需求分析3.1 HTTP/HTTPS 页面下载器该页面下载器借助HTTP/HTTPS协议从给定的URL地址下载网页内容,并将其存入指定路径的文件中。在下载某个链接的网页之前,该下载器应先判断出待下载文件的类型,用以屏蔽掉图片,音频,可执行文件等容量大的文件下载,因为那些文件的下载十分耗时,直接影响到整个系统的运行与性能。除了下载页面内容外,下载器还应记录下载的页面总数,用以最后结果的统计及性能的分析。3.2 页面链接的提取和URL的过滤很明显,要使爬虫得以运行,就应该具有维持爬虫爬行的URL地址。也就是说,除了下载某个URL指定页面的内容外,还应分析页面的HTML代码,从中提取出所有符合条件的相关链接,然后存入URL队列中等待爬虫的获取。然而,并非所有的URL都是待爬行的对象,在存入队列之前应对其进行过滤处理,只保留“内部链接” ,即与初始URL主机名相同的链接,而舍弃外部链接及其他类型的链接。因为如果对URL不加控制,最终将可能造成爬虫陷入不可终止的无限爬行之中,这一点是我们不愿发生的。3.3 URL管理器爬虫的主要工作之一是组织所访问过的和将要访问的站点的列表,这些列表成为作业。URL管理器又称作业管理器,用于管理已爬行过的、等待被爬行的、正在爬行的和因下载出错而停止爬行的作业或URL,即负责URL的提取和装载工作。URL管理器应保证每一个即将装载的URL在作业列表中不含有该URL,即URL去重。除了存储每个URL之外,管理器还应记录及动态地改变它们当前所处的状态,以便爬虫程序能取出合理的URL而得以正常运转。3.4 URL 转发器可以说URL转发器是实现可扩展Spider的关键之处,其主要任务是实现对URL的分配和转发。服务器端的主要工作就是实现对URL的特定转发,将URL根据指定的原则分配给不同的客户端进行爬行;而各个爬虫在每爬取一个网页之前,都要由该转发器做出判断,若属于自己的爬行范围并且该URL未曾爬行过,则由URL管理器将该URL追加到等待作业中,否则进行转发工作,即把该URL发给服务器,由服务器端做出进一步分析来决定转发给哪台爬虫。3.5 多线程网络爬虫该系统实现的爬虫,不仅是分布式的,即多台爬虫并行开启爬行任务,而且应该是多线程的。也就相当于把抓取网页这件事情由以前的一个worker来完成变成现在的许许多多的worker来实现,这些worker之间相互协调,共同维持同一个URL队列,从而可以大大提高抓取的速度及系统的性能。worker变多了,秩序管理就应相应增强,否则将造成对共享数据的破坏,以及worker间混乱的局面。这里的秩序管理也就是线程间的同步,这一点也是实现多线程的关键。3.6 服务器端与客户端的通信与协调要实现可扩展的网络爬虫,首先应该解决的问题就是爬虫与主控端畅通的会话以及在此基础上提供一个融洽、协调的通信环境。当启动会话后,对于客户端提出的每一个请求,服务器都应给出相应的回复以避免客户端长时间的等待。当会话完成时,双方应各自发出指令来正常结束本次通话。首先,打开服务器用以随时响应客户端发出的请求,接着当某台爬虫客户端准备好就绪工作后便发出指令向服务器注册自己的ID号,注册成功后,服务器上记录并显示出该台爬虫的信息并向该客户端返回所分配的ID号及当前就绪态的爬虫总数。其次,当各爬虫均处于就绪态时,由服务器发出开始爬行的命令,从而将初始URL经分发器处理后启动其中某台爬虫首先工作,经过一系列的转发和分发使整个系统处于有序的运行之中。在运行过程中,各客户端可以动态地显示下载页面数、爬行过的URL列表、当前活动的线程数及与服务器间的会话情况,而服务器也可以随时发出爬行中断命令来中断各爬虫的运行以结束本次会话,或者动态地增加爬虫客户端的数目,新增的爬虫能让服务器觉察到并立即予以分配ID和URL、更新爬虫列表并把该消息通知其他爬虫,使整个系统处于新的运转之中。最后,当各爬虫正常结束爬行任务或被中断爬行时,将各自向服务器通报自己的爬行结果包括下载页面总数,下载经历的时间及URL下载速度等。之后,本次会话结束,系统正常退出。4 系统的设计与实现过程4.1 开发环境与工具本次开发环境与工具为:操作系统:Microsoft Windows XP Professional Service Pack 2;开发工具:EClipse JDK1.6.0 SQL server 2005;Tomcat6.0开发语言:Java; PC机: HP Pavilion(AMD CPU 2.01GHz,256M内存,80G硬盘 )。4.2 爬虫中服务器端的设计与实现4.2.1功能流程图和代码段及其界面图首先,给出服务器端总体功能流程图图3及相应代码:图3 服务器端总体功能流程图相应代码如下:/-public void receive()/等待并处理客户端的连接 try server=new ServerSocket(port); /初始化服务器套接字 while(true) socket=server.accept(); /等待连接 System.err.println(socket.getInetAddress()+"连接n"); /得到客户机地址 Client client=new Client(socket,id); /实例化一个客户线程 clients.addElement(client); /增加客户线程到向量中 addInfor(client); / 显示客户信息 btStart.setEnabled(true); client.start(); /启动线程,处理本次会话 notifyChatRoom(); /通知更新后的爬虫数目 id=produceID(id);/为下一个客户分配新的ID catch(Exception ex) ex.printStackTrace(); /输出出错信息 /-接着,再给出服务器中处理每个客户端的会话线程的相应代码及工作流程图图7相关部分代码如下:/- line=reader.readLine(); /读取数据流 if(keyword.equals("URL")/若客户端发送来的是URL请求 String msg=st.nextToken("0"); msg=msg.substring(1); CrawlerServer.addItemToDialog("From "+this.id+":"+msg); CrawlerServer.sendToWhom(msg);/服务器实行转发处理 else if(keyword.equals("Completed")|keyword.equals("Interrupted") /客户端发送“完成”或“被中断” String pageNum=st.nextToken(); String crawlingTime=st.nextToken(); String element=this.id+":"+"爬行页面总数 "+pageNum+ "爬行所需时间 "+crawlingTime; CrawlerServer.result.add(element); CrawlerServer.result.add(" "); CrawlerSpletedNum+; if(completedNum=CrawlerServer.clients.size() /若所有爬虫都完成或中断爬行任务则显示完成或中断爬行 if(keyword.equals("Completed") CrawlerServer.btStop.setLabel("完成爬行"); else CrawlerServer.btStop.setLabel("中断爬行"); CrawlerServer.btStop.setEnabled(false); CrawlerServer.closeAll(); else if(keyword.equals("QUIT") /退出命令 CrawlerServer.disconnect(this); /断开连接 CrawlerServer.notifyChatRoom(); /刷新信息 /-工作流程图如图4所示:图4 服务端会话线程功能流程图服务器端界面图如下图5:图5服务器端界面图当各爬虫都已就绪好并点击“发送”按钮后,服务器端的“各爬虫信息栏”将显示每个爬虫的信息,包括IP和ID号,同时“开始爬行”按钮由灰变亮。点击该按钮后,整个系统便开始运行,此时“停止爬行”按钮由灰变亮,接着便可在“服务器与爬虫对话栏”对话框中显示URL转发情况。操作人员也可点击“停止爬行”按钮来中断所有爬虫的运行,同时在“各爬虫状态监控栏”中可动态显示各个爬虫当前的采集信息,包括已下载网页数、活动线程数和爬虫所处的状态。当爬虫运行终止时,还进一步显示其耗时及URL抓取率。4.2.2 URL分发器的实现当服务器接受到来自客户端的URL请求时,便要实现对URL的转发的工作。将URL转发给正在运行的客户端中的某一个。转发的第一步是实现URL字符串与具体数字间的转换,借用MD5算法中的getMD5ofStr(String)方法可以首先完成从URL字符串到32位16进制数字字符串之间的变换。String str_url=md5.getMD5ofStr(_url);接着进行url的散列,针对32位的16进制数,我们的散列策略是拆分法,即,把32位长的16进制数字每4位分为一组,再这8组数转换成10进制整数,然后累加,将累加和对爬虫总数取余,其结果加1后便是转发目标的ID号。double int_url=md5.getDNum(str_url);/得到累加和 int totalCrawler=clients.size();/获取爬虫总数 int index=(int)int_url%totalCrawler;/取余得出转发目标ID主控端转发器完整代码如下:/-public static void sendToWhom(String _url) String str_url=md5.getMD5ofStr(_url); double int_url=md5.getDNum(str_url); /得到累加和 int totalCrawler=clients.size();/获取爬虫总数 int index=(int)int_url%totalCrawler; /取余得出转发目标ID Client c; StringBuffer url=new StringBuffer("URL:"); url.append(_url); c=(Client)clients.elementAt(index);/取出待转发的爬虫对象 c.send(url);/向该爬虫发送URL addItemToDialog("To "+c.id+": "+_url); btStart.setEnabled(false);/-4.2.3 ID生成器的实现ID生成器是用于给每台爬虫分配一个唯一的ID号,实现唯一标识和统一管理。它由两部分构成,第一部分都相同:“ID_”,第二部分为一个两位的数字。例如ID_01,ID_02,ID_10等。因此当要新生一个ID号时只需将第二部分的数字增一即可。以下是生成器代码:/-public static String produceID(String id)int index=id.indexOf('_');int num;String numPart=id.substring(index+1);if(numPart.startsWith("0")num=Integer.parseInt(numPart.substring(1);elsenum=Integer.parseInt(numPart);num+;/ID号的整数部分增一return (num>9)?("ID_"+num):("ID_0"+num);/-4.3 爬虫客户端的设计与实现4.3.1 功能流程图和代码段及其界面图爬虫客户端流程图如图6所示:图6 爬虫客户端功能流程图客户端套接字及会话线程的创建相关代码:/-socket=new Socket(InetAddress.getLocalHost(),5656); /实例化一个套接字 ps=new PrintStream(socket.getOutputStream(); /获取输出流 StringBuffer info=new StringBuffer("INFO:"); String userinfo=InetAddress.getLocalHost().toString(); ps.println(info.append(userinfo); /向服务器端注册ID号 ps.flush(); /确保数据流正确写入 listen=new Listen(this,socket); /实例化监听线程 listen.start(); /启动线程/-客户端的界面图如下图7所示:图7客户端的界面首先,用户在“页面存储路径”文本框中输入存储已下载页面的路径以及“服务器IP”文本框中输入服务器IP地址,确保服务器已运行时点击“发送”按钮。如果连接成功,则“当前状态”文本框将由“断开连接”变为“已连接”状态,同时在“分配的 ID号”文本框中显示服务器为其返回的ID号。接着爬虫开启采集与转发页面的任务,并将采集结果在右边的“已抓取的URL页面栏”列表框中动态显示,将与服务器间转发情况在左边的“服务器与客户端对话栏”的对话框中动态显示。在爬虫运行过程中,可在界面最底端的几个文本框中监视其爬行状况:其中右边的第一个文本框中动态显示已下载的页面数,第二个文本框动态显示系统活动的线程数目,通过点击最右边的“刷新时间”按钮,可在“爬行总耗时”文本框中静态显示爬虫已运行的时间。4.3.2 URL转发器的实现Spider在抓取每一个网页前,都要从URL管理器那申请一个处于等待状态的URL,接着由该转发器决定此URL是否属于自己的爬行范围,若属于则SpiderWorker启动线程开始爬行,否则将继续申请直到分配到符合条件的URL为止。下面的代码取自SpiderWorker类中的run()方法,主要代码:/-target = this.owner.getWorkload();/获取等待状态的URLwhile(target!=null) if(!isRightUrl(target)/转发 client.tfDialog.append("To Server: "+target+"n"); client.send("URL:"+target); else break; target = this.owner.getWorkload();/-其中isRightUrl()方法原型为:public boolean isRightUrl(String url)它的功能类似于ClientServer类中的sendToWhom()方法,其代码这里不再重复。程序流程图如图8所示:图8 URL转发器功能流程图4.3.3 URL接收器及其消重的实现当服务器向客户端转发URL时,爬虫客户端应准备接收此URL。并不是每次都把收到的URL简单地交给URL管理器处理,而应首先作出判断,因为每个爬虫在运行前需要被构造即初始化,以及传递初始URL作为爬行的起点。所以如果接收的是第一个URL则应构造并初始化爬虫,否则加入URL队列等待爬行。其流程图如图9所示:图9 URL接收器功能流程图至于URL去重,只需创建一个url列表存放已访问过的url。每次在接收新的url之前查看此表,如果含有该url,则将其抛弃,否则转交URL管理器。下面是主要代码:/-if(this.client.startURL)/若第一次接收到URL this.client.getSite=new GetSite(this.client,url,client.tfPath.getText();/构造爬虫 this.client.startURL=false; this.client.startingTime=System.currentTimeMillis();/记录起始爬行时间else /否则,转交URL管理器 if(!urlList.contains(url) this.client.getSite._spider.addWorkload(url);/-4.3.4 恢复采集断点功能的实现所谓采集断点的恢复,是指对于给定的每一个站点,在某一次采集中断后系统将记住采集的断点并在下一次运行时从该断点处接着爬行而并不是从第一个链接开始采集。断点的恢复功能不仅可以避免不必要的重复采集工作而极大提高系统的采集效率,而且也属于本系统设计中主要考虑的问题之一。 Bot包中的单机上的页面采集系统(Spider类)不具备断点恢复的功能,因为它在每次运行的开始与结束时都将做数据库清理工作。为实现该功能,本人对其做如下更改:首先删去在Spider类的构造方法及halt()方法中对数据库清理的语句workload.clear();由于只有那些在数据库中处于Waiting状态的URL才有被SpiderWorker采集的机会,故在启动爬虫线程之前应将上一次被中断的且处于Running状态的URL的状态恢复为Waiting状态而等待再次被采集,一方面可以恢复断点继续运行,另一方面还可以防止因数据库中没有处于Waiting状态的URL而使整个系统无法推进。这里改变URL状态的是新增的方法changeStatus();public void changeStatus() workload.setStatus(); 另外,由于爬行的站点不止一个,故数据库中会保存不同站点的URL信息。如果不做处理最终将导致采集混乱的局面,因为数据库中不同站点的URL都有处于等待状态的可能,如果在选取被采集URL时只看其所处状态就会出现采集其他网页的错误。处理方法是增加判断条件,将Waiting态下的URL与种子URL做比较然后再做取舍,关键代码如下示:/-