HttpClient 和 HtmlParser 实现简易爬虫

来源:互联网 发布:淘宝开通淘金币抵扣 编辑:程序博客网 时间:2024/06/05 00:39
简易爬虫的实现
HttpClient 提供了便利的 HTTP 协议访问,使得我们可以很容易的得到某个网页的源码并保存在本地;HtmlParser 提供了如此简便灵巧的类库,可以从网页中便捷的提取出指向其他网页的超链接。笔者结合这两个开源包,构建了一个简易的网络爬虫。
爬虫 (Crawler) 原理
学过数据结构的读者都知道有向图这种数据结构。如下图所示,如果将网页看成是图中的某一个节点,而将网页中指向其他网页的链接看成是这个节点指向其他节点的边,那么我们很容易将整个 Internet 上的网页建模成一个有向图。理论上,通过遍历算法遍历该图,可以访问到Internet 上的几乎所有的网页。最简单的遍历就是宽度优先以及深度优先。以下笔者实现的简易爬虫就是使用了宽度优先的爬行策略。

图 2. 网页关系的建模图


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

图 3. 爬虫流程图


各个类的源码以及说明
对应上面的流程图,简易爬虫由下面几个类组成,各个类职责如下:
Crawler.java:爬虫的主方法入口所在的类,实现爬取的主要流程。
LinkDb.java:用来保存已经访问的 url 和待爬取的 url 的类,提供url出对入队操作。
Queue.java: 实现了一个简单的队列,在 LinkDb.java 中使用了此类。
FileDownloader.java:用来下载 url 所指向的网页。
HtmlParserTool.java: 用来抽取出网页中的链接。
LinkFilter.java:一个接口,实现其 accept() 方法用来对抽取的链接进行过滤。
下面是各个类的源码,代码中的注释有比较详细的说明。

清单6 Crawler.java
[java] view plaincopy
  1. package com.ie;  
  2.   
  3. import java.util.Set;  
  4. public class Crawler {  
  5.     /* 使用种子 url 初始化 URL 队列*/  
  6.     private void initCrawlerWithSeeds(String[] seeds)  
  7.     {  
  8.         for(int i=0;i<seeds.length;i++)  
  9.             LinkDB.addUnvisitedUrl(seeds[i]);  
  10.     }  
  11.       
  12.     /* 爬取方法*/  
  13.     public void crawling(String[] seeds)  
  14.     {  
  15.         LinkFilter filter = new LinkFilter(){  
  16.             //提取以 http://www.twt.edu.cn 开头的链接  
  17.             public boolean accept(String url) {  
  18.                 if(url.startsWith("http://www.twt.edu.cn"))  
  19.                     return true;  
  20.                 else  
  21.                     return false;  
  22.             }  
  23.         };  
  24.         //初始化 URL 队列  
  25.         initCrawlerWithSeeds(seeds);  
  26.         //循环条件:待抓取的链接不空且抓取的网页不多于 1000  
  27.         while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=1000)  
  28.         {  
  29.             //队头 URL 出对  
  30.             String visitUrl=LinkDB.unVisitedUrlDeQueue();  
  31.             if(visitUrl==null)  
  32.                 continue;  
  33.             FileDownLoader downLoader=new FileDownLoader();  
  34.             //下载网页  
  35.             downLoader.downloadFile(visitUrl);  
  36.             //该 url 放入到已访问的 URL 中  
  37.             LinkDB.addVisitedUrl(visitUrl);  
  38.             //提取出下载网页中的 URL  
  39.               
  40.             Set<String> links=HtmlParserTool.extracLinks(visitUrl,filter);  
  41.             //新的未访问的 URL 入队  
  42.             for(String link:links)  
  43.             {  
  44.                     LinkDB.addUnvisitedUrl(link);  
  45.             }  
  46.         }  
  47.     }  
  48.     //main 方法入口  
  49.     public static void main(String[]args)  
  50.     {  
  51.         Crawler crawler = new Crawler();  
  52.         crawler.crawling(new String[]{"http://www.twt.edu.cn"});  
  53.     }  
  54. }  

清单7 LinkDb.java
[java] view plaincopy
  1. package com.ie;  
  2.   
  3. import java.util.HashSet;  
  4. import java.util.Set;  
  5.   
  6. /** 
  7.  * 用来保存已经访问过 Url 和待访问的 Url 的类 
  8.  */  
  9. public class LinkDB {  
  10.   
  11.     //已访问的 url 集合  
  12.     private static Set<String> visitedUrl = new HashSet<String>();  
  13.     //待访问的 url 集合  
  14.     private static Queue<String> unVisitedUrl = new Queue<String>();  
  15.   
  16.       
  17.     public static Queue<String> getUnVisitedUrl() {  
  18.         return unVisitedUrl;  
  19.     }  
  20.   
  21.     public static void addVisitedUrl(String url) {  
  22.         visitedUrl.add(url);  
  23.     }  
  24.   
  25.     public static void removeVisitedUrl(String url) {  
  26.         visitedUrl.remove(url);  
  27.     }  
  28.   
  29.     public static String unVisitedUrlDeQueue() {  
  30.         return unVisitedUrl.deQueue();  
  31.     }  
  32.   
  33.     // 保证每个 url 只被访问一次  
  34.     public static void addUnvisitedUrl(String url) {  
  35.         if (url != null && !url.trim().equals("")  
  36.  && !visitedUrl.contains(url)  
  37.                 && !unVisitedUrl.contians(url))  
  38.             unVisitedUrl.enQueue(url);  
  39.     }  
  40.   
  41.     public static int getVisitedUrlNum() {  
  42.         return visitedUrl.size();  
  43.     }  
  44.   
  45.     public static boolean unVisitedUrlsEmpty() {  
  46.         return unVisitedUrl.empty();  
  47.     }  
  48. }  

清单8 Queue.java
[java] view plaincopy
  1. package com.ie;  
  2.   
  3. import java.util.LinkedList;  
  4. /** 
  5.  * 数据结构队列 
  6.  */  
  7. public class Queue<T> {  
  8.   
  9.     private LinkedList<T> queue=new LinkedList<T>();  
  10.       
  11.     public void enQueue(T t)  
  12.     {  
  13.         queue.addLast(t);  
  14.     }  
  15.       
  16.     public T deQueue()  
  17.     {  
  18.         return queue.removeFirst();  
  19.     }  
  20.       
  21.     public boolean isQueueEmpty()  
  22.     {  
  23.         return queue.isEmpty();  
  24.     }  
  25.       
  26.     public boolean contians(T t)  
  27.     {  
  28.         return queue.contains(t);  
  29.     }  
  30.       
  31.     public boolean empty()  
  32.     {  
  33.         return queue.isEmpty();  
  34.     }  
  35. }  

清单 9 FileDownLoader.java
[java] view plaincopy
  1. package com.ie;  
  2.   
  3. import java.io.DataOutputStream;  
  4. import java.io.File;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;  
  8. import org.apache.commons.httpclient.HttpClient;  
  9. import org.apache.commons.httpclient.HttpException;  
  10. import org.apache.commons.httpclient.HttpStatus;  
  11. import org.apache.commons.httpclient.methods.GetMethod;  
  12. import org.apache.commons.httpclient.params.HttpMethodParams;  
  13.   
  14. public class FileDownLoader {  
  15.       
  16.     /**根据 url 和网页类型生成需要保存的网页的文件名 
  17.      *去除掉 url 中非文件名字符  
  18.      */  
  19.     public  String getFileNameByUrl(String url,String contentType)  
  20.     {  
  21.         url=url.substring(7);//remove http://  
  22.         if(contentType.indexOf("html")!=-1)//text/html  
  23.         {  
  24.             url= url.replaceAll("[\\?/:*|<>\"]""_")+".html";  
  25.             return url;  
  26.         }  
  27.         else//如application/pdf  
  28.         {  
  29. return url.replaceAll("[\\?/:*|<>\"]""_")+"."+ \  
  30.           contentType.substring(contentType.lastIndexOf("/")+1);  
  31.         }     
  32.     }  
  33.   
  34.     /**保存网页字节数组到本地文件 
  35.      * filePath 为要保存的文件的相对地址 
  36.      */  
  37.     private void saveToLocal(byte[] data,String filePath)  
  38.     {  
  39.         try {  
  40.             DataOutputStream out=new DataOutputStream(  
  41. new FileOutputStream(new File(filePath)));  
  42.             for(int i=0;i<data.length;i++)  
  43.             out.write(data[i]);  
  44.             out.flush();  
  45.             out.close();  
  46.         } catch (IOException e) {  
  47.             e.printStackTrace();  
  48.         }  
  49.     }  
  50.   
  51.     /*下载 url 指向的网页*/  
  52.     public String  downloadFile(String url)  
  53.     {  
  54.           String filePath=null;  
  55.           /* 1.生成 HttpClinet 对象并设置参数*/  
  56.           HttpClient httpClient=new HttpClient();  
  57.           //设置 Http 连接超时 5s  
  58.               httpClient.getHttpConnectionManager().getParams().  
  59. setConnectionTimeout(5000);  
  60.             
  61.           /*2.生成 GetMethod 对象并设置参数*/  
  62.           GetMethod getMethod=new GetMethod(url);      
  63.           //设置 get 请求超时 5s  
  64.           getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);  
  65.           //设置请求重试处理  
  66.           getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,  
  67.             new DefaultHttpMethodRetryHandler());  
  68.             
  69.           /*3.执行 HTTP GET 请求*/  
  70.           try{   
  71.               int statusCode = httpClient.executeMethod(getMethod);  
  72.               //判断访问的状态码  
  73.               if (statusCode != HttpStatus.SC_OK)   
  74.               {  
  75. System.err.println("Method failed: "+ getMethod.getStatusLine());  
  76.                   filePath=null;  
  77.               }  
  78.                 
  79.               /*4.处理 HTTP 响应内容*/  
  80.  byte[] responseBody = getMethod.getResponseBody();//读取为字节数组  
  81.               //根据网页 url 生成保存时的文件名  
  82. filePath="temp\\"+getFileNameByUrl(url,  
  83.            getMethod.getResponseHeader("Content-Type").getValue());  
  84.             saveToLocal(responseBody,filePath);  
  85.           } catch (HttpException e) {  
  86.                    // 发生致命的异常,可能是协议不对或者返回的内容有问题  
  87.                    System.out.println("Please check your provided http   
  88. address!");  
  89.                    e.printStackTrace();  
  90.                   } catch (IOException e) {  
  91.                    // 发生网络异常  
  92.                    e.printStackTrace();  
  93.                   } finally {  
  94.                    // 释放连接  
  95.                    getMethod.releaseConnection();            
  96.                   }  
  97.                   return filePath;  
  98.     }  
  99.     //测试的 main 方法  
  100.     public static void main(String[]args)  
  101.     {  
  102.         FileDownLoader downLoader = new FileDownLoader();  
  103.         downLoader.downloadFile("http://www.twt.edu.cn");  
  104.     }  
  105. }  

清单 10 HtmlParserTool.java
[java] view plaincopy
  1. package com.ie;  
  2.   
  3. import java.util.HashSet;  
  4. import java.util.Set;  
  5.   
  6. import org.htmlparser.Node;  
  7. import org.htmlparser.NodeFilter;  
  8. import org.htmlparser.Parser;  
  9. import org.htmlparser.filters.NodeClassFilter;  
  10. import org.htmlparser.filters.OrFilter;  
  11. import org.htmlparser.tags.LinkTag;  
  12. import org.htmlparser.util.NodeList;  
  13. import org.htmlparser.util.ParserException;  
  14.   
  15. public class HtmlParserTool {  
  16.     // 获取一个网站上的链接,filter 用来过滤链接  
  17.     public static Set<String> extracLinks(String url,LinkFilter filter) {  
  18.   
  19.         Set<String> links = new HashSet<String>();  
  20.         try {  
  21.             Parser parser = new Parser(url);  
  22.             parser.setEncoding("gb2312");  
  23.             // 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性所表示的链接  
  24.             NodeFilter frameFilter = new NodeFilter() {  
  25.                 public boolean accept(Node node) {  
  26.                     if (node.getText().startsWith("frame src=")) {  
  27.                         return true;  
  28.                     } else {  
  29.                         return false;  
  30.                     }  
  31.                 }  
  32.             };  
  33.             // OrFilter 来设置过滤 <a> 标签,和 <frame> 标签  
  34.             OrFilter linkFilter = new OrFilter(new NodeClassFilter(  
  35.                     LinkTag.class), frameFilter);  
  36.             // 得到所有经过过滤的标签  
  37.             NodeList list = parser.extractAllNodesThatMatch(linkFilter);  
  38.             for (int i = 0; i < list.size(); i++) {  
  39.                 Node tag = list.elementAt(i);  
  40.                 if (tag instanceof LinkTag)// <a> 标签  
  41.                 {  
  42.                     LinkTag link = (LinkTag) tag;  
  43.                     String linkUrl = link.getLink();// url  
  44.                     if(filter.accept(linkUrl))  
  45.                         links.add(linkUrl);  
  46.                 } else// <frame> 标签  
  47.                 {  
  48.                 // 提取 frame 里 src 属性的链接如 <frame src="test.html"/>  
  49.                     String frame = tag.getText();  
  50.                     int start = frame.indexOf("src=");  
  51.                     frame = frame.substring(start);  
  52.                     int end = frame.indexOf(" ");  
  53.                     if (end == -1)  
  54.                         end = frame.indexOf(">");  
  55.                     String frameUrl = frame.substring(5, end - 1);  
  56.                     if(filter.accept(frameUrl))  
  57.                         links.add(frameUrl);  
  58.                 }  
  59.             }  
  60.         } catch (ParserException e) {  
  61.             e.printStackTrace();  
  62.         }  
  63.         return links;  
  64.     }  
  65.     //测试的 main 方法  
  66.     public static void main(String[]args)  
  67.     {  
  68. Set<String> links = HtmlParserTool.extracLinks(  
  69. "http://www.twt.edu.cn",new LinkFilter()  
  70.         {  
  71.             //提取以 http://www.twt.edu.cn 开头的链接  
  72.             public boolean accept(String url) {  
  73.                 if(url.startsWith("http://www.twt.edu.cn"))  
  74.                     return true;  
  75.                 else  
  76.                     return false;  
  77.             }  
  78.               
  79.         });  
  80.         for(String link : links)  
  81.             System.out.println(link);  
  82.     }  
  83. }  



清单11 LinkFilter.java

[java] view plaincopy
  1. package com.ie;  
  2.   
  3. public interface LinkFilter {  
  4.     public boolean accept(String url);  
  5. }  


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