zoukankan      html  css  js  c++  java
  • HtmlParser + HttpClient 实现爬虫

    简易爬虫的实现

    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
    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 介绍中说明过了。

  • 相关阅读:
    放大镜功能
    background兼容IE9以下版本
    JSON解析
    vue.js 组件-全局组件和局部组件
    i++ ++i的原子性
    【转】程序员面试笔试宝典
    【转】函数调用栈 格式化操作
    【转】TCP三次握手过程
    一些面试题
    【转】HP(惠普)大中华区总裁孙振耀退休感言
  • 原文地址:https://www.cnblogs.com/mywy/p/5065136.html
Copyright © 2011-2022 走看看