java爬虫demo

来源:互联网 发布:怪物猎人ol捏脸数据库 编辑:程序博客网 时间:2024/05/16 01:13

说到爬虫,使用Java本身自带的URLConnection可以实现一些基本的抓取页面的功能,但是对于一些比较高级的功能,比如重定向的处理,HTML标记的去除,仅仅使用URLConnection还是不够的。

在这里我们可以使用HttpClient这个第三方jar包。

接下来我们使用HttpClient简单的写一个爬去百度的Demo:

[cpp] view plain copy
 print?
  1. import java.io.FileOutputStream;  
  2. import java.io.InputStream;  
  3. import java.io.OutputStream;  
  4. import org.apache.commons.httpclient.HttpClient;  
  5. import org.apache.commons.httpclient.HttpStatus;  
  6. import org.apache.commons.httpclient.methods.GetMethod;  
  7. /** 
  8.  *  
  9.  * @author CallMeWhy 
  10.  *  
  11.  */  
  12. public class Spider {  
  13.  private static HttpClient httpClient = new HttpClient();  
  14.  /** 
  15.   * @param path 
  16.   *            目标网页的链接 
  17.   * @return 返回布尔值,表示是否正常下载目标页面 
  18.   * @throws Exception 
  19.   *             读取网页流或写入本地文件流的IO异常 
  20.   */  
  21.  public static boolean downloadPage(String path) throws Exception {  
  22.   // 定义输入输出流  
  23.   InputStream input = null;  
  24.   OutputStream output = null;  
  25.   // 得到 post 方法  
  26.   GetMethod getMethod = new GetMethod(path);  
  27.   // 执行,返回状态码  
  28.   int statusCode = httpClient.executeMethod(getMethod);  
  29.   // 针对状态码进行处理  
  30.   // 简单起见,只处理返回值为 200 的状态码  
  31.   if (statusCode == HttpStatus.SC_OK) {  
  32.    input = getMethod.getResponseBodyAsStream();  
  33.    // 通过对URL的得到文件名  
  34.    String filename = path.substring(path.lastIndexOf('/') + 1)  
  35.      + ".html";  
  36.    // 获得文件输出流  
  37.    output = new FileOutputStream(filename);  
  38.    // 输出到文件  
  39.    int tempByte = -1;  
  40.    while ((tempByte = input.read()) > 0) {  
  41.     output.write(tempByte);  
  42.    }  
  43.    // 关闭输入流  
  44.    if (input != null) {  
  45.     input.close();  
  46.    }  
  47.    // 关闭输出流  
  48.    if (output != null) {  
  49.     output.close();  
  50.    }  
  51.    return true;  
  52.   }  
  53.   return false;  
  54.  }  
  55.  public static void main(String[] args) {  
  56.   try {  
  57.    // 抓取百度首页,输出  
  58.    Spider.downloadPage("<a target=_blank href="http://www.baidu.com/" style="color: rgb(0, 102, 153); text-decoration: none;">http://www.baidu.com</a>");  
  59.   } catch (Exception e) {  
  60.    e.printStackTrace();  
  61.   }  
  62.  }  
  63. }  


但是这样基本的爬虫是不能满足各色各样的爬虫需求的。

先来介绍宽度优先爬虫。

宽度优先相信大家都不陌生,简单说来可以这样理解宽度优先爬虫。

我们把互联网看作一张超级大的有向图,每一个网页上的链接都是一个有向边,每一个文件或没有链接的纯页面则是图中的终点:


宽度优先爬虫就是这样一个爬虫,爬走在这个有向图上,从根节点开始一层一层往外爬取新的节点的数据。

宽度遍历算法如下所示:

(1) 顶点 V 入队列。
(2) 当队列非空时继续执行,否则算法为空。
(3) 出队列,获得队头节点 V,访问顶点 V 并标记 V 已经被访问。
(4) 查找顶点 V 的第一个邻接顶点 col。
(5) 若 V 的邻接顶点 col 未被访问过,则 col 进队列。
(6) 继续查找 V 的其他邻接顶点 col,转到步骤(5),若 V 的所有邻接顶点都已经被访问过,则转到步骤(2)。

按照宽度遍历算法,上图的遍历顺序为:A->B->C->D->E->F->H->G->I,这样一层一层的遍历下去。

而宽度优先爬虫其实爬取的是一系列的种子节点,和图的遍历基本相同。

我们可以把需要爬取页面的URL都放在一个TODO表中,将已经访问的页面放在一个Visited表中:


则宽度优先爬虫的基本流程如下:

(1) 把解析出的链接和 Visited 表中的链接进行比较,若 Visited 表中不存在此链接, 表示其未被访问过。
(2) 把链接放入 TODO 表中。
(3) 处理完毕后,从 TODO 表中取得一条链接,直接放入 Visited 表中。
(4) 针对这个链接所表示的网页,继续上述过程。如此循环往复。

下面我们就来一步一步制作一个宽度优先的爬虫。

首先,对于先设计一个数据结构用来存储TODO表, 考虑到需要先进先出所以采用队列,自定义一个Quere类:

[java] view plain copy
 print?
  1. import java.util.LinkedList;  
  2. /** 
  3.  * 自定义队列类 保存TODO表 
  4.  */  
  5. public class Queue {  
  6.  /** 
  7.   * 定义一个队列,使用LinkedList实现 
  8.   */  
  9.  private LinkedList<Object> queue = new LinkedList<Object>(); // 入队列  
  10.  /** 
  11.   * 将t加入到队列中 
  12.   */  
  13.  public void enQueue(Object t) {  
  14.   queue.addLast(t);  
  15.  }  
  16.  /** 
  17.   * 移除队列中的第一项并将其返回 
  18.   */  
  19.  public Object deQueue() {  
  20.   return queue.removeFirst();  
  21.  }  
  22.  /** 
  23.   * 返回队列是否为空 
  24.   */  
  25.  public boolean isQueueEmpty() {  
  26.   return queue.isEmpty();  
  27.  }  
  28.  /** 
  29.   * 判断并返回队列是否包含t 
  30.   */  
  31.  public boolean contians(Object t) {  
  32.   return queue.contains(t);  
  33.  }  
  34.  /** 
  35.   * 判断并返回队列是否为空 
  36.   */  
  37.  public boolean empty() {  
  38.   return queue.isEmpty();  
  39.  }  
  40. }  



还需要一个数据结构来记录已经访问过的 URL,即Visited表。

考虑到这个表的作用,每当要访问一个 URL 的时候,首先在这个数据结构中进行查找,如果当前的 URL 已经存在,则丢弃这个URL任务。

这个数据结构需要不重复并且能快速查找,所以选择HashSet来存储。

综上,我们另建一个SpiderQueue类来保存Visited表和TODO表:

[java] view plain copy
 print?
  1. import java.util.HashSet;  
  2. import java.util.Set;  
  3. /** 
  4.  * 自定义类 保存Visited表和unVisited表 
  5.  */  
  6. public class SpiderQueue {  
  7.  /** 
  8.   * 已访问的url集合,即Visited表 
  9.   */  
  10.  private static Set<Object> visitedUrl = new HashSet<>();  
  11.  /** 
  12.   * 添加到访问过的 URL 队列中 
  13.   */  
  14.  public static void addVisitedUrl(String url) {  
  15.   visitedUrl.add(url);  
  16.  }  
  17.  /** 
  18.   * 移除访问过的 URL 
  19.   */  
  20.  public static void removeVisitedUrl(String url) {  
  21.   visitedUrl.remove(url);  
  22.  }  
  23.  /** 
  24.   * 获得已经访问的 URL 数目 
  25.   */  
  26.  public static int getVisitedUrlNum() {  
  27.   return visitedUrl.size();  
  28.  }  
  29.  /** 
  30.   * 待访问的url集合,即unVisited表 
  31.   */  
  32.  private static Queue unVisitedUrl = new Queue();  
  33.  /** 
  34.   * 获得UnVisited队列 
  35.   */  
  36.  public static Queue getUnVisitedUrl() {  
  37.   return unVisitedUrl;  
  38.  }  
  39.  /** 
  40.   * 未访问的unVisitedUrl出队列 
  41.   */  
  42.  public static Object unVisitedUrlDeQueue() {  
  43.   return unVisitedUrl.deQueue();  
  44.  }  
  45.  /** 
  46.   * 保证添加url到unVisitedUrl的时候每个 URL只被访问一次 
  47.   */  
  48.  public static void addUnvisitedUrl(String url) {  
  49.   if (url != null && !url.trim().equals("") && !visitedUrl.contains(url)  
  50.     && !unVisitedUrl.contians(url))  
  51.    unVisitedUrl.enQueue(url);  
  52.  }  
  53.  /** 
  54.   * 判断未访问的 URL队列中是否为空 
  55.   */  
  56.  public static boolean unVisitedUrlsEmpty() {  
  57.   return unVisitedUrl.empty();  
  58.  }  
  59. }  


上面是一些自定义类的封装,接下来就是一个定义一个用来下载网页的工具类,我们将其定义为DownTool类:

[java] view plain copy
 print?
  1. package controller;  
  2. import java.io.*;  
  3. import org.apache.commons.httpclient.*;  
  4. import org.apache.commons.httpclient.methods.*;  
  5. import org.apache.commons.httpclient.params.*;  
  6. public class DownTool {  
  7.  /** 
  8.   * 根据 URL 和网页类型生成需要保存的网页的文件名,去除 URL 中的非文件名字符 
  9.   */  
  10.  private String getFileNameByUrl(String url, String contentType) {  
  11.   // 移除 "http://" 这七个字符  
  12.   url = url.substring(7);  
  13.   // 确认抓取到的页面为 text/html 类型  
  14.   if (contentType.indexOf("html") != -1) {  
  15.    // 把所有的url中的特殊符号转化成下划线  
  16.    url = url.replaceAll("[\\?/:*|<>\"]""_") + ".html";  
  17.   } else {  
  18.    url = url.replaceAll("[\\?/:*|<>\"]""_") + "."  
  19.      + contentType.substring(contentType.lastIndexOf("/") + 1);  
  20.   }  
  21.   return url;  
  22.  }  
  23.  /** 
  24.   * 保存网页字节数组到本地文件,filePath 为要保存的文件的相对地址 
  25.   */  
  26.  private void saveToLocal(byte[] data, String filePath) {  
  27.   try {  
  28.    DataOutputStream out = new DataOutputStream(new FileOutputStream(  
  29.      new File(filePath)));  
  30.    for (int i = 0; i < data.length; i++)  
  31.     out.write(data[i]);  
  32.    out.flush();  
  33.    out.close();  
  34.   } catch (IOException e) {  
  35.    e.printStackTrace();  
  36.   }  
  37.  }  
  38.  // 下载 URL 指向的网页  
  39.  public String downloadFile(String url) {  
  40.   String filePath = null;  
  41.   // 1.生成 HttpClinet对象并设置参数  
  42.   HttpClient httpClient = new HttpClient();  
  43.   // 设置 HTTP连接超时 5s  
  44.   httpClient.getHttpConnectionManager().getParams()  
  45.     .setConnectionTimeout(5000);  
  46.   // 2.生成 GetMethod对象并设置参数  
  47.   GetMethod getMethod = new GetMethod(url);  
  48.   // 设置 get请求超时 5s  
  49.   getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);  
  50.   // 设置请求重试处理  
  51.   getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,  
  52.     new DefaultHttpMethodRetryHandler());  
  53.   // 3.执行GET请求  
  54.   try {  
  55.    int statusCode = httpClient.executeMethod(getMethod);  
  56.    // 判断访问的状态码  
  57.    if (statusCode != HttpStatus.SC_OK) {  
  58.     System.err.println("Method failed: "  
  59.       + getMethod.getStatusLine());  
  60.     filePath = null;  
  61.    }  
  62.    // 4.处理 HTTP 响应内容  
  63.    byte[] responseBody = getMethod.getResponseBody();// 读取为字节数组  
  64.    // 根据网页 url 生成保存时的文件名  
  65.    filePath = "temp\\"  
  66.      + getFileNameByUrl(url,  
  67.        getMethod.getResponseHeader("Content-Type")  
  68.          .getValue());  
  69.    saveToLocal(responseBody, filePath);  
  70.   } catch (HttpException e) {  
  71.    // 发生致命的异常,可能是协议不对或者返回的内容有问题  
  72.    System.out.println("请检查你的http地址是否正确");  
  73.    e.printStackTrace();  
  74.   } catch (IOException e) {  
  75.    // 发生网络异常  
  76.    e.printStackTrace();  
  77.   } finally {  
  78.    // 释放连接  
  79.    getMethod.releaseConnection();  
  80.   }  
  81.   return filePath;  
  82.  }  
  83. }  

在这里我们需要一个HtmlParserTool类来处理Html标记:

[java] view plain copy
 print?
  1. package controller;  
  2. import java.util.HashSet;  
  3. import java.util.Set;  
  4. import org.htmlparser.Node;  
  5. import org.htmlparser.NodeFilter;  
  6. import org.htmlparser.Parser;  
  7. import org.htmlparser.filters.NodeClassFilter;  
  8. import org.htmlparser.filters.OrFilter;  
  9. import org.htmlparser.tags.LinkTag;  
  10. import org.htmlparser.util.NodeList;  
  11. import org.htmlparser.util.ParserException;  
  12. import model.LinkFilter;  
  13. public class HtmlParserTool {  
  14.  // 获取一个网站上的链接,filter 用来过滤链接  
  15.  public static Set<String> extracLinks(String url, LinkFilter filter) {  
  16.   Set<String> links = new HashSet<String>();  
  17.   try {  
  18.    Parser parser = new Parser(url);  
  19.    parser.setEncoding("gb2312");  
  20.    // 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性  
  21.    NodeFilter frameFilter = new NodeFilter() {  
  22.     private static final long serialVersionUID = 1L;  
  23.     @Override  
  24.     public boolean accept(Node node) {  
  25.      if (node.getText().startsWith("frame src=")) {  
  26.       return true;  
  27.      } else {  
  28.       return false;  
  29.      }  
  30.     }  
  31.    };  
  32.    // OrFilter 来设置过滤 <a> 标签和 <frame> 标签  
  33.    OrFilter linkFilter = new OrFilter(new NodeClassFilter(  
  34.      LinkTag.class), frameFilter);  
  35.    // 得到所有经过过滤的标签  
  36.    NodeList list = parser.extractAllNodesThatMatch(linkFilter);  
  37.    for (int i = 0; i < list.size(); i++) {  
  38.     Node tag = list.elementAt(i);  
  39.     if (tag instanceof LinkTag)// <a> 标签  
  40.     {  
  41.      LinkTag link = (LinkTag) tag;  
  42.      String linkUrl = link.getLink();// URL  
  43.      if (filter.accept(linkUrl))  
  44.       links.add(linkUrl);  
  45.     } else// <frame> 标签  
  46.     {  
  47.      // 提取 frame 里 src 属性的链接, 如 <frame src="test.html"/>  
  48.      String frame = tag.getText();  
  49.      int start = frame.indexOf("src=");  
  50.      frame = frame.substring(start);  
  51.      int end = frame.indexOf(" ");  
  52.      if (end == -1)  
  53.       end = frame.indexOf(">");  
  54.      String frameUrl = frame.substring(5, end - 1);  
  55.      if (filter.accept(frameUrl))  
  56.       links.add(frameUrl);  
  57.     }  
  58.    }  
  59.   } catch (ParserException e) {  
  60.    e.printStackTrace();  
  61.   }  
  62.   return links;  
  63.  }  
  64. }  

最后我们来写个爬虫类调用前面的封装类和函数:

[java] view plain copy
 print?
  1. package controller;  
  2. import java.util.Set;  
  3. import model.LinkFilter;  
  4. import model.SpiderQueue;  
  5. public class BfsSpider {  
  6.  /** 
  7.   * 使用种子初始化URL队列 
  8.   */  
  9.  private void initCrawlerWithSeeds(String[] seeds) {  
  10.   for (int i = 0; i < seeds.length; i++)  
  11.    SpiderQueue.addUnvisitedUrl(seeds[i]);  
  12.  }  
  13.  // 定义过滤器,提取以 <a target=_blank href="http://www.xxxx.com/" style="color: rgb(0, 102, 153); text-decoration: none;">http://www.xxxx.com</a>开头的链接  
  14.  public void crawling(String[] seeds) {  
  15.   LinkFilter filter = new LinkFilter() {  
  16.    public boolean accept(String url) {  
  17.     if (url.startsWith("<a target=_blank href="http://www.baidu.com/" style="color: rgb(0, 102, 153); text-decoration: none;">http://www.baidu.com</a>"))  
  18.      return true;  
  19.     else  
  20.      return false;  
  21.    }  
  22.   };  
  23.   // 初始化 URL 队列  
  24.   initCrawlerWithSeeds(seeds);  
  25.   // 循环条件:待抓取的链接不空且抓取的网页不多于 1000  
  26.   while (!SpiderQueue.unVisitedUrlsEmpty()  
  27.     && SpiderQueue.getVisitedUrlNum() <= 1000) {  
  28.    // 队头 URL 出队列  
  29.    String visitUrl = (String) SpiderQueue.unVisitedUrlDeQueue();  
  30.    if (visitUrl == null)  
  31.     continue;  
  32.    DownTool downLoader = new DownTool();  
  33.    // 下载网页  
  34.    downLoader.downloadFile(visitUrl);  
  35.    // 该 URL 放入已访问的 URL 中  
  36.    SpiderQueue.addVisitedUrl(visitUrl);  
  37.    // 提取出下载网页中的 URL  
  38.    Set<String> links = HtmlParserTool.extracLinks(visitUrl, filter);  
  39.    // 新的未访问的 URL 入队  
  40.    for (String link : links) {  
  41.     SpiderQueue.addUnvisitedUrl(link);  
  42.    }  
  43.   }  
  44.  }  
  45.  // main 方法入口  
  46.  public static void main(String[] args) {  
  47.   BfsSpider crawler = new BfsSpider();  
  48.   crawler.crawling(new String[] { "<a target=_blank href="http://www.baidu.com/" style="color: rgb(0, 102, 153); text-decoration: none;">http://www.baidu.com</a>" });  
  49.  }  
  50. }  

运行可以看到,爬虫已经把百度网页下所有的页面都抓取出来了:

以上就是java使用HttpClient工具包和宽度爬虫进行抓取内容的操作的全部内容。

来源:http://blog.csdn.net/zhihui1017/article/details/50511241

原创粉丝点击