2 宽度优先爬虫和带偏好的爬虫(2)

    接上节

  3 Java 宽度优先爬虫示例

    本节使用java实现一个简易的爬虫。其中用到了HttpClient和HtmlParser两个开源工具包。HttpClient的内容之前已经做过详细的阐述。有关HtmlParser的用法,以后会给出详细的介绍。为了便于理解,下面给出示例程序的结构,如下图:

                                         2 宽度优先爬虫和带偏好的爬虫(2)_第1张图片

 

    首先,需要定义图中所描述的“URL队列”,这里使用一个LinkedList来实现这个队列。

Queue类

/**
*队列,保存将要访问的URL
*/

public class Queue{
   //使用链表实现队列
    private LinkedList queue = new LinkedList();
  //入队列
   public void enQueue(Object t){
         queue.addLast(t);
  }  
  //出队列
   public Object deQueue(){
         return queue.removeFirst();  
  }
   //判断队列是否为空
     public boolean isQueueEmpty(){
           return queue.isEmpty();
  } 
   //判断队列是否包含t
     public boolean contians(Object t){
           return queue.contains(t);
  }    
     public boolean empty(){
          return queue.isEmpty();
  }


}     

    除了URL队列之外,在爬虫过程中,还需要一个数据结构记录已经访问过得URL。每当要访问URL的时候,首先在这个数据结构中进行查找,如果当前的URL已经存在,则丢弃它。这个数据结构要有两个特点:

  • 结构中保存的URL不能重复。
  • 能够快速地查找(实际系统中URL的数目非常多,因此要考虑查找性能)。

  针对以上两点,我们选择HashSet作为存储结构。

LinkQueue类:

 1 public class LinkQueue{
 2   37 }

  下面的代码详细说明了网页下载并处理的过程。和第一节讲述的内容相比,它考虑了更多的方面。比如存储网页,设置请求超时策略等。

DownLoadFile类:

public class DownLoadFile{
  /**
  *根据URL和网页类型生成需要保存的网页的文件名,去除URL中的非文件名字符
  */
  public String getFileNameByUrl(String url,String contentType)
  {
    //移除http:
    url=url.substring(7);
    //text/html类型
    if(contentType.indexOf("html")!=-1)
    {
      url=url.replaceAll("[\\?/:*|<>\"]","_")+".html";
      return url;
    }
    //如application/pdf类型
    else
    {
      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(IOExceptione){
          e.printStackTrace();
        }
     }
        //下载URL指向的网页
     public String downloadFile(String url){
        String filePath=null;
        //1.生成HttpClinet对象并设置参数
        HttpClienthttp Client=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.执行HTTPGET请求
        try{
          int statusCode=httpClient.executeMethod(getMethod);
          //判断访问的状态码
          if(statusCode!=HttpStatus.SC_OK){
              System.err.println("Methodfailed:"+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("Pleasecheckyourprovidedhttpaddress!");
              e.printStackTrace();
            }catch(IOException e){
              //发生网络异常
              e.printStackTrace();
            }finally{
              //释放连接
              getMethod.releaseConnection();
            }
            return filePath;        
     }
}
}

    接下来,演示如何从获得的网页中提取URL,Java有一个非常实用的开源工具包HtmlParser,它专门针对Html页面进行处理,不仅能提取URL,还能提取文本以及你想要的任何内容。关于它的有关内容,会在后面详细介绍。以下是代码:

 

HtmlParserTool类:

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(inti=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属性的链接,如<framesrc="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;
                }
            }

 

最后,来看看宽度爬虫的主程序:

MyCrawler类:

public class MyCrawler{
/**
*使用种子初始化URL队列
*@return
*@paramseeds种子URL
*/
private void initCrawlerWithSeeds(String[]seeds)
{
    for(int i=0;i<seeds.length;i++)
        LinkQueue.addUnvisitedUrl(seeds[i]);
}
/**
*抓取过程
*@return
*@paramseeds
*/
public void crawling(String[]seeds)
{ //定义过滤器,提取以http://www.lietu.com开头的链接
    LinkFilter filter=new LinkFilter(){
        public boolean accept(Stringurl){
            if(url.startsWith("http://www.lietu.com"))
                return true;
            else
                return false;
        }
};
   //初始化URL队列
    initCrawlerWithSeeds(seeds);
      //循环条件:待抓取的链接不空且抓取的网页不多于1000
    while(!LinkQueue.unVisitedUrlsEmpty()&&LinkQueue.getVisitedUrlNum()<=1000)
    {
       //队头URL出队列
        String visitUrl=(String)LinkQueue.unVisitedUrlDeQueue();
        if(visitUrl==null)
            continue;
        DownLoadFile downLoader=new DownLoadFile();
        //下载网页
        downLoader.downloadFile(visitUrl);
       //该URL放入已访问的URL中
        LinkQueue.addVisitedUrl(visitUrl);
       //提取出下载网页中的URL
        Set<String>links=HtmlParserTool.extracLinks(visitUrl,filter);
      //新的未访问的URL入队
        for(Stringlink:links)
                {
                       LinkQueue.addUnvisitedUrl(link);
                }
                }
                }
                //main方法入口
public static void main(String[]args)
{
    MyCrawler crawler=new MyCrawler();
    crawler.crawling(new String[]{"http://www.lietu.com"});
    }
}

 

  上面的主程序使用了一个LinkFilter接口,并且实现了一个内部类。这个接口的目的是为了过滤提取出来的URL,它使得程序中提取出来的URL只会和猎图网站相关。而不会提取其他无关的网站,代码如下:

public interface LinkFilter{

      public boolean accept(String url);
}

 

 

你可能感兴趣的:(爬虫)