HttpClient和 HtmlParser实现爬虫

网络爬虫技术php

1       什么叫网络爬虫

 

网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更常常的称为网页追逐者),是一种按照必定的规则,自动地抓取万维网信息的程序或者脚本。另一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫html

2       网络爬虫的分类

网络爬虫按照系统结构和实现技术,大体能够分为如下几种类型:java

一、通用网络爬虫(General Purpose Web Crawler) ;node

二、主题网络爬虫(Topical Web Crawler) ;web

三、深层网络爬虫(Deep Web Crawler)。算法

实际应用中一般是将系统几种爬虫技术相互结合。数据库

2.1   通用网络爬虫

通用网络爬虫根据预先设定的一个或若干初始种子URL开始,以此得到初始网页上的URL列表,在爬行过程当中不断从URL队列中获一个的URL,进而访问并下载该页面。页面下载后页面解析器去掉页面上的HTML标记后获得页面内容,将摘要、URL等信息保存到Web数据库中,同时抽取当前页面上新的URL,保存到URL队列,直到知足系统中止条件。其工做流程如图1所示。apache

 

通用爬虫主要存在如下几方面的局限性:编程

1)  因为抓取目标是尽量大的覆盖网络,因此爬行的结果中包含大量用户不须要的网页;数组

2)  不能很好地搜索和获取信息含量密集且具备必定结构的数据;

3)  通用搜索引擎大可能是基于关键字的检索,对于支持语义信息的查询和索引擎智能化的要求难以实现。

因而可知,通用爬虫想在爬行网页时,既保证网页的质量和数量,又要保证网页的时效性是很难实现的。

2.2   主题网络爬虫

主题爬虫并不追求大的覆盖率,也不是全盘接受全部的网页和URL,它根据既定的抓取目标,有选择的访问万维网上的网页与相关的连接,获取所须要的信息,不只客服了通用爬虫存在的问题,而H-返回的数据资源更精确。主题爬虫的基本工做原理是按照预先肯定的主题,分析超连接和刚刚抓取的网页内容,获取下一个要爬行的URL,尽量保证多爬行与主题相关的网页,所以主题爬虫要解决如下关键问题:

1)  如何断定一个已经抓取的网页是否与主题相关;

2)  如何过滤掉海量的网页中与主题不相关的或者相关度较低的网页;

3)  如何有目的、有控制的抓取与特定主题相关的web页面信息;

4)  如何决定待访问URL的访问次序;

5)  如何提升主题爬虫的覆盖度;

6)  如何协调抓取目标的描述或定义与网页分析算法及候选URL排序算法之问的关系;

7)  如何寻找和发现高质量网页和关键资源。高质量网页和关键资源不只能够大大提升主题爬虫搜集Web页面的效率和质量,还能够为主题表示模型的优化等应用提供支持

2.2.1  模板设计

主题爬虫的目标是尽量多的发现和搜集与预约主题相关的网页,其最大特色在于具有分析网页内容和判别主题相关度的能力。根据主题爬虫的工做原理,下面设计了一个主题爬虫系统,主要有页面采集模块、页面分析模块、相关度计算模块、页面过滤模块和连接排序模块几部分组成,其整体功能模块结构如

图2所示。

 

页面采集模块:主要是根据待访问URL队列进行页面下载,再交给网页分析模型处理以抽取网页主题向量空间模型。该模块是任何爬虫系统都必不可少的模块。页面分析模块:该模块的功能是对采集到的页面进行分析,主要用于链接超连接排序模块和页面相关度计算模块。

页面相关度计算模块:该模块是整个系统的核心模块,主要用于评估与主题的相关度,并提供相关的爬行策略用以指导爬虫的爬行过程。URL的超连接评价得分越高,爬行的优先级就越高。其主要思想是,在系统爬行以前,页面相关度计算模块根据用户输入的关键字和初始文本信息进行学习,训练一个页面相关度评价模型。当一个被认为是主题相关的页面爬行下来以后,该页面就被送入页面相关度评价器计算其主题相关度值,若该值大于或等于给定的某阂值,则该页面就被存入页面库,不然丢弃。

页面过滤模块:过滤掉与主题无关的连接,同时将该URL及其全部隐含的子连接一并去除。经过过滤,爬虫就无需遍历与主题不相关的页面,从而保证了爬行效率。

排序模块:将过滤后页面按照优先级高低加入到待访问的URL队列里

2.2.2  主题爬虫流程设计

主题爬虫须要根据必定的网页分析算法,过滤掉与主题无关的连接,保留有用的连接并将其放入等待抓取的URL队列。而后,它会根据必定的搜索策略从待抓取的队列中选择下一个要抓取的URL,并重复上述过程,直到知足系统中止条件为止。全部被抓取网页都会被系统存储,通过必定的分析、过滤,而后创建索引,以便用户查询和检索;这一过程所获得的分析结果能够对之后的抓取过程提供反馈和指导。其工做流程如图3所示。

 

 

2.3   深层网络爬虫

1994年Dr.jillEllsworth提出DeepWeb(深层页面)的概念,即DeepWeb是指普通搜索引擎难以发现的信息内容的Web页面¨。DeepWeb中的信息量比普通的网页信息量多,并且质量更高。可是普通的搜索引擎因为技术限制而搜集不到这些高质量、高权威的信息。这些信息一般隐藏在深度Web页面的大型动态数据库中,涉及数据集成、中文语义识别等诸多领域。如此庞大的信息资源若是没有合理的、高效的方法去获取,将是巨大的损失。所以,对于深度网爬行技术的研究具备极为重大的现实意义和理论价值。

  常规的网络爬虫在运行中没法发现隐藏在普通网页中的信息和规律,缺少必定的主动性和智能性。好比须要输入用户名和密码的页面,或者包含页码导航的页面均没法爬行。深度爬虫的设计针对常规网络爬虫的这些不足,将其结构作以改进,增长了表单分析和页面状态保持两个部分,其结构如图4所示,经过分析网页的结构并将其归类为普通网页或存在更多信息的深度网页,针对深度网页构造合适的表单参数而且提交,以获得更多的页面。深度爬虫的流程图如图4所示。深度爬虫与常规爬虫的不一样是,深度爬虫在下载完成页面以后并无当即遍历其中的全部超连接,而是使用必定的算法将其进行分类,对于不一样的类别采起不一样的方法计算查询参数,并将参数再次提交到服务器。若是提交的查询参数正确,那么将会获得隐藏的页面和连接。深度爬虫的目标是尽量多地访问和收集互联网上的网页,因为深度页面是经过提交表单的方式访问,所以爬行深度页面存在如下三个方面的困难:

1)深度爬虫须要有高效的算法去应对数量巨大的深层页面数据;

2)不少服务器端DeepWeb要求校验表单输入,如用户名、密码、校验码等,若是校验失败,将不能爬到DeepWeb数据;

3)须要JavaScript等脚本支持分析客户端DeepWeb。

 

 

3       爬虫的技术实现

经过Apache Jakarta Common 中的子项目HttpClient与 纯JAVA编写的HtmlParser解析库来共同实现网络爬虫

3.1   什么是HttpClient

HttpClient提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,而且它支持 HTTP 协议最新的版本,能使得咱们能够很容易的获得某个网页的源码并保存在本地。其主要的功能介绍有:

1)     实现了全部 HTTP 的方法(GET,POST,PUT,HEAD 等)

2)     支持自动转向

3)     支持 HTTPS 协议

4)     支持代理服务器

3.2   什么是HtmlParser

HtmlParser是一个纯的java写的html标准通用标记语言下的一个应用)解析的库,它不依赖于其它的java库文件,主要用于改造或提取Html。它能超高速解析html,而且不会出错。或者绝不夸张地说,Htmlparser就是目前最好的html解析和分析的工具。不管你是想抓取网页数据仍是改造Html的内容。

它还提供了简便灵巧的类库,能够从网页中便捷的提取出指向其余网页的超连接。

3.3   HttpClient 简介

HTTP 协议是如今的因特网最重要的协议之一。除了 WEB 浏览器以外, WEB 服务,基于网络的应用程序以及日益增加的网络计算不断扩展着 HTTP 协议的角色,使得愈来愈多的应用程序须要 HTTP 协议的支持。虽然 JAVA 类库 .net 包提供了基本功能,来使用 HTTP 协议访问网络资源,可是其灵活性和功能远不能知足不少应用程序的须要。而 Jakarta Commons HttpClient 组件寻求提供更为灵活,更加高效的 HTTP 协议支持,简化基于 HTTP 协议的应用程序的建立。 HttpClient 提供了不少的特性,支持最新的 HTTP 标准,能够访问这里了解更多关于 HttpClinet 的详细信息。目前有不少的开源项目都用到了 HttpClient 提供的 HTTP功能,登录网址能够查看这些项目。本文中使用 HttpClinet 提供的类库来访问和下载 Internet上面的网页,在后续部分会详细介绍到其提供的两种请求网络资源的方法: Get 请求和 Post 请求。Apatche 提供免费的 HTTPClien t源码和 JAR 包下载,能够登录这里 下载最新的HttpClient 组件。其主要的功能介绍有:实现了全部 HTTP 的方法(GET,POST,PUT,HEAD 等)、支持自动转向、支持 HTTPS 协议、支持代理服务器等

3.4   HtmlParser 简介

当今的 Internet 上面有数亿记的网页,愈来愈多应用程序将这些网页做为分析和处理的数据对象。这些网页多为半结构化的文本,有着大量的标签和嵌套的结构。当咱们本身开发一些处理网页的应用程序时,会想到要开发一个单独的网页解析器,这一部分的工做一定须要付出至关的精力和时间。事实上,作为 JAVA 应用程序开发者, HtmlParser 为其提供了强大而灵活易用的开源类库,大大节省了写一个网页解析器的开销。 HtmlParser 是 https://sourceforge.net/projects/htmlparser/ 上活跃的一个开源项目,它提供了线性和嵌套两种方式来解析网页,主要用于 html 网页的转换(Transformation) 以及网页内容的抽取 (Extraction)。HtmlParser 有以下一些易于使用的特性:过滤器 (Filters),访问者模式 (Visitors),处理自定义标签以及易于使用的 JavaBeans。正如 HtmlParser 首页所说:它是一个快速,健壮以及严格测试过的组件;以它设计的简洁,程序运行的速度以及处理 Internet 上真实网页的能力吸引着愈来愈多的开发者。 本文中就是利用HtmlParser 里提取网页里的连接,实现简易爬虫里的关键部分。HtmlParser 最新的版本是HtmlParser1.6,能够登录这里下载其源码、 API 参考文档以及 JAR 包。

 

3.5   开发环境搭建

 

3.6   HttpClient 基本类库使用

HttpClinet 提供了几个类来支持 HTTP 访问。经过一些示例代码来熟悉和说明这些类的功能和使用。 HttpClient 提供的 HTTP 的访问主要是经过 GetMethod 类和 PostMethod 类来实现的,他们分别对应了 HTTP Get 请求与 Http Post 请求。

GetMethod

使用 GetMethod 来访问一个 URL 对应的网页,须要以下一些步骤。

  1. 生成一个 HttpClinet 对象并设置相应的参数。
  2. 生成一个 GetMethod 对象并设置响应的参数。
  3. 用 HttpClinet 生成的对象来执行 GetMethod 生成的 Get 方法。
  4. 处理响应状态码。
  5. 若响应正常,处理 HTTP 响应内容。
  6. 释放链接。

以下的代码展现了这些步骤,其中的注释对代码进行了较详细的说明。

代码以下:

/* 1 生成 HttpClinet 对象并设置参数*/

  HttpClient httpClient=new HttpClient();

  //设置 Http 链接超时为5秒

httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);

 

  /*2 生成 GetMethod 对象并设置参数*/

  GetMethod getMethod=new GetMethod(url);  

  //设置 get 请求超时为 5 秒

getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);

  //设置请求重试处理,用的是默认的重试处理:请求三次

getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,

          new DefaultHttpMethodRetryHandler());

 

  /*3 执行 HTTP GET 请求*/

  try{

           int statusCode = httpClient.executeMethod(getMethod);

           /*4 判断访问的状态码*/

      if (statusCode != HttpStatus.SC_OK)

      {

System.err.println("Method failed: "+ getMethod.getStatusLine());

      }

 

      /*5 处理 HTTP 响应内容*/

      //HTTP响应头部信息,这里简单打印

  Header[] headers=getMethod.getResponseHeaders();

      for(Header  h:  headers)

               System.out.println(h.getName()+" "+h.getValue());*/

      //读取 HTTP 响应内容,这里简单打印网页内容

      byte[] responseBody = getMethod.getResponseBody();//读取为字节数组

System.out.println(new String(responseBody));

      //读取为 InputStream,在网页内容数据量大时候推荐使用

      InputStream response = getMethod.getResponseBodyAsStream();//

      …

}

catch (HttpException e)

{

           // 发生致命的异常,多是协议不对或者返回的内容有问题

                   System.out.println("Please check your provided http address!");

e.printStackTrace();

          }

catch (IOException e)

  {

                 // 发生网络异常

                 e.printStackTrace();

          } finally {

                          /*6 .释放链接*/

                          getMethod.releaseConnection();                

                     }

这里值得注意的几个地方是:

  1. 设置链接超时和请求超时,这两个超时的意义不一样,须要分别设置。
  2. 响应状态码的处理。
  3. 返回的结果能够为字节数组,也能够为 InputStream,然后者在网页内容数据量较大的时候推荐使用。

在处理返回结果的时候能够根据本身的须要,进行相应的处理。如笔者是须要保存网页到本地,所以就能够写一个 saveToLocaleFile(byte[] data, String filePath) 的方法,将字节数组保存成本地文件。后续的简易爬虫部分会有相应的介绍。

PostMethod

PostMethod 方法与 GetMethod 方法的使用步骤大致相同。可是因为 PostMethod 使用的是HTTP 的 Post 请求,于是请求参数的设置与 GetMethod 有所不一样。在 GetMethod 中,请求的参数直接写在 URL 里,通常以这样形式出现:http://hostname:port//file?name1=value1&name2=value …。请求参数是 name,value 对。好比我想获得百度搜索“Thinking In Java”的结果网页,就可使 GetMethod 的构造方法中的 url 为:http://www.baidu.com/s?wd=Thinking+In+Java 。而 PostMethod 则能够模拟网页里表单提交的过程,经过设置表单里 post 请求参数的值,来动态的得到返回的网页结果。如下的代码展现了如何建立一个 Post 对象,并设置相应的请求参数。

代码:

PostMethod postMethod = new PostMethod("http://dict.cn/");

postMethod.setRequestBody(new NameValuePair[]{new NameValuePair("q","java")});

3.7   HtmlParser 基本类库使用

HtmlParser 提供了强大的类库来处理 Internet 上的网页,能够实现对网页特定内容的提取和修改。下面经过几个例子来介绍 HtmlParser 的一些使用。这些例子其中的代码,有部分用在了后面介绍的简易爬虫中。如下全部的代码和方法都在在类 HtmlParser.Test.java 里,这是笔者编写的一个用来测试 HtmlParser 用法的类。

  • 迭代遍历网页全部节点

网页是一个半结构化的嵌套文本文件,有相似 XML 文件的树形嵌套结构。使用HtmlParser 可让咱们轻易的迭代遍历网页的全部节点。以下代码展现了如何来实现这个功能。

代码

// 循环访问全部节点,输出包含关键字的值节点

         public static void extractKeyWordText(String url, String keyword) {

                 try {

            //生成一个解析器对象,用网页的 url 做为参数

                          Parser parser = new Parser(url);

                          //设置网页的编码,这里只是请求了一个 gb2312 编码网页

                          parser.setEncoding("gb2312");

                          //迭代全部节点, null 表示不使用 NodeFilter

                          NodeList list = parser.parse(null);

            //从初始的节点列表跌倒全部的节点

                          processNodeList(list, keyword);

                 } catch (ParserException e) {

                          e.printStackTrace();

                 }

         }

 

         private static void processNodeList(NodeList list, String keyword) {

                 //迭代开始

                 SimpleNodeIterator iterator = list.elements();

                 while (iterator.hasMoreNodes()) {

                          Node node = iterator.nextNode();

                          //获得该节点的子节点列表

                          NodeList childList = node.getChildren();

                          //孩子节点为空,说明是值节点

                          if (null == childList)

                          {

                                   //获得值节点的值

                                   String result = node.toPlainTextString();

                                   //若包含关键字,则简单打印出来文本

                                   if (result.indexOf(keyword) != -1)

                                            System.out.println(result);

                          } //end if

                          //孩子节点不为空,继续迭代该孩子节点

                          else

                          {

                                   processNodeList(childList, keyword);

                          }//end else

                 }//end wile

         }

上面的中有两个方法:

  1. private static void processNodeList(NodeList list, String keyword)

该方法是用相似深度优先的方法来迭代遍历整个网页节点,将那些包含了某个关键字的值节点的值打印出来。

  1. public static void extractKeyWordText(String url, String keyword)

该方法生成针对 String 类型的 url 变量表明的某个特定网页的解析器,调用 1中的方法实现简单的遍历。

以上的代码展现了如何迭代全部的网页,更多的工做能够在此基础上展开。好比找到某个特定的网页内部节点,其实就能够在遍历全部的节点基础上来判断,看被迭代的节点是否知足特定的须要。

  1. 使用 NodeFilter

NodeFilter 是一个接口,任何一个自定义的 Filter 都须要实现这个接口中的 boolean accept() 方法。若是但愿迭代网页节点的时候保留当前节点,则在节点条件知足的状况下返回 true;不然返回 false。HtmlParse 里提供了不少实现了 NodeFilter 接口的类,下面就一些笔者所用到的,以及经常使用的 Filter 作一些介绍:

  1. 对 Filter 作逻辑操做的 Fitler 有:AndFilterNotFilterOrFilterXorFilter
  2. 这些 Filter 来组合不一样的 Filter,造成知足两个 Filter 逻辑关系结果的 Filter。
  3. 判断节点的孩子,兄弟,以及父亲节点状况的 Filter 有:HasChildFilterHasParentFilterHasSiblingFilter
  4. 判断节点自己状况的 Filter 有 HasAttributeFilter:判读节点是否有特定属性;LinkStringFilter:判断节点是不是具备特定模式 (pattern) url 的节点;

TagNameFilter:判断节点是否具备特定的名字;NodeClassFilter:判读节点是不是某个 HtmlParser 定义好的 Tag 类型。在 org.htmlparser.tags 包下有对应 Html标签的各类 Tag,例如 LinkTag,ImgeTag 等。

还有其余的一些 Filter 在这里不一一列举了,能够org.htmlparser.filters 下找到。以下代码展现了如何使用上面提到过的一些 filter 来抽取网页中的 <a> 标签里的 href属性值,<img> 标签里的 src 属性值,以及 <frame> 标签里的 src 的属性值。

代码

// 获取一个网页上全部的连接和图片连接

public static void extracLinks(String url) {

       try {

         Parser parser = new Parser(url);

         parser.setEncoding("gb2312");

//过滤 <frame> 标签的 filter,用来提取 frame 标签里的 src 属性所、表示的连接

         NodeFilter frameFilter = new NodeFilter() {

public boolean accept(Node node) {

         if (node.getText().startsWith("frame src=")) {

                 return true;

         } else {

                 return false;

         }

         }

};

//OrFilter 来设置过滤 <a> 标签,<img> 标签和 <frame> 标签,三个标签是 or 的关系

OrFilte rorFilter = new OrFilter(new NodeClassFilter(LinkTag.class), new

NodeClassFilter(ImageTag.class));

          OrFilter linkFilter = new OrFilter(orFilter, frameFilter);

         //获得全部通过过滤的标签

         NodeList list = parser.extractAllNodesThatMatch(linkFilter);

         for (int i = 0; i < list.size(); i++) {

                  Node tag = list.elementAt(i);

                 if (tag instanceof LinkTag)//<a> 标签

                 {

                          LinkTag link = (LinkTag) tag;

                          String linkUrl = link.getLink();//url

                          String text = link.getLinkText();//连接文字

                          System.out.println(linkUrl + "**********" + text);

                 }

                 else if (tag instanceof ImageTag)//<img> 标签

                 {

                          ImageTag image = (ImageTag) list.elementAt(i);

                          System.out.print(image.getImageURL() + "********");//图片地址

                          System.out.println(image.getText());//图片文字

                 }

                 else//<frame> 标签

                 {

//提取 frame 里 src 属性的连接如 <frame src="test.html"/>

                          String frame = tag.getText();

                          int start = frame.indexOf("src=");

                          frame = frame.substring(start);

                          int end = frame.indexOf(" ");

                          if (end == -1)

                                   end = frame.indexOf(">");

                          frame = frame.substring(5, end - 1);

                          System.out.println(frame);

                 }

         }

} catch (ParserException e) {

                          e.printStackTrace();

}

}

简单强大的 StringBean

若是你想要网页中去掉全部的标签后剩下的文本,那就是用 StringBean 吧。如下简单的代码能够帮你解决这样的问题:

StringBean sb = new StringBean();

sb.setLinks(false);//设置结果中去点连接

sb.setURL(url);//设置你所须要滤掉网页标签的页面 url

System.out.println(sb.getStrings());//打印结果

HtmlParser 提供了强大的类库来处理网页,因为本文旨在简单的介绍,所以只是将与笔者后续爬虫部分有关的关键类库进行了示例说明。感兴趣的读者能够专门来研究一下 HtmlParser 更为强大的类库。

简易爬虫的实现

HttpClient 提供了便利的 HTTP 协议访问,使得咱们能够很容易的获得某个网页的源码并保存在本地;HtmlParser 提供了如此简便灵巧的类库,能够从网页中便捷的提取出指向其余网页的超连接。笔者结合这两个开源包,构建了一个简易的网络爬虫。

网页关系的建模图

 

简易爬虫实现流程

在看简易爬虫的实现代码以前,先介绍一下简易爬虫爬取网页的流程。

爬虫流程图

 

各个类的源码以及说明

对应上面的流程图,简易爬虫由下面几个类组成,各个类职责以下:

Crawler.java:爬虫的主方法入口所在的类,实现爬取的主要流程。

LinkDb.java:用来保存已经访问的 url 和待爬取的 url 的类,提供url出对入队操做。

Queue.java: 实现了一个简单的队列,在 LinkDb.java 中使用了此类。

FileDownloader.java:用来下载 url 所指向的网页。

HtmlParserTool.java: 用来抽取出网页中的连接。

LinkFilter.java:一个接口,实现其 accept() 方法用来对抽取的连接进行过滤。

下面是各个类的源码,代码中的注释有比较详细的说明。

Crawler.java

package com.ie;

 

import java.util.Set;

public class Crawler {

         /* 使用种子 url 初始化 URL 队列*/

         private void initCrawlerWithSeeds(String[] seeds)

         {

                 for(int i=0;i<seeds.length;i++)

                          LinkDB.addUnvisitedUrl(seeds[i]);

         }

        

         /* 爬取方法*/

         public void crawling(String[] seeds)

         {

                 LinkFilter filter = new LinkFilter(){

                          //提取以 http://www.twt.edu.cn 开头的连接

                          public boolean accept(String url) {

                                   if(url.startsWith("http://www.twt.edu.cn"))

                                            return true;

                                   else

                                            return false;

                          }

                 };

                 //初始化 URL 队列

                 initCrawlerWithSeeds(seeds);

                 //循环条件:待抓取的连接不空且抓取的网页很少于 1000

                 while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=1000)

                 {

                          //队头 URL 出对

                          String visitUrl=LinkDB.unVisitedUrlDeQueue();

                          if(visitUrl==null)

                                   continue;

                          FileDownLoader downLoader=new FileDownLoader();

                          //下载网页

                          downLoader.downloadFile(visitUrl);

                          //该 url 放入到已访问的 URL 中

                          LinkDB.addVisitedUrl(visitUrl);

                          //提取出下载网页中的 URL

                         

                          Set<String> links=HtmlParserTool.extracLinks(visitUrl,filter);

                          //新的未访问的 URL 入队

                          for(String link:links)

                          {

                                            LinkDB.addUnvisitedUrl(link);

                          }

                 }

         }

         //main 方法入口

         public static void main(String[]args)

         {

                 Crawler crawler = new Crawler();

                 crawler.crawling(new String[]{"http://www.twt.edu.cn"});

         }

}

LinkDb.java

package com.ie;

 

import java.util.HashSet;

import java.util.Set;

 

/**

 * 用来保存已经访问过 Url 和待访问的 Url 的类

 */

public class LinkDB {

 

         //已访问的 url 集合

         private static Set<String> visitedUrl = new HashSet<String>();

         //待访问的 url 集合

         private static Queue<String> unVisitedUrl = new Queue<String>();

 

        

         public static Queue<String> getUnVisitedUrl() {

                 return unVisitedUrl;

         }

 

         public static void addVisitedUrl(String url) {

                 visitedUrl.add(url);

         }

 

         public static void removeVisitedUrl(String url) {

                 visitedUrl.remove(url);

         }

 

         public static String unVisitedUrlDeQueue() {

                 return unVisitedUrl.deQueue();

         }

 

         // 保证每一个 url 只被访问一次

         public static void addUnvisitedUrl(String url) {

                 if (url != null && !url.trim().equals("")

 && !visitedUrl.contains(url)

                                   && !unVisitedUrl.contians(url))

                          unVisitedUrl.enQueue(url);

         }

 

         public static int getVisitedUrlNum() {

                 return visitedUrl.size();

         }

 

         public static boolean unVisitedUrlsEmpty() {

                 return unVisitedUrl.empty();

         }

}

Queue.java

package com.ie;

 

import java.util.LinkedList;

/**

 * 数据结构队列

 */

public class Queue<T> {

 

         private LinkedList<T> queue=new LinkedList<T>();

        

         public void enQueue(T t)

         {

                 queue.addLast(t);

         }

        

         public T deQueue()

         {

                 return queue.removeFirst();

         }

        

         public boolean isQueueEmpty()

         {

                 return queue.isEmpty();

         }

        

         public boolean contians(T t)

         {

                 return queue.contains(t);

         }

        

         public boolean empty()

         {

                 return queue.isEmpty();

         }

}

 FileDownLoader.java

package com.ie;

 

import java.io.DataOutputStream;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;

import org.apache.commons.httpclient.HttpClient;

import org.apache.commons.httpclient.HttpException;

import org.apache.commons.httpclient.HttpStatus;

import org.apache.commons.httpclient.methods.GetMethod;

import org.apache.commons.httpclient.params.HttpMethodParams;

 

public class FileDownLoader {

        

         /**根据 url 和网页类型生成须要保存的网页的文件名

          *去除掉 url 中非文件名字符

          */

         public  String getFileNameByUrl(String url,String contentType)

         {

                 url=url.substring(7);//remove http://

                 if(contentType.indexOf("html")!=-1)//text/html

                 {

                          url= url.replaceAll("[\\?/:*|<>\"]", "_")+".html";

                          return url;

                 }

                 else//如application/pdf

                 {

return url.replaceAll("[\\?/:*|<>\"]", "_")+"."+ \

          contentType.substring(contentType.lastIndexOf("/")+1);

                 }       

         }

 

         /**保存网页字节数组到本地文件

          * filePath 为要保存的文件的相对地址

          */

         private void saveToLocal(byte[] data,String filePath)

         {

                 try {

                          DataOutputStream out=new DataOutputStream(

new FileOutputStream(new File(filePath)));

                          for(int i=0;i<data.length;i++)

                          out.write(data[i]);

                          out.flush();

                          out.close();

                 } catch (IOException e) {

                          e.printStackTrace();

                 }

         }

 

         /*下载 url 指向的网页*/

         public String  downloadFile(String url)

         {

                   String filePath=null;

                   /* 1.生成 HttpClinet 对象并设置参数*/

                   HttpClient httpClient=new HttpClient();

                   //设置 Http 链接超时 5s

                            httpClient.getHttpConnectionManager().getParams().

setConnectionTimeout(5000);

                  

                   /*2.生成 GetMethod 对象并设置参数*/

                    GetMethod getMethod=new GetMethod(url);  

                   //设置 get 请求超时 5s

                   getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);

                   //设置请求重试处理

                   getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,

                          new DefaultHttpMethodRetryHandler());

                  

                   /*3.执行 HTTP GET 请求*/

                   try{

                            int statusCode = httpClient.executeMethod(getMethod);

                            //判断访问的状态码

                            if (statusCode != HttpStatus.SC_OK)

                            {

System.err.println("Method failed: "+ getMethod.getStatusLine());

                                     filePath=null;

                            }

                           

                            /*4.处理 HTTP 响应内容*/

 byte[] responseBody = getMethod.getResponseBody();//读取为字节数组

                            //根据网页 url 生成保存时的文件名

filePath="temp\\"+getFileNameByUrl(url,

                    getMethod.getResponseHeader("Content-Type").getValue());

                          saveToLocal(responseBody,filePath);

                   } catch (HttpException e) {

                                      // 发生致命的异常,多是协议不对或者返回的内容有问题

                                      System.out.println("Please check your provided http

address!");

                                      e.printStackTrace();

                                     } catch (IOException e) {

                                      // 发生网络异常

                                      e.printStackTrace();

                                     } finally {

                                      // 释放链接

                                      getMethod.releaseConnection();              

                                     }

                                     return filePath;

         }

         //测试的 main 方法

         public static void main(String[]args)

         {

                 FileDownLoader downLoader = new FileDownLoader();

                 downLoader.downloadFile("http://www.twt.edu.cn");

         }

}

HtmlParserTool.java

package com.ie;

 

import java.util.HashSet;

import java.util.Set;

 

import org.htmlparser.Node;

import org.htmlparser.NodeFilter;

import org.htmlparser.Parser;

import org.htmlparser.filters.NodeClassFilter;

import org.htmlparser.filters.OrFilter;

import org.htmlparser.tags.LinkTag;

import org.htmlparser.util.NodeList;

import org.htmlparser.util.ParserException;

 

public class HtmlParserTool {

         // 获取一个网站上的连接,filter 用来过滤连接

         public static Set<String> extracLinks(String url,LinkFilter filter) {

 

                 Set<String> links = new HashSet<String>();

                 try {

                          Parser parser = new Parser(url);

                          parser.setEncoding("gb2312");

                          // 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性所表示的连接

                          NodeFilter frameFilter = new NodeFilter() {

                                   public boolean accept(Node node) {

                                            if (node.getText().startsWith("frame src=")) {

                                                    return true;

                                            } else {

                                                    return false;

                                            }

                                   }

                          };

                          // OrFilter 来设置过滤 <a> 标签,和 <frame> 标签

                          OrFilter linkFilter = new OrFilter(new NodeClassFilter(

                                            LinkTag.class), frameFilter);

                          // 获得全部通过过滤的标签

                          NodeList list = parser.extractAllNodesThatMatch(linkFilter);

                          for (int i = 0; i < list.size(); i++) {

                                   Node tag = list.elementAt(i);

                                   if (tag instanceof LinkTag)// <a> 标签

                                   {

                                            LinkTag link = (LinkTag) tag;

                                            String linkUrl = link.getLink();// url

                                            if(filter.accept(linkUrl))

                                                    links.add(linkUrl);

                                   } else// <frame> 标签

                                   {

                         // 提取 frame 里 src 属性的连接如 <frame src="test.html"/>

                                            String frame = tag.getText();

                                            int start = frame.indexOf("src=");

                                            frame = frame.substring(start);

                                            int end = frame.indexOf(" ");

                                            if (end == -1)

                                                    end = frame.indexOf(">");

                                            String frameUrl = frame.substring(5, end - 1);

                                           if(filter.accept(frameUrl))

                                                    links.add(frameUrl);

                                   }

                          }

                 } catch (ParserException e) {

                          e.printStackTrace();

                 }

                 return links;

         }

         //测试的 main 方法

         public static void main(String[]args)

         {

Set<String> links = HtmlParserTool.extracLinks(

"http://www.twt.edu.cn",new LinkFilter()

                 {

                          //提取以 http://www.twt.edu.cn 开头的连接

                          public boolean accept(String url) {

                                   if(url.startsWith("http://www.twt.edu.cn"))

                                            return true;

                                   else

                                            return false;

                          }

                         

                 });

                 for(String link : links)

                          System.out.println(link);

         }

}

LinkFilter.java

package com.ie;

 

public interface LinkFilter {

         public boolean accept(String url);

}

 HttpClient 和 HtmlParser 实现简易爬虫HttpClient 和 HtmlParser 实现简易爬虫HttpClient 和 HtmlParser 实现简易爬虫HttpClient 和 HtmlParser 实现简易爬虫HttpClient 和 HtmlParser 实现简易爬虫HttpClient 和 HtmlParser 实现简易爬虫

相关文章
相关标签/搜索