`

使用 HttpClient 和 HtmlParser 实现简易爬虫(三)

阅读更多

上面的中有两个方法:

  private static void processNodeList(NodeList list, String keyword)

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

  public static void extractKeyWordText(String url, String keyword)

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

  清单 3 的代码展示了如何迭代所有的网页,更多的工作可以在此基础上展开。比如找到某个特定的网页内部节点,其实就可以在遍历所有的节点基础上来判断,看被迭代的节点是否满足特定的需要。

  使用 NodeFilter

  NodeFilter 是一个接口,任何一个自定义的 Filter 都需要实现这个接口中的 boolean accept() 方法。如果希望迭代网页节点的时候保留当前节点,则在节点条件满足的情况下返回 true;否则返回 false。HtmlParse 里提供了很多实现了 NodeFilter 接口的类,下面就一些笔者所用到的,以及常用的 Filter 做一些介绍:

  对 Filter 做逻辑操作的 Fitler 有:AndFilter,NotFilter ,OrFilter,XorFilter。

  这些 Filter 来组合不同的 Filter,形成满足两个 Filter 逻辑关系结果的 Filter。

  判断节点的孩子,兄弟,以及父亲节点情况的 Filter 有:HasChildFilter HasParentFilter,HasSiblingFilter。

  判断节点本身情况的 Filter 有 HasAttributeFilter:判读节点是否有特定属性;LinkStringFilter:判断节点是否是具有特定模式 (pattern) url 的节点;

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

 

 

 

 还有其他的一些 Filter 在这里不一一列举了,可以在 org.htmlparser.filters 下找到。

  清单 4 展示了如何使用上面提到过的一些 filter 来抽取网页中的 <a> 标签里的 href属性值,<img> 标签里的 src 属性值,以及 <frame> 标签里的 src 的属性值。

  清单4

// 获取一个网页上所有的链接和图片链接
  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 吧。以下简单的代码可以帮你解决这样的问题:

  清单5

  StringBean sb = new StringBean();

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

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

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

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

  简易爬虫的实现

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

  爬虫 (Crawler) 原理

  学过数据结构的读者都知道有向图这种数据结构。如下图所示,如果将网页看成是图中的某一个节点,而将网页中指向其他网页的链接看成是这个节点指向其他节点的边,那么我们很容易将整个 Internet 上的网页建模成一个有向图。理论上,通过遍历算法遍历该图,可以访问到Internet 上的几乎所有的网页。最简单的遍历就是宽度优先以及深度优先。以下笔者实现的简易爬虫就是使用了宽度优先的爬行策略。

  图 2. 网页关系的建模图

使用 HttpClient 和 HtmlParser 实现简易爬虫

  简易爬虫实现流程

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

 

 

图 3. 爬虫流程图

使用 HttpClient 和 HtmlParser 实现简易爬虫

  各个类的源码以及说明

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

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

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

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

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

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

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

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

  清单6 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"});
  }
}

 

 清单7 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();
  }
}

 清单8 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();
  }
}

  清单 9 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");
  }
}

 

清单 10 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);
  }
}
清单11 LinkFilter.java
package com.ie;
public interface LinkFilter {
  public boolean accept(String url);
}

 

  这些代码中关键的部分都在 HttpClient 和 HtmlParser 介绍中说明过了,其他部分也比较容易,请感兴趣的读者自行理解。

  总结

  这篇文章主要是介绍与展示了如何使用开源的 HttpClinet 包和 HtmlParser 包,以及结合这两者来给出了一个简易网络爬虫程序的实现,当然这个爬虫与实际真正的爬虫还是有所差距。由于更多的目的是关注这两个开源包的运用,加上本文篇幅有限,因此,没有对这两个开源包做非常详尽的介绍。希望这篇文章能够引导读者对 HttpClient 包和 HtmlParser 产生兴趣,从而利用他们构建强大的 JAVA 网络应用程序。

分享到:
评论

相关推荐

    使用 HttpClient 和 HtmlParser 实现简易网络爬虫

    在使用HttpClient和HtmlParser实现网络爬虫的过程中,首先需要设置开发环境。这里推荐使用Eclipse Europa作为集成开发环境(IDE),并确保安装了JDK 1.6。在Eclipse中创建一个新的JAVA工程,并将HttpClient和...

    使用 HttpClient 和 HtmlParser 实现简易爬虫

    ### 使用HttpClient和HtmlParser实现简易爬虫的知识点详解 #### 一、HttpClient与HtmlParser简介 **HttpClient简介:** HttpClient是Jakarta Commons项目中的一个重要组件,用于提供灵活且高效的HTTP协议支持。它...

    使用_HttpClient_和_HtmlParser_实现简易爬虫

    ### 使用HttpClient和HtmlParser实现简易爬虫 #### HttpClient与HtmlParser简介 在互联网技术领域,爬虫技术是一种非常重要的工具,被广泛应用于数据抓取、信息检索等方面。爬虫技术的核心在于能够高效地获取和...

    HttpClient ,jsoup和 HtmlParser ,htmllexer实现简易爬虫用到的jar包

    本话题主要涉及三个关键库:HttpClient、jsoup以及HtmlParser和htmllexer。这些库为构建简单的Java爬虫提供了必要的工具和功能。 HttpClient是Apache基金会的一个开源项目,提供了用于执行HTTP请求的强大工具集。它...

    java实现模拟登录网站最全的资料

    `使用 HttpClient 和 HtmlParser 实现简易爬虫.mht`等文件可能包含了使用这两个库实现模拟登录和爬虫的实例。 6. **网页爬虫**:结合HttpClient和HTMLParser,可以构建简单的网页爬虫。`使用httpParser提取HTML中的...

    Commons-httpClient3.1.Jar,htmllexer.jar,htmlparser.jar

    在提供的信息中,我们关注的是三个Java库:`Commons-httpClient3.1.jar`, `htmllexer.jar`, 和 `htmlparser.jar`。这些库在构建简易爬虫时扮演着关键角色。下面我们将详细探讨这三个库的功能、用途以及如何在实际...

    java开源包3

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包4

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包8

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包1

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包11

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包2

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包6

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包5

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包10

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包7

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

    java开源包9

    同时,任何第三方都可以使用OAUTH认证服务,任 何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间...

Global site tag (gtag.js) - Google Analytics