爱问 爱问共享资料 爱问分类
首页 > > > 自己动手写网络爬虫.pdf

自己动手写网络爬虫.pdf

自己动手写网络爬虫.pdf

上传者: 小田
658次下载 0人收藏 暂无简介 简介 2012-12-30 举报

简介:全本,x星上面划拉的。

第1篇自己动手抓取数据第1章全面剖析网络爬虫你知道百度、Google是如何获取数以亿计的网页并且实时更新的吗?你知道在搜索引擎领域人们常说的Spider是什么吗?本章将全面介绍网络爬虫的方方面面。读完之后,你将完全有能力自己写一个网络爬虫,随意抓取互联网上任何感兴趣的东西。既然百度、Google这些搜索引擎巨头已经帮我们抓取了互联网上的大部分信息,为什么还要自己写爬虫呢?因为深入整合信息的需求是广泛存在的。在企业中,爬虫抓取下来的信息可以作为数据仓库多维展现的数据源,也可以作为数据挖掘的来源。甚至有人为了炒股,专门抓取股票信息。既然从美国中情局到普通老百姓都需要,那还等什么,让我们快开始吧。411.1抓取网页网络爬虫的基本操作是抓取网页。那么如何才能随心所欲地获得自己想要的页面?这一节将从URL开始讲起,然后告诉大家如何抓取网页,并给出一个使用Java语言抓取网页的例子。最后,要讲一讲抓取过程中的一个重要问题:如何处理HTTP状态码。1.1.1深入理解URL抓取网页的过程其实和读者平时使用IE浏览器浏览网页的道理是一样的。比如,你打开一个浏览器,输入猎兔搜索网站的地址,如图1.1所示。图1.1使用浏览器浏览网页“打开”网页的过程其实就是浏览器作为一个浏览的“客户端”,向服务器端发送了一次请求,把服务器端的文件“抓”到本地,再进行解释、展现。更进一步,可以通过浏览器端查看“抓取”过来的文件源代码。选择“查看”|“源文件”命令,就会出现从服务器上“抓取”下来的文件的源代码,如图1.2所示。在上面的例子中,我们在浏览器的地址栏中输入的字符串叫做URL。那么,什么是URL呢?直观地讲,URL就是在浏览器端输入的http://www.lietu.com这个字符串。下面我们深入介绍有关URL的知识。在理解URL之前,首先要理解URI的概念。什么是URI?Web上每种可用的资源,如HTML文档、图像、视频片段、程序等都由一个通用资源标志符(UniversalResourceIdentifier,URI)进行定位。URI通常由三部分组成:访问资源的命名机制;存放资源的主机名;资源自身的名称,由路径表示。如下面的URI:http://www.webmonkey.com.cn/html/html40/第1章全面剖析网络爬虫5图1.2浏览器端源代码我们可以这样解释它:这是一个可以通过HTTP协议访问的资源,位于主机www.webmonkey.com.cn上,通过路径“/html/html40”访问。URL是URI的一个子集。它是UniformResourceLocator的缩写,译为“统一资源定位符”。通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,特别是著名的Mosaic。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL的格式由三部分组成:第一部分是协议(或称为服务方式)。第二部分是存有该资源的主机IP地址(有时也包括端口号)。第三部分是主机资源的具体地址,如目录和文件名等。第一部分和第二部分用“://”符号隔开,第二部分和第三部分用“/”符号隔开。第一部分和第二部分是不可缺少的,第三部分有时可以省略。根据URL的定义,我们给出了常用的两种URL协议的例子,供大家参考。1.HTTP协议的URL示例使用超级文本传输协议HTTP,提供超级文本信息服务的资源。例:http://www.peopledaily.com.cn/channel/welcome.htm其计算机域名为www.peopledaily.com.cn。超级文本文件(文件类型为.html)是在目录/channel下的welcome.htm。这是中国人民日报的一台计算机。例:http://www.rol.cn.net/talk/talk1.htm其计算机域名为www.rol.cn.net。超级文本文件(文件类型为.html)是在目录/talk下的61talk1.htm。这是瑞得聊天室的地址,可由此进入瑞得聊天室的第1室。2.文件的URL用URL表示文件时,服务器方式用file表示,后面要有主机IP地址、文件的存取路径(即目录)和文件名等信息。有时可以省略目录和文件名,但“/”符号不能省略。例:file://ftp.yoyodyne.com/pub/files/foobar.txt上面这个URL代表存放在主机ftp.yoyodyne.com上的pub/files/目录下的一个文件,文件名是foobar.txt。例:file://ftp.yoyodyne.com/pub代表主机ftp.yoyodyne.com上的目录/pub。例:file://ftp.yoyodyne.com/代表主机ftp.yoyodyne.com的根目录。爬虫最主要的处理对象就是URL,它根据URL地址取得所需要的文件内容,然后对它进行进一步的处理。因此,准确地理解URL对理解网络爬虫至关重要。从下一节开始,我们将详细地讲述如何根据URL地址来获得网页内容。1.1.2通过指定的URL抓取网页内容上一节详细介绍了URL的构成,这一节主要阐述如何根据给定的URL来抓取网页。所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地。类似于使用程序模拟IE浏览器的功能,把URL作为HTTP请求的内容发送到服务器端,然后读取服务器端的响应资源。Java语言是为网络而生的编程语言,它把网络资源看成是一种文件,它对网络资源的访问和对本地文件的访问一样方便。它把请求和响应封装为流。因此我们可以根据相应内容,获得响应流,之后从流中按字节读取数据。例如,java.net.URL类可以对相应的Web服务器发出请求并且获得响应文档。java.net.URL类有一个默认的构造函数,使用URL地址作为参数,构造URL对象:URLpageURL=newURL(path);接着,可以通过获得的URL对象来取得网络流,进而像操作本地文件一样来操作网络资源:InputStreamstream=pageURL.openStream();在实际的项目中,网络环境比较复杂,因此,只用java.net包中的API来模拟IE客户端的工作,代码量非常大。需要处理HTTP返回的状态码,设置HTTP代理,处理HTTPS协议等工作。为了便于应用程序的开发,实际开发时常常使用Apache的HTTP客户端开源项目——HttpClient。它完全能够处理HTTP连接中的各种问题,使用起来非常方便。只需在项目中引入HttpClient.jar包,就可以模拟IE来获取网页内容。例如://创建一个客户端,类似于打开一个浏览器HttpClienthttpclient=newHttpClient();第1章全面剖析网络爬虫7//创建一个get方法,类似于在浏览器地址栏中输入一个地址GetMethodgetMethod=newGetMethod("http://www.blablabla.com");//回车,获得响应状态码intstatusCode=httpclient.executeMethod(getMethod);//查看命中情况,可以获得的东西还有很多,比如head、cookies等System.out.println("response="+getMethod.getResponseBodyAsString());//释放getMethod.releaseConnection();上面的示例代码是使用HttpClient进行请求与响应的例子。第一行表示创建一个客户端,相当于打开浏览器。第二行使用get方式对http://www.blablabla.com进行请求。第三行执行请求,获取响应状态。第四行的getMethod.getResponseBodyAsString()方法能够以字符串方式获取返回的内容。这也是网页抓取所需要的内容。在这个示例中,只是简单地把返回的内容打印出来,而在实际项目中,通常需要把返回的内容写入本地文件并保存。最后还要关闭网络连接,以免造成资源消耗。这个例子是用get方式来访问Web资源。通常,get请求方式把需要传递给服务器的参数作为URL的一部分传递给服务器。但是,HTTP协议本身对URL字符串长度有所限制。因此不能传递过多的参数给服务器。为了避免这种问题,通常情况下,采用post方法进行HTTP请求,HttpClient包对post方法也有很好的支持。例如://得到post方法PostMethodPostMethod=newPostMethod("http://www.saybot.com/postme");//使用数组来传递参数NameValuePair[]postData=newNameValuePair[2];//设置参数postData[0]=newNameValuePair("武器","枪");postData[1]=newNameValuePair("什么枪","神枪");postMethod.addParameters(postData);//回车,获得响应状态码intstatusCode=httpclient.executeMethod(getMethod);//查看命中情况,可以获得的东西还有很多,比如head、cookies等System.out.println("response="+getMethod.getResponseBodyAsString());//释放getMethod.releaseConnection();上面的例子说明了如何使用post方法来访问Web资源。与get方法不同,post方法可以使用NameValuePair来设置参数,因此可以设置“无限”多的参数。而get方法采用把参81数写在URL里面的方式,由于URL有长度限制,因此传递参数的长度会有限制。有时,我们执行爬虫程序的机器不能直接访问Web资源,而是需要通过HTTP代理服务器去访问,HttpClient对代理服务器也有很好的支持。如://创建HttpClient相当于打开一个代理HttpClienthttpClient=newHttpClient();//设置代理服务器的IP地址和端口httpClient.getHostConfiguration().setProxy("192.168.0.1",9527);//告诉httpClient,使用抢先认证,否则你会收到“你没有资格”的恶果httpClient.getParams().setAuthenticationPreemptive(true);//MyProxyCredentialsProvder返回代理的credential(username/password)httpClient.getParams().setParameter(CredentialsProvider.PROVIDER,newMyProxyCredentialsProvider());//设置代理服务器的用户名和密码httpClient.getState().setProxyCredentials(newAuthScope("192.168.0.1",AuthScope.ANY_PORT,AuthScope.ANY_REALM),newUsernamePasswordCredentials("username","password"));上面的例子详细解释了如何使用HttpClient设置代理服务器。如果你所在的局域网访问Web资源需要代理服务器的话,你可以参照上面的代码设置。这一节,我们介绍了使用HttpClient抓取网页的内容,之后,我们将给出一个详细的例子来说明如何获取网页。1.1.3Java网页抓取示例在这一节中,我们根据之前讲过的内容,写一个实际的网页抓取的例子。这个例子把上一节讲的内容做了一定的总结,代码如下:publicclassRetrivePage{privatestaticHttpClienthttpClient=newHttpClient();//设置代理服务器static{//设置代理服务器的IP地址和端口httpClient.getHostConfiguration().setProxy("172.17.18.84",8080);}publicstaticbooleandownloadPage(Stringpath)throwsHttpException,IOException{InputStreaminput=null;OutputStreamoutput=null;//得到post方法PostMethodpostMethod=newPostMethod(path);//设置post方法的参数NameValuePair[]postData=newNameValuePair[2];postData[0]=newNameValuePair("name","lietu");postData[1]=new第1章全面剖析网络爬虫9NameValuePair("password","*****");postMethod.addParameters(postData);//执行,返回状态码intstatusCode=httpClient.executeMethod(postMethod);//针对状态码进行处理(简单起见,只处理返回值为200的状态码)if(statusCode==HttpStatus.SC_OK){input=postMethod.getResponseBodyAsStream();//得到文件名Stringfilename=path.substring(path.lastIndexOf('/')+1);//获得文件输出流output=newFileOutputStream(filename);//输出到文件inttempByte=-1;while((tempByte=input.read())>0){output.write(tempByte);}//关闭输入输出流if(input!=null){input.close();}if(output!=null){output.close();}returntrue;}returnfalse;}/***测试代码*/publicstaticvoidmain(String[]args){//抓取lietu首页,输出try{RetrivePage.downloadPage("http://www.lietu.com/");}catch(HttpExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}catch(IOExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}}}上面的例子是抓取猎兔搜索主页的示例。它是一个比较简单的网页抓取示例,由于互联网的复杂性,真正的网页抓取程序会考虑非常多的问题。比如,资源名的问题,资源类型的问题,状态码的问题。而其中最重要的就是针对各种返回的状态码的处理。下一节将重点介绍处理状态码的问题。1011.1.4处理HTTP状态码上一节介绍HttpClient访问Web资源的时候,涉及HTTP状态码。比如下面这条语句:intstatusCode=httpClient.executeMethod(getMethod);//回车,获得响应状态码HTTP状态码表示HTTP协议所返回的响应的状态。比如客户端向服务器发送请求,如果成功地获得请求的资源,则返回的状态码为200,表示响应成功。如果请求的资源不存在,则通常返回404错误。HTTP状态码通常分为5种类型,分别以1~5五个数字开头,由3位整数组成。1XX通常用作实验用途。这一节主要介绍2XX、3XX、4XX、5XX等常用的几种状态码,如表1.1所示。表1.1HTTP常用状态码状态代码代码描述处理方式200请求成功获得响应的内容,进行处理201请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到爬虫中不会遇到202请求被接受,但处理尚未完成阻塞等待204服务器端已经实现了请求,但是没有返回新的信息。如果客户是用户代理,则无须为此更新自身的文档视图丢弃300该状态码不被HTTP/1.0的应用程序直接使用,只是作为3XX类型回应的默认解释。存在多个可用的被请求资源若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃301请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源重定向到分配的URL302请求到的资源在一个不同的URL处临时保存重定向到临时的URL304请求的资源未更新丢弃400非法请求丢弃401未授权丢弃403禁止丢弃404没有找到丢弃5XX回应代码以“5”开头的状态码表示服务器端发现自己出现错误,不能继续执行请求丢弃当返回的状态码为5XX时,表示应用服务器出现错误,采用简单的丢弃处理就可以解决。第1章全面剖析网络爬虫11当返回值状态码为3XX时,通常进行转向,以下是转向的代码片段,读者可以和上一节的代码自行整合到一起://若需要转向,则进行转向操作if((statusCode==HttpStatus.SC_MOVED_TEMPORARILY)||(statusCode==HttpStatus.SC_MOVED_PERMANENTLY)||(statusCode==HttpStatus.SC_SEE_OTHER)||(statusCode==HttpStatus.SC_TEMPORARY_REDIRECT)){//读取新的URL地址Headerheader=postMethod.getResponseHeader("location");if(header!=null){StringnewUrl=header.getValue();if(newUrl==null||newUrl.equals("")){newUrl="/";//使用post转向PostMethodredirect=newPostMethod(newUrl);//发送请求,做进一步处理……}}}当响应状态码为2XX时,根据表1.1的描述,我们只需要处理200和202两种状态码,其他的返回值可以不做进一步处理。200的返回状态码是成功状态码,可以直接进行网页抓取,例如://处理返回值为200的状态码if(statusCode==HttpStatus.SC_OK){input=postMethod.getResponseBodyAsStream();//得到文件名Stringfilename=path.substring(path.lastIndexOf('/')+1);//获得文件输出流output=newFileOutputStream(filename);//输出到文件inttempByte=-1;while((tempByte=input.read())>0){output.write(tempByte);}}202的响应状态码表示请求已经接受,服务器再做进一步处理。1.2宽度优先爬虫和带偏好的爬虫1.1节介绍了如何获取单个网页内容。在实际项目中,则使用爬虫程序遍历互联网,把网络中相关的网页全部抓取过来,这也体现了爬虫程序“爬”的概念。爬虫程序是如何遍历互联网,把网页全部抓取下来的呢?互联网可以看成一个超级大的“图”,而每个页面可以看作是一个“节点”。页面中的链接可以看成是图的“有向边”。因此,能够通过图121的遍历的方式对互联网这个超级大“图”进行访问。图的遍历通常可分为宽度优先遍历和深度优先遍历两种方式。但是深度优先遍历可能会在深度上过“深”地遍历或者陷入“黑洞”,大多数爬虫都不采用这种方式。另一方面,在爬取的时候,有时候也不能完全按照宽度优先遍历的方式,而是给待遍历的网页赋予一定的优先级,根据这个优先级进行遍历,这种方法称为带偏好的遍历。本小节会分别介绍宽度优先遍历和带偏好的遍历。1.2.1图的宽度优先遍历下面先来看看图的宽度优先遍历过程。图的宽度优先遍历(BFS)算法是一个分层搜索的过程,和树的层序遍历算法相同。在图中选中一个节点,作为起始节点,然后按照层次遍历的方式,一层一层地进行访问。图的宽度优先遍历需要一个队列作为保存当前节点的子节点的数据结构。具体的算法如下所示:(1)顶点V入队列。(2)当队列非空时继续执行,否则算法为空。(3)出队列,获得队头节点V,访问顶点V并标记V已经被访问。(4)查找顶点V的第一个邻接顶点col。(5)若V的邻接顶点col未被访问过,则col进队列。(6)继续查找V的其他邻接顶点col,转到步骤(5),若V的所有邻接顶点都已经被访问过,则转到步骤(2)。下面,我们以图示的方式介绍宽度优先遍历的过程,如图1.3所示。GBACDFEIH图1.3宽度优先遍历过程第1章全面剖析网络爬虫13选择A作为种子节点,则宽度优先遍历的过程,如表1.2所示。表1.2宽度优先遍历过程操作队列中的元素初始空A入队列AA出队列空BCDEF入队列BCDEFB出队列CDEFC出队列DEFD出队列EFE出队列FH入队列FHF出队列HG入队列HGH出队列GI入队列GIG出队列II出队列空在表1.2所示的遍历过程中,出队列的节点顺序既是图的宽度优先遍历的访问顺序。由此可以看出,图1.3所示的宽度优先遍历的访问顺序为A->B->C->D->E->F->H->G->I本节讲述了宽度优先遍历的理论基础,把互联网看成一个“超图”,则对这张图也可以采用宽度优先遍历的方式进行访问。下面将着重讲解如何对互联网进行宽度优先遍历。1.2.2宽度优先遍历互联网1.2.1节介绍的宽度优先遍历是从一个种子节点开始的。而实际的爬虫项目是从一系列的种子链接开始的。所谓种子链接,就好比宽度优先遍历中的种子节点(图1.3中的A节点)一样。实际的爬虫项目中种子链接可以有多个,而宽度优先遍历中的种子节点只有一个。比如,可以指定www.lietu.com和www.sina.com两个种子链接。如何定义一个链接的子节点?每个链接对应一个HTML页面或者其他文件(word、excel、pdf、jpg等),在这些文件中,只有HTML页面有相应的“子节点”,这些“子节点”就是HTML页面上对应的超链接。如www.lietu.com页面中(如图1.4所示),“招聘”、“网址”、“更多”以及页面下方的“搜索产品”,“技术文档”,“成功案例”,“猎兔新闻”,“联系猎兔”,“关于我们”,ENGLISH等都是www.lietu.com的子节点。这些子节点本身又是一个链接。对于非HTML文档,比如Excel文件等,不能从中提取超链接,因此,可以看作是图的“终端”节点。就好像图1.3中的B、C、D、I、G等节点一样。141图1.4猎兔搜索主页整个的宽度优先爬虫过程就是从一系列的种子节点开始,把这些网页中的“子节点”(也就是超链接)提取出来,放入队列中依次进行抓取。被处理过的链接需要放入一张表(通常称为Visited表)中。每次新处理一个链接之前,需要查看这个链接是否已经存在于Visited表中。如果存在,证明链接已经处理过,跳过,不做处理,否则进行下一步处理。实际的过程如图1.5所示。新解析出的URLTODO表Visited表初始URL地址解析URL图1.5宽度优先爬虫过程如图1.5所示,初始的URL地址是爬虫系统中提供的种子URL(一般在系统的配置文件中指定)。当解析这些种子URL所表示的网页时,会产生新的URL(比如从页面中的<ahref=“http://www.admin.com”中提取出http://www.admin.com这个链接)。然后,进行以下工作:(1)把解析出的链接和Visited表中的链接进行比较,若Visited表中不存在此链接,表示其未被访问过。(2)把链接放入TODO表中。(3)处理完毕后,再次从TODO表中取得一条链接,直接放入Visited表中。(4)针对这个链接所表示的网页,继续上述过程。如此循环往复。表1.3显示了对图1.3所示的页面的爬取过程。表1.3网络爬取TODO表Visited表A空BCDEFACDEFA,BDEFA,B,C第1章全面剖析网络爬虫15续表TODO表Visited表EFA,B,C,DFHA,B,C,D,EHGA,B,C,D,E,FGIA,B,C,D,E,F,HIA,B,C,D,E,F,H,G空A,B,C,D,E,F,H,G,I宽度优先遍历是爬虫中使用最广泛的一种爬虫策略,之所以使用宽度优先搜索策略,主要原因有三点:重要的网页往往离种子比较近,例如我们打开新闻网站的时候往往是最热门的新闻,随着不断的深入冲浪,所看到的网页的重要性越来越低。万维网的实际深度最多能达到17层,但到达某个网页总存在一条很短的路径。而宽度优先遍历会以最快的速度到达这个网页。宽度优先有利于多爬虫的合作抓取,多爬虫合作通常先抓取站内链接,抓取的封闭性很强。这一小节详细讲述了宽度优先遍历互联网的方法。下一节将给出一个详细的例子来说明如何实现这种方法。1.2.3Java宽度优先爬虫示例本节使用Java实现一个简易的爬虫。其中用到了HttpClient和HtmlParser两个开源工具包。HttpClient的内容之前已经做过详细的阐述。有关HtmlParser的用法,我们会在4.2节给出详细的介绍。为了便于读者理解,下面给出示例程序的结构,如图1.6所示。初始化URL队列判断条件新URL入队抽出网页中的URL下载URL指定的页面队头URL出队列退出程序URL队列为空或爬取到指定数量的网页图1.6爬虫示例程序结构161首先,需要定义图1.6中所描述的“URL队列”,这里使用一个LinkedList来实现这个队列。QueueQueueQueueQueue类:/***队列,保存将要访问的URL*/publicclassQueue{//使用链表实现队列privateLinkedListqueue=newLinkedList();//入队列publicvoidenQueue(Objectt){queue.addLast(t);}//出队列publicObjectdeQueue(){returnqueue.removeFirst();}//判断队列是否为空publicbooleanisQueueEmpty(){returnqueue.isEmpty();}//判断队列是否包含tpublicbooleancontians(Objectt){returnqueue.contains(t);}publicbooleanempty(){returnqueue.isEmpty();}}除了URL队列之外,在爬虫过程中,还需要一个数据结构来记录已经访问过的URL。每当要访问一个URL的时候,首先在这个数据结构中进行查找,如果当前的URL已经存在,则丢弃它。这个数据结构要有两个特点:结构中保存的URL不能重复。能够快速地查找(实际系统中URL的数目非常多,因此要考虑查找性能)。针对以上两点,我们选择HashSet作为存储结构。LinkQueueLinkQueueLinkQueueLinkQueue类:publicclassLinkQueue{//已访问的url集合privatestaticSetvisitedUrl=newHashSet();//待访问的url集合privatestaticQueueunVisitedUrl=newQueue();第1章全面剖析网络爬虫17//获得URL队列publicstaticQueuegetUnVisitedUrl(){returnunVisitedUrl;}//添加到访问过的URL队列中publicstaticvoidaddVisitedUrl(Stringurl){visitedUrl.add(url);}//移除访问过的URLpublicstaticvoidremoveVisitedUrl(Stringurl){visitedUrl.remove(url);}//未访问的URL出队列publicstaticObjectunVisitedUrlDeQueue(){returnunVisitedUrl.deQueue();}//保证每个URL只被访问一次publicstaticvoidaddUnvisitedUrl(Stringurl){if(url!=null&&!url.trim().equals("")&&!visitedUrl.contains(url)&&!unVisitedUrl.contians(url))unVisitedUrl.enQueue(url);}//获得已经访问的URL数目publicstaticintgetVisitedUrlNum(){returnvisitedUrl.size();}//判断未访问的URL队列中是否为空publicstaticbooleanunVisitedUrlsEmpty(){returnunVisitedUrl.empty();}}下面的代码详细说明了网页下载并处理的过程。和1.1节讲述的内容相比,它考虑了更多的方面。比如如何存储网页,设置请求超时策略等。DownLoadFileDownLoadFileDownLoadFileDownLoadFile类:publicclassDownLoadFile{/***根据URL和网页类型生成需要保存的网页的文件名,去除URL中的非文件名字符*/publicStringgetFileNameByUrl(Stringurl,StringcontentType){//移除http:url=url.substring(7);//text/html类型if(contentType.indexOf("html")!=-1)181{url=url.replaceAll("[\\?/:*|<>\"]","_")+".html";returnurl;}//如application/pdf类型else{returnurl.replaceAll("[\\?/:*|<>\"]","_")+"."+contentType.substring(contentType.lastIndexOf("/")+1);}}/***保存网页字节数组到本地文件,filePath为要保存的文件的相对地址*/privatevoidsaveToLocal(byte[]data,StringfilePath){try{DataOutputStreamout=newDataOutputStream(newFileOutputStream(newFile(filePath)));for(inti=0;i<data.length;i++)out.write(data[i]);out.flush();out.close();}catch(IOExceptione){e.printStackTrace();}}//下载URL指向的网页publicStringdownloadFile(Stringurl){StringfilePath=null;//1.生成HttpClinet对象并设置参数HttpClienthttpClient=newHttpClient();//设置HTTP连接超时5shttpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);//2.生成GetMethod对象并设置参数GetMethodgetMethod=newGetMethod(url);//设置get请求超时5sgetMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);//设置请求重试处理getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,newDefaultHttpMethodRetryHandler());//3.执行HTTPGET请求try{intstatusCode=httpClient.executeMethod(getMethod);//判断访问的状态码if(statusCode!=HttpStatus.SC_OK){第1章全面剖析网络爬虫19System.err.println("Methodfailed:"+getMethod.getStatusLine());filePath=null;}//4.处理HTTP响应内容byte[]responseBody=getMethod.getResponseBody();//读取为字节数组//根据网页url生成保存时的文件名filePath="temp\\"+getFileNameByUrl(url,getMethod.getResponseHeader("Content-Type").getValue());saveToLocal(responseBody,filePath);}catch(HttpExceptione){//发生致命的异常,可能是协议不对或者返回的内容有问题System.out.println("Pleasecheckyourprovidedhttpaddress!");e.printStackTrace();}catch(IOExceptione){//发生网络异常e.printStackTrace();}finally{//释放连接getMethod.releaseConnection();}returnfilePath;}}接下来,演示如何从获得的网页中提取URL。Java有一个非常实用的开源工具包HtmlParser,它专门针对Html页面进行处理,不仅能提取URL,还能提取文本以及你想要的任何内容。关于它的有关内容,会在第4章详细介绍。好了,让我们看看代码吧!HtmlParserToolHtmlParserToolHtmlParserToolHtmlParserTool类:publicclassHtmlParserTool{//获取一个网站上的链接,filter用来过滤链接publicstaticSet<String>extracLinks(Stringurl,LinkFilterfilter){Set<String>links=newHashSet<String>();try{Parserparser=newParser(url);parser.setEncoding("gb2312");//过滤<frame>标签的filter,用来提取frame标签里的src属性NodeFilterframeFilter=newNodeFilter(){publicbooleanaccept(Nodenode){if(node.getText().startsWith("framesrc=")){returntrue;}else{returnfalse;}};201//OrFilter来设置过滤<a>标签和<frame>标签OrFilterlinkFilter=newOrFilter(newNodeClassFilter(LinkTag.class),frameFilter);//得到所有经过过滤的标签NodeListlist=parser.extractAllNodesThatMatch(linkFilter);for(inti=0;i<list.size();i++){Nodetag=list.elementAt(i);if(taginstanceofLinkTag)//<a>标签{LinkTaglink=(LinkTag)tag;StringlinkUrl=link.getLink();//URLif(filter.accept(linkUrl))links.add(linkUrl);}else//<frame>标签{//提取frame里src属性的链接,如<framesrc="test.html"/>Stringframe=tag.getText();intstart=frame.indexOf("src=");frame=frame.substring(start);intend=frame.indexOf("");if(end==-1)end=frame.indexOf(">");StringframeUrl=frame.substring(5,end-1);if(filter.accept(frameUrl))links.add(frameUrl);}}}catch(ParserExceptione){e.printStackTrace();}returnlinks;}}最后,来看看宽度爬虫的主程序。MyCrawlerMyCrawlerMyCrawlerMyCrawler类:publicclassMyCrawler{/***使用种子初始化URL队列*@return*@paramseeds种子URL*/privatevoidinitCrawlerWithSeeds(String[]seeds){for(inti=0;i<seeds.length;i++)LinkQueue.addUnvisitedUrl(seeds[i]);}第1章全面剖析网络爬虫21/***抓取过程*@return*@paramseeds*/publicvoidcrawling(String[]seeds){//定义过滤器,提取以http://www.lietu.com开头的链接LinkFilterfilter=newLinkFilter(){publicbooleanaccept(Stringurl){if(url.startsWith("http://www.lietu.com"))returntrue;elsereturnfalse;}};//初始化URL队列initCrawlerWithSeeds(seeds);//循环条件:待抓取的链接不空且抓取的网页不多于1000while(!LinkQueue.unVisitedUrlsEmpty()&&LinkQueue.getVisitedUrlNum()<=1000){//队头URL出队列StringvisitUrl=(String)LinkQueue.unVisitedUrlDeQueue();if(visitUrl==null)continue;DownLoadFiledownLoader=newDownLoadFile();//下载网页downLoader.downloadFile(visitUrl);//该URL放入已访问的URL中LinkQueue.addVisitedUrl(visitUrl);//提取出下载网页中的URLSet<String>links=HtmlParserTool.extracLinks(visitUrl,filter);//新的未访问的URL入队for(Stringlink:links){LinkQueue.addUnvisitedUrl(link);}}}//main方法入口publicstaticvoidmain(String[]args){MyCrawlercrawler=newMyCrawler();crawler.crawling(newString[]{"http://www.lietu.com"});}}上面的主程序使用了一个LinkFilter接口,并且实现为一个内部类。这个接口的目的是221为了过滤提取出来的URL,它使得程序中提取出来的URL只会和猎兔网站相关。而不会提取其他无关的网站。代码如下:publicinterfaceLinkFilter{publicbooleanaccept(Stringurl);}1.2.4带偏好的爬虫有时,在URL队列中选择需要抓取的URL时,不一定按照队列“先进先出”的方式进行选择。而把重要的URL先从队列中“挑”出来进行抓取。这种策略也称作“页面选择”(PageSelection)。这可以使有限的网络资源照顾重要性高的网页。那么哪些网页是重要性高的网页呢?判断网页的重要性的因素很多,主要有链接的欢迎度(知道链接的重要性了吧)、链接的重要度和平均链接深度、网站质量、历史权重等主要因素。链接的欢迎度主要是由反向链接(backlinks,即指向当前URL的链接)的数量和质量决定的,我们定义为IB(P)。链接的重要度,是一个关于URL字符串的函数,仅仅考察字符串本身,比如认为“.com”和“home”的URL重要度比“.cc”和“map”高,我们定义为IL(P)。平均链接深度,根据上面所分析的宽度优先的原则计算出全站的平均链接深度,然后认为距离种子站点越近的重要性越高。我们定义为ID(P)。如果我们定义网页的重要性为I(P),那么,页面的重要度由下面的公式决定:I(P)=X*IB(P)+Y*IL(P)(1.1)其中,X和Y两个参数,用来调整IB(P)和IL(P)所占比例的大小,ID(P)由宽度优先的遍历规则保证,因此不作为重要的指标函数。如何实现最佳优先爬虫呢,最简单的方式可以使用优先级队列来实现TODO表,并且把每个URL的重要性作为队列元素的优先级。这样,每次选出来扩展的URL就是具有最高重要性的网页。有关优先级队列的介绍,请参考1.2.5节中的内容。例如,假设图1.3中节点的重要性为D>B>C>A>E>F>I>G>H,则整个遍历过程如表1.4所示。表

自己动手写网络爬虫.pdf

自己动手写网络爬虫.pdf

上传者: 小田
658次下载 0人收藏 暂无简介 简介 2012-12-30 举报

简介:全本,x星上面划拉的。

第1篇自己动手抓取数据第1章全面剖析网络爬虫你知道百度、Google是如何获取数以亿计的网页并且实时更新的吗?你知道在搜索引擎领域人们常说的Spider是什么吗?本章将全面介绍网络爬虫的方方面面。读完之后,你将完全有能力自己写一个网络爬虫,随意抓取互联网上任何感兴趣的东西。既然百度、Google这些搜索引擎巨头已经帮我们抓取了互联网上的大部分信息,为什么还要自己写爬虫呢?因为深入整合信息的需求是广泛存在的。在企业中,爬虫抓取下来的信息可以作为数据仓库多维展现的数据源,也可以作为数据挖掘的来源。甚至有人为了炒股,专门抓取股票信息。既然从美国中情局到普通老百姓都需要,那还等什么,让我们快开始吧。411.1抓取网页网络爬虫的基本操作是抓取网页。那么如何才能随心所欲地获得自己想要的页面?这一节将从URL开始讲起,然后告诉大家如何抓取网页,并给出一个使用Java语言抓取网页的例子。最后,要讲一讲抓取过程中的一个重要问题:如何处理HTTP状态码。1.1.1深入理解URL抓取网页的过程其实和读者平时使用IE浏览器浏览网页的道理是一样的。比如,你打开一个浏览器,输入猎兔搜索网站的地址,如图1.1所示。图1.1使用浏览器浏览网页“打开”网页的过程其实就是浏览器作为一个浏览的“客户端”,向服务器端发送了一次请求,把服务器端的文件“抓”到本地,再进行解释、展现。更进一步,可以通过浏览器端查看“抓取”过来的文件源代码。选择“查看”|“源文件”命令,就会出现从服务器上“抓取”下来的文件的源代码,如图1.2所示。在上面的例子中,我们在浏览器的地址栏中输入的字符串叫做URL。那么,什么是URL呢?直观地讲,URL就是在浏览器端输入的http://www.lietu.com这个字符串。下面我们深入介绍有关URL的知识。在理解URL之前,首先要理解URI的概念。什么是URI?Web上每种可用的资源,如HTML文档、图像、视频片段、程序等都由一个通用资源标志符(UniversalResourceIdentifier,URI)进行定位。URI通常由三部分组成:访问资源的命名机制;存放资源的主机名;资源自身的名称,由路径表示。如下面的URI:http://www.webmonkey.com.cn/html/html40/第1章全面剖析网络爬虫5图1.2浏览器端源代码我们可以这样解释它:这是一个可以通过HTTP协议访问的资源,位于主机www.webmonkey.com.cn上,通过路径“/html/html40”访问。URL是URI的一个子集。它是UniformResourceLocator的缩写,译为“统一资源定位符”。通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,特别是著名的Mosaic。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL的格式由三部分组成:第一部分是协议(或称为服务方式)。第二部分是存有该资源的主机IP地址(有时也包括端口号)。第三部分是主机资源的具体地址,如目录和文件名等。第一部分和第二部分用“://”符号隔开,第二部分和第三部分用“/”符号隔开。第一部分和第二部分是不可缺少的,第三部分有时可以省略。根据URL的定义,我们给出了常用的两种URL协议的例子,供大家参考。1.HTTP协议的URL示例使用超级文本传输协议HTTP,提供超级文本信息服务的资源。例:http://www.peopledaily.com.cn/channel/welcome.htm其计算机域名为www.peopledaily.com.cn。超级文本文件(文件类型为.html)是在目录/channel下的welcome.htm。这是中国人民日报的一台计算机。例:http://www.rol.cn.net/talk/talk1.htm其计算机域名为www.rol.cn.net。超级文本文件(文件类型为.html)是在目录/talk下的61talk1.htm。这是瑞得聊天室的地址,可由此进入瑞得聊天室的第1室。2.文件的URL用URL表示文件时,服务器方式用file表示,后面要有主机IP地址、文件的存取路径(即目录)和文件名等信息。有时可以省略目录和文件名,但“/”符号不能省略。例:file://ftp.yoyodyne.com/pub/files/foobar.txt上面这个URL代表存放在主机ftp.yoyodyne.com上的pub/files/目录下的一个文件,文件名是foobar.txt。例:file://ftp.yoyodyne.com/pub代表主机ftp.yoyodyne.com上的目录/pub。例:file://ftp.yoyodyne.com/代表主机ftp.yoyodyne.com的根目录。爬虫最主要的处理对象就是URL,它根据URL地址取得所需要的文件内容,然后对它进行进一步的处理。因此,准确地理解URL对理解网络爬虫至关重要。从下一节开始,我们将详细地讲述如何根据URL地址来获得网页内容。1.1.2通过指定的URL抓取网页内容上一节详细介绍了URL的构成,这一节主要阐述如何根据给定的URL来抓取网页。所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地。类似于使用程序模拟IE浏览器的功能,把URL作为HTTP请求的内容发送到服务器端,然后读取服务器端的响应资源。Java语言是为网络而生的编程语言,它把网络资源看成是一种文件,它对网络资源的访问和对本地文件的访问一样方便。它把请求和响应封装为流。因此我们可以根据相应内容,获得响应流,之后从流中按字节读取数据。例如,java.net.URL类可以对相应的Web服务器发出请求并且获得响应文档。java.net.URL类有一个默认的构造函数,使用URL地址作为参数,构造URL对象:URLpageURL=newURL(path);接着,可以通过获得的URL对象来取得网络流,进而像操作本地文件一样来操作网络资源:InputStreamstream=pageURL.openStream();在实际的项目中,网络环境比较复杂,因此,只用java.net包中的API来模拟IE客户端的工作,代码量非常大。需要处理HTTP返回的状态码,设置HTTP代理,处理HTTPS协议等工作。为了便于应用程序的开发,实际开发时常常使用Apache的HTTP客户端开源项目——HttpClient。它完全能够处理HTTP连接中的各种问题,使用起来非常方便。只需在项目中引入HttpClient.jar包,就可以模拟IE来获取网页内容。例如://创建一个客户端,类似于打开一个浏览器HttpClienthttpclient=newHttpClient();第1章全面剖析网络爬虫7//创建一个get方法,类似于在浏览器地址栏中输入一个地址GetMethodgetMethod=newGetMethod("http://www.blablabla.com");//回车,获得响应状态码intstatusCode=httpclient.executeMethod(getMethod);//查看命中情况,可以获得的东西还有很多,比如head、cookies等System.out.println("response="+getMethod.getResponseBodyAsString());//释放getMethod.releaseConnection();上面的示例代码是使用HttpClient进行请求与响应的例子。第一行表示创建一个客户端,相当于打开浏览器。第二行使用get方式对http://www.blablabla.com进行请求。第三行执行请求,获取响应状态。第四行的getMethod.getResponseBodyAsString()方法能够以字符串方式获取返回的内容。这也是网页抓取所需要的内容。在这个示例中,只是简单地把返回的内容打印出来,而在实际项目中,通常需要把返回的内容写入本地文件并保存。最后还要关闭网络连接,以免造成资源消耗。这个例子是用get方式来访问Web资源。通常,get请求方式把需要传递给服务器的参数作为URL的一部分传递给服务器。但是,HTTP协议本身对URL字符串长度有所限制。因此不能传递过多的参数给服务器。为了避免这种问题,通常情况下,采用post方法进行HTTP请求,HttpClient包对post方法也有很好的支持。例如://得到post方法PostMethodPostMethod=newPostMethod("http://www.saybot.com/postme");//使用数组来传递参数NameValuePair[]postData=newNameValuePair[2];//设置参数postData[0]=newNameValuePair("武器","枪");postData[1]=newNameValuePair("什么枪","神枪");postMethod.addParameters(postData);//回车,获得响应状态码intstatusCode=httpclient.executeMethod(getMethod);//查看命中情况,可以获得的东西还有很多,比如head、cookies等System.out.println("response="+getMethod.getResponseBodyAsString());//释放getMethod.releaseConnection();上面的例子说明了如何使用post方法来访问Web资源。与get方法不同,post方法可以使用NameValuePair来设置参数,因此可以设置“无限”多的参数。而get方法采用把参81数写在URL里面的方式,由于URL有长度限制,因此传递参数的长度会有限制。有时,我们执行爬虫程序的机器不能直接访问Web资源,而是需要通过HTTP代理服务器去访问,HttpClient对代理服务器也有很好的支持。如://创建HttpClient相当于打开一个代理HttpClienthttpClient=newHttpClient();//设置代理服务器的IP地址和端口httpClient.getHostConfiguration().setProxy("192.168.0.1",9527);//告诉httpClient,使用抢先认证,否则你会收到“你没有资格”的恶果httpClient.getParams().setAuthenticationPreemptive(true);//MyProxyCredentialsProvder返回代理的credential(username/password)httpClient.getParams().setParameter(CredentialsProvider.PROVIDER,newMyProxyCredentialsProvider());//设置代理服务器的用户名和密码httpClient.getState().setProxyCredentials(newAuthScope("192.168.0.1",AuthScope.ANY_PORT,AuthScope.ANY_REALM),newUsernamePasswordCredentials("username","password"));上面的例子详细解释了如何使用HttpClient设置代理服务器。如果你所在的局域网访问Web资源需要代理服务器的话,你可以参照上面的代码设置。这一节,我们介绍了使用HttpClient抓取网页的内容,之后,我们将给出一个详细的例子来说明如何获取网页。1.1.3Java网页抓取示例在这一节中,我们根据之前讲过的内容,写一个实际的网页抓取的例子。这个例子把上一节讲的内容做了一定的总结,代码如下:publicclassRetrivePage{privatestaticHttpClienthttpClient=newHttpClient();//设置代理服务器static{//设置代理服务器的IP地址和端口httpClient.getHostConfiguration().setProxy("172.17.18.84",8080);}publicstaticbooleandownloadPage(Stringpath)throwsHttpException,IOException{InputStreaminput=null;OutputStreamoutput=null;//得到post方法PostMethodpostMethod=newPostMethod(path);//设置post方法的参数NameValuePair[]postData=newNameValuePair[2];postData[0]=newNameValuePair("name","lietu");postData[1]=new第1章全面剖析网络爬虫9NameValuePair("password","*****");postMethod.addParameters(postData);//执行,返回状态码intstatusCode=httpClient.executeMethod(postMethod);//针对状态码进行处理(简单起见,只处理返回值为200的状态码)if(statusCode==HttpStatus.SC_OK){input=postMethod.getResponseBodyAsStream();//得到文件名Stringfilename=path.substring(path.lastIndexOf('/')+1);//获得文件输出流output=newFileOutputStream(filename);//输出到文件inttempByte=-1;while((tempByte=input.read())>0){output.write(tempByte);}//关闭输入输出流if(input!=null){input.close();}if(output!=null){output.close();}returntrue;}returnfalse;}/***测试代码*/publicstaticvoidmain(String[]args){//抓取lietu首页,输出try{RetrivePage.downloadPage("http://www.lietu.com/");}catch(HttpExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}catch(IOExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}}}上面的例子是抓取猎兔搜索主页的示例。它是一个比较简单的网页抓取示例,由于互联网的复杂性,真正的网页抓取程序会考虑非常多的问题。比如,资源名的问题,资源类型的问题,状态码的问题。而其中最重要的就是针对各种返回的状态码的处理。下一节将重点介绍处理状态码的问题。1011.1.4处理HTTP状态码上一节介绍HttpClient访问Web资源的时候,涉及HTTP状态码。比如下面这条语句:intstatusCode=httpClient.executeMethod(getMethod);//回车,获得响应状态码HTTP状态码表示HTTP协议所返回的响应的状态。比如客户端向服务器发送请求,如果成功地获得请求的资源,则返回的状态码为200,表示响应成功。如果请求的资源不存在,则通常返回404错误。HTTP状态码通常分为5种类型,分别以1~5五个数字开头,由3位整数组成。1XX通常用作实验用途。这一节主要介绍2XX、3XX、4XX、5XX等常用的几种状态码,如表1.1所示。表1.1HTTP常用状态码状态代码代码描述处理方式200请求成功获得响应的内容,进行处理201请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到爬虫中不会遇到202请求被接受,但处理尚未完成阻塞等待204服务器端已经实现了请求,但是没有返回新的信息。如果客户是用户代理,则无须为此更新自身的文档视图丢弃300该状态码不被HTTP/1.0的应用程序直接使用,只是作为3XX类型回应的默认解释。存在多个可用的被请求资源若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃301请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源重定向到分配的URL302请求到的资源在一个不同的URL处临时保存重定向到临时的URL304请求的资源未更新丢弃400非法请求丢弃401未授权丢弃403禁止丢弃404没有找到丢弃5XX回应代码以“5”开头的状态码表示服务器端发现自己出现错误,不能继续执行请求丢弃当返回的状态码为5XX时,表示应用服务器出现错误,采用简单的丢弃处理就可以解决。第1章全面剖析网络爬虫11当返回值状态码为3XX时,通常进行转向,以下是转向的代码片段,读者可以和上一节的代码自行整合到一起://若需要转向,则进行转向操作if((statusCode==HttpStatus.SC_MOVED_TEMPORARILY)||(statusCode==HttpStatus.SC_MOVED_PERMANENTLY)||(statusCode==HttpStatus.SC_SEE_OTHER)||(statusCode==HttpStatus.SC_TEMPORARY_REDIRECT)){//读取新的URL地址Headerheader=postMethod.getResponseHeader("location");if(header!=null){StringnewUrl=header.getValue();if(newUrl==null||newUrl.equals("")){newUrl="/";//使用post转向PostMethodredirect=newPostMethod(newUrl);//发送请求,做进一步处理……}}}当响应状态码为2XX时,根据表1.1的描述,我们只需要处理200和202两种状态码,其他的返回值可以不做进一步处理。200的返回状态码是成功状态码,可以直接进行网页抓取,例如://处理返回值为200的状态码if(statusCode==HttpStatus.SC_OK){input=postMethod.getResponseBodyAsStream();//得到文件名Stringfilename=path.substring(path.lastIndexOf('/')+1);//获得文件输出流output=newFileOutputStream(filename);//输出到文件inttempByte=-1;while((tempByte=input.read())>0){output.write(tempByte);}}202的响应状态码表示请求已经接受,服务器再做进一步处理。1.2宽度优先爬虫和带偏好的爬虫1.1节介绍了如何获取单个网页内容。在实际项目中,则使用爬虫程序遍历互联网,把网络中相关的网页全部抓取过来,这也体现了爬虫程序“爬”的概念。爬虫程序是如何遍历互联网,把网页全部抓取下来的呢?互联网可以看成一个超级大的“图”,而每个页面可以看作是一个“节点”。页面中的链接可以看成是图的“有向边”。因此,能够通过图121的遍历的方式对互联网这个超级大“图”进行访问。图的遍历通常可分为宽度优先遍历和深度优先遍历两种方式。但是深度优先遍历可能会在深度上过“深”地遍历或者陷入“黑洞”,大多数爬虫都不采用这种方式。另一方面,在爬取的时候,有时候也不能完全按照宽度优先遍历的方式,而是给待遍历的网页赋予一定的优先级,根据这个优先级进行遍历,这种方法称为带偏好的遍历。本小节会分别介绍宽度优先遍历和带偏好的遍历。1.2.1图的宽度优先遍历下面先来看看图的宽度优先遍历过程。图的宽度优先遍历(BFS)算法是一个分层搜索的过程,和树的层序遍历算法相同。在图中选中一个节点,作为起始节点,然后按照层次遍历的方式,一层一层地进行访问。图的宽度优先遍历需要一个队列作为保存当前节点的子节点的数据结构。具体的算法如下所示:(1)顶点V入队列。(2)当队列非空时继续执行,否则算法为空。(3)出队列,获得队头节点V,访问顶点V并标记V已经被访问。(4)查找顶点V的第一个邻接顶点col。(5)若V的邻接顶点col未被访问过,则col进队列。(6)继续查找V的其他邻接顶点col,转到步骤(5),若V的所有邻接顶点都已经被访问过,则转到步骤(2)。下面,我们以图示的方式介绍宽度优先遍历的过程,如图1.3所示。GBACDFEIH图1.3宽度优先遍历过程第1章全面剖析网络爬虫13选择A作为种子节点,则宽度优先遍历的过程,如表1.2所示。表1.2宽度优先遍历过程操作队列中的元素初始空A入队列AA出队列空BCDEF入队列BCDEFB出队列CDEFC出队列DEFD出队列EFE出队列FH入队列FHF出队列HG入队列HGH出队列GI入队列GIG出队列II出队列空在表1.2所示的遍历过程中,出队列的节点顺序既是图的宽度优先遍历的访问顺序。由此可以看出,图1.3所示的宽度优先遍历的访问顺序为A->B->C->D->E->F->H->G->I本节讲述了宽度优先遍历的理论基础,把互联网看成一个“超图”,则对这张图也可以采用宽度优先遍历的方式进行访问。下面将着重讲解如何对互联网进行宽度优先遍历。1.2.2宽度优先遍历互联网1.2.1节介绍的宽度优先遍历是从一个种子节点开始的。而实际的爬虫项目是从一系列的种子链接开始的。所谓种子链接,就好比宽度优先遍历中的种子节点(图1.3中的A节点)一样。实际的爬虫项目中种子链接可以有多个,而宽度优先遍历中的种子节点只有一个。比如,可以指定www.lietu.com和www.sina.com两个种子链接。如何定义一个链接的子节点?每个链接对应一个HTML页面或者其他文件(word、excel、pdf、jpg等),在这些文件中,只有HTML页面有相应的“子节点”,这些“子节点”就是HTML页面上对应的超链接。如www.lietu.com页面中(如图1.4所示),“招聘”、“网址”、“更多”以及页面下方的“搜索产品”,“技术文档”,“成功案例”,“猎兔新闻”,“联系猎兔”,“关于我们”,ENGLISH等都是www.lietu.com的子节点。这些子节点本身又是一个链接。对于非HTML文档,比如Excel文件等,不能从中提取超链接,因此,可以看作是图的“终端”节点。就好像图1.3中的B、C、D、I、G等节点一样。141图1.4猎兔搜索主页整个的宽度优先爬虫过程就是从一系列的种子节点开始,把这些网页中的“子节点”(也就是超链接)提取出来,放入队列中依次进行抓取。被处理过的链接需要放入一张表(通常称为Visited表)中。每次新处理一个链接之前,需要查看这个链接是否已经存在于Visited表中。如果存在,证明链接已经处理过,跳过,不做处理,否则进行下一步处理。实际的过程如图1.5所示。新解析出的URLTODO表Visited表初始URL地址解析URL图1.5宽度优先爬虫过程如图1.5所示,初始的URL地址是爬虫系统中提供的种子URL(一般在系统的配置文件中指定)。当解析这些种子URL所表示的网页时,会产生新的URL(比如从页面中的<ahref=“http://www.admin.com”中提取出http://www.admin.com这个链接)。然后,进行以下工作:(1)把解析出的链接和Visited表中的链接进行比较,若Visited表中不存在此链接,表示其未被访问过。(2)把链接放入TODO表中。(3)处理完毕后,再次从TODO表中取得一条链接,直接放入Visited表中。(4)针对这个链接所表示的网页,继续上述过程。如此循环往复。表1.3显示了对图1.3所示的页面的爬取过程。表1.3网络爬取TODO表Visited表A空BCDEFACDEFA,BDEFA,B,C第1章全面剖析网络爬虫15续表TODO表Visited表EFA,B,C,DFHA,B,C,D,EHGA,B,C,D,E,FGIA,B,C,D,E,F,HIA,B,C,D,E,F,H,G空A,B,C,D,E,F,H,G,I宽度优先遍历是爬虫中使用最广泛的一种爬虫策略,之所以使用宽度优先搜索策略,主要原因有三点:重要的网页往往离种子比较近,例如我们打开新闻网站的时候往往是最热门的新闻,随着不断的深入冲浪,所看到的网页的重要性越来越低。万维网的实际深度最多能达到17层,但到达某个网页总存在一条很短的路径。而宽度优先遍历会以最快的速度到达这个网页。宽度优先有利于多爬虫的合作抓取,多爬虫合作通常先抓取站内链接,抓取的封闭性很强。这一小节详细讲述了宽度优先遍历互联网的方法。下一节将给出一个详细的例子来说明如何实现这种方法。1.2.3Java宽度优先爬虫示例本节使用Java实现一个简易的爬虫。其中用到了HttpClient和HtmlParser两个开源工具包。HttpClient的内容之前已经做过详细的阐述。有关HtmlParser的用法,我们会在4.2节给出详细的介绍。为了便于读者理解,下面给出示例程序的结构,如图1.6所示。初始化URL队列判断条件新URL入队抽出网页中的URL下载URL指定的页面队头URL出队列退出程序URL队列为空或爬取到指定数量的网页图1.6爬虫示例程序结构161首先,需要定义图1.6中所描述的“URL队列”,这里使用一个LinkedList来实现这个队列。QueueQueueQueueQueue类:/***队列,保存将要访问的URL*/publicclassQueue{//使用链表实现队列privateLinkedListqueue=newLinkedList();//入队列publicvoidenQueue(Objectt){queue.addLast(t);}//出队列publicObjectdeQueue(){returnqueue.removeFirst();}//判断队列是否为空publicbooleanisQueueEmpty(){returnqueue.isEmpty();}//判断队列是否包含tpublicbooleancontians(Objectt){returnqueue.contains(t);}publicbooleanempty(){returnqueue.isEmpty();}}除了URL队列之外,在爬虫过程中,还需要一个数据结构来记录已经访问过的URL。每当要访问一个URL的时候,首先在这个数据结构中进行查找,如果当前的URL已经存在,则丢弃它。这个数据结构要有两个特点:结构中保存的URL不能重复。能够快速地查找(实际系统中URL的数目非常多,因此要考虑查找性能)。针对以上两点,我们选择HashSet作为存储结构。LinkQueueLinkQueueLinkQueueLinkQueue类:publicclassLinkQueue{//已访问的url集合privatestaticSetvisitedUrl=newHashSet();//待访问的url集合privatestaticQueueunVisitedUrl=newQueue();第1章全面剖析网络爬虫17//获得URL队列publicstaticQueuegetUnVisitedUrl(){returnunVisitedUrl;}//添加到访问过的URL队列中publicstaticvoidaddVisitedUrl(Stringurl){visitedUrl.add(url);}//移除访问过的URLpublicstaticvoidremoveVisitedUrl(Stringurl){visitedUrl.remove(url);}//未访问的URL出队列publicstaticObjectunVisitedUrlDeQueue(){returnunVisitedUrl.deQueue();}//保证每个URL只被访问一次publicstaticvoidaddUnvisitedUrl(Stringurl){if(url!=null&&!url.trim().equals("")&&!visitedUrl.contains(url)&&!unVisitedUrl.contians(url))unVisitedUrl.enQueue(url);}//获得已经访问的URL数目publicstaticintgetVisitedUrlNum(){returnvisitedUrl.size();}//判断未访问的URL队列中是否为空publicstaticbooleanunVisitedUrlsEmpty(){returnunVisitedUrl.empty();}}下面的代码详细说明了网页下载并处理的过程。和1.1节讲述的内容相比,它考虑了更多的方面。比如如何存储网页,设置请求超时策略等。DownLoadFileDownLoadFileDownLoadFileDownLoadFile类:publicclassDownLoadFile{/***根据URL和网页类型生成需要保存的网页的文件名,去除URL中的非文件名字符*/publicStringgetFileNameByUrl(Stringurl,StringcontentType){//移除http:url=url.substring(7);//text/html类型if(contentType.indexOf("html")!=-1)181{url=url.replaceAll("[\\?/:*|<>\"]","_")+".html";returnurl;}//如application/pdf类型else{returnurl.replaceAll("[\\?/:*|<>\"]","_")+"."+contentType.substring(contentType.lastIndexOf("/")+1);}}/***保存网页字节数组到本地文件,filePath为要保存的文件的相对地址*/privatevoidsaveToLocal(byte[]data,StringfilePath){try{DataOutputStreamout=newDataOutputStream(newFileOutputStream(newFile(filePath)));for(inti=0;i<data.length;i++)out.write(data[i]);out.flush();out.close();}catch(IOExceptione){e.printStackTrace();}}//下载URL指向的网页publicStringdownloadFile(Stringurl){StringfilePath=null;//1.生成HttpClinet对象并设置参数HttpClienthttpClient=newHttpClient();//设置HTTP连接超时5shttpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);//2.生成GetMethod对象并设置参数GetMethodgetMethod=newGetMethod(url);//设置get请求超时5sgetMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);//设置请求重试处理getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,newDefaultHttpMethodRetryHandler());//3.执行HTTPGET请求try{intstatusCode=httpClient.executeMethod(getMethod);//判断访问的状态码if(statusCode!=HttpStatus.SC_OK){第1章全面剖析网络爬虫19System.err.println("Methodfailed:"+getMethod.getStatusLine());filePath=null;}//4.处理HTTP响应内容byte[]responseBody=getMethod.getResponseBody();//读取为字节数组//根据网页url生成保存时的文件名filePath="temp\\"+getFileNameByUrl(url,getMethod.getResponseHeader("Content-Type").getValue());saveToLocal(responseBody,filePath);}catch(HttpExceptione){//发生致命的异常,可能是协议不对或者返回的内容有问题System.out.println("Pleasecheckyourprovidedhttpaddress!");e.printStackTrace();}catch(IOExceptione){//发生网络异常e.printStackTrace();}finally{//释放连接getMethod.releaseConnection();}returnfilePath;}}接下来,演示如何从获得的网页中提取URL。Java有一个非常实用的开源工具包HtmlParser,它专门针对Html页面进行处理,不仅能提取URL,还能提取文本以及你想要的任何内容。关于它的有关内容,会在第4章详细介绍。好了,让我们看看代码吧!HtmlParserToolHtmlParserToolHtmlParserToolHtmlParserTool类:publicclassHtmlParserTool{//获取一个网站上的链接,filter用来过滤链接publicstaticSet<String>extracLinks(Stringurl,LinkFilterfilter){Set<String>links=newHashSet<String>();try{Parserparser=newParser(url);parser.setEncoding("gb2312");//过滤<frame>标签的filter,用来提取frame标签里的src属性NodeFilterframeFilter=newNodeFilter(){publicbooleanaccept(Nodenode){if(node.getText().startsWith("framesrc=")){returntrue;}else{returnfalse;}};201//OrFilter来设置过滤<a>标签和<frame>标签OrFilterlinkFilter=newOrFilter(newNodeClassFilter(LinkTag.class),frameFilter);//得到所有经过过滤的标签NodeListlist=parser.extractAllNodesThatMatch(linkFilter);for(inti=0;i<list.size();i++){Nodetag=list.elementAt(i);if(taginstanceofLinkTag)//<a>标签{LinkTaglink=(LinkTag)tag;StringlinkUrl=link.getLink();//URLif(filter.accept(linkUrl))links.add(linkUrl);}else//<frame>标签{//提取frame里src属性的链接,如<framesrc="test.html"/>Stringframe=tag.getText();intstart=frame.indexOf("src=");frame=frame.substring(start);intend=frame.indexOf("");if(end==-1)end=frame.indexOf(">");StringframeUrl=frame.substring(5,end-1);if(filter.accept(frameUrl))links.add(frameUrl);}}}catch(ParserExceptione){e.printStackTrace();}returnlinks;}}最后,来看看宽度爬虫的主程序。MyCrawlerMyCrawlerMyCrawlerMyCrawler类:publicclassMyCrawler{/***使用种子初始化URL队列*@return*@paramseeds种子URL*/privatevoidinitCrawlerWithSeeds(String[]seeds){for(inti=0;i<seeds.length;i++)LinkQueue.addUnvisitedUrl(seeds[i]);}第1章全面剖析网络爬虫21/***抓取过程*@return*@paramseeds*/publicvoidcrawling(String[]seeds){//定义过滤器,提取以http://www.lietu.com开头的链接LinkFilterfilter=newLinkFilter(){publicbooleanaccept(Stringurl){if(url.startsWith("http://www.lietu.com"))returntrue;elsereturnfalse;}};//初始化URL队列initCrawlerWithSeeds(seeds);//循环条件:待抓取的链接不空且抓取的网页不多于1000while(!LinkQueue.unVisitedUrlsEmpty()&&LinkQueue.getVisitedUrlNum()<=1000){//队头URL出队列StringvisitUrl=(String)LinkQueue.unVisitedUrlDeQueue();if(visitUrl==null)continue;DownLoadFiledownLoader=newDownLoadFile();//下载网页downLoader.downloadFile(visitUrl);//该URL放入已访问的URL中LinkQueue.addVisitedUrl(visitUrl);//提取出下载网页中的URLSet<String>links=HtmlParserTool.extracLinks(visitUrl,filter);//新的未访问的URL入队for(Stringlink:links){LinkQueue.addUnvisitedUrl(link);}}}//main方法入口publicstaticvoidmain(String[]args){MyCrawlercrawler=newMyCrawler();crawler.crawling(newString[]{"http://www.lietu.com"});}}上面的主程序使用了一个LinkFilter接口,并且实现为一个内部类。这个接口的目的是221为了过滤提取出来的URL,它使得程序中提取出来的URL只会和猎兔网站相关。而不会提取其他无关的网站。代码如下:publicinterfaceLinkFilter{publicbooleanaccept(Stringurl);}1.2.4带偏好的爬虫有时,在URL队列中选择需要抓取的URL时,不一定按照队列“先进先出”的方式进行选择。而把重要的URL先从队列中“挑”出来进行抓取。这种策略也称作“页面选择”(PageSelection)。这可以使有限的网络资源照顾重要性高的网页。那么哪些网页是重要性高的网页呢?判断网页的重要性的因素很多,主要有链接的欢迎度(知道链接的重要性了吧)、链接的重要度和平均链接深度、网站质量、历史权重等主要因素。链接的欢迎度主要是由反向链接(backlinks,即指向当前URL的链接)的数量和质量决定的,我们定义为IB(P)。链接的重要度,是一个关于URL字符串的函数,仅仅考察字符串本身,比如认为“.com”和“home”的URL重要度比“.cc”和“map”高,我们定义为IL(P)。平均链接深度,根据上面所分析的宽度优先的原则计算出全站的平均链接深度,然后认为距离种子站点越近的重要性越高。我们定义为ID(P)。如果我们定义网页的重要性为I(P),那么,页面的重要度由下面的公式决定:I(P)=X*IB(P)+Y*IL(P)(1.1)其中,X和Y两个参数,用来调整IB(P)和IL(P)所占比例的大小,ID(P)由宽度优先的遍历规则保证,因此不作为重要的指标函数。如何实现最佳优先爬虫呢,最简单的方式可以使用优先级队列来实现TODO表,并且把每个URL的重要性作为队列元素的优先级。这样,每次选出来扩展的URL就是具有最高重要性的网页。有关优先级队列的介绍,请参考1.2.5节中的内容。例如,假设图1.3中节点的重要性为D>B>C>A>E>F>I>G>H,则整个遍历过程如表1.4所示。表
  • 相关资料
  • 该用户的其他资料
  • 名称/格式
  • 下载次数
  • 资料大小
  • 名称/格式
  • 下载次数
  • 资料大小

用户评论(1)

0/200
  • 10.69.3.32 2013-03-04 23:46:32

    这个是全本的 很好 值得看看 谢谢分享

上传我的资料

资料阅读排行

关闭

请选择举报的类型

关闭

提示

提交成功!

感谢您对爱问共享资料的支持,我们将尽快核实并处理您的举报信息。

关闭

提示

提交失败!

您的举报信息提交失败,请重试!

关闭

提示

重复举报!

亲爱的用户!感觉您对爱问共享资料的支持,请勿重复举报噢!

全屏 缩小 放大
收藏
资料评价:

/ 352
所需积分:0 立即下载
返回
顶部
举报
资料
关闭

温馨提示

感谢您对爱问共享资料的支持,精彩活动将尽快为您呈现,敬请期待!