首页 > 技术文章 > spider爬虫练习

StephenChowcai 2017-10-01 20:10 原文

package com.jinzhi.spider;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.stream.events.StartDocument;

//多线程类,爬虫程序要用多线程来处理网页内容的爬取,效率更高
public class SearchCrawler implements Runnable {
 /**
  *
  * disallowListCache缓存robot不允许搜索的URL。
  * Robot协议在Web站点的根目录下设置一个robots.txt文件, 
     * 规定站点上的哪些页面是限制搜索的。 搜索程序应该在搜索过程中跳过这些区域
  *
  **/
 //不允许搜索的URL缓存
 private HashMap<String, ArrayList<String>> disallowListCache = new HashMap<String, ArrayList<String>>();//设置一存储限制搜索的区域
 ArrayList<String> errorList = new ArrayList<String>();//错误链接集合
 public ArrayList<String> result = new ArrayList<>();//结果集合
 String startUrl;//开始搜索的起点
 int maxUrl;//最大处理的url数;
 String searchString;//要搜索的字符串(英文)
 boolean caseSensitive = false;//大小写是否敏感
 boolean limitHost = false;//是否在限制的主机内搜索
 
 //请求任务URL,根据得到的URL下载相应的HTML代码,利用HTML代码调用其他模块完成相关处理。
 public SearchCrawler(String startUrl, int maxUrl, String searchString) {
  this.startUrl = startUrl;
  this.maxUrl = maxUrl;
  this.searchString = searchString;
 }
 //得到结果——结果肯定不是一个字符串,所以选择用字符串集合,
 public ArrayList<String> getResult() {
  
  return result;
 }
 //启动搜索线程
 public void run() {
  //真正查询操作
  crawl(startUrl, maxUrl, searchString, limitHost, caseSensitive);
  
 }
 //把url包装为URL类
 private URL verifyUrl(String url) {
  //不是以Http://开头则返回null
  if(!url.toLowerCase().startsWith("http://"))
   //方法直接返回
   return null;
   //声明一个新的url
   URL verifiedUrl = null;
  try {
   //用传递进来的url包装一个URL
   verifiedUrl = new URL(url);
  } catch (Exception e) {
   //否则
   return null;
  }
  //把包装好的URL对象返回
  return verifiedUrl;
 }
 //检测URL是否允许被使用
 private boolean isRobotAllowed(URL urlToCheck) {
  //通过URL获取给出对应的主机
  String host = urlToCheck.getHost().toLowerCase();
  //通过主机获取url集合,通过Map键值对的形式,获取主机不允许搜索的URL(是以ArrayList的形式标识)缓存
  ArrayList<String> disallowList = disallowListCache.get(host);
  //如果没有缓存,下载并缓存
  if(disallowList == null) {
   disallowList  = new ArrayList<>();
   try {
    //准备记录不允许搜索的URL
    URL robotsFileUrl = new URL("http://" + host + "/robots.txt");
    //把不允许搜索的URL实例获取的字节流转换为字符流    //打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
    BufferedReader reader = new BufferedReader(new InputStreamReader(robotsFileUrl.openStream()));
    //读取robot文件,创建不允许访问的路径列表
    
    String line;//设置字符串缓冲区
    //不等于空就继续读取
    while((line=reader.readLine())!=null) {
     //判断是否包含字符串Disallow
     if(line.indexOf("Disallow:") == 0) {
      //获取不允许访问路径——   1.去除Disallow:
      String disallowPath = line.substring("Disallow:".length());
      //检测是否有注释
      int commenIndex = disallowPath.indexOf("#");
      if(commenIndex != -1) {
       //   2.去掉注释
       disallowPath = disallowPath.substring(0, commenIndex);
      }
      //  3. 去掉空格
      disallowPath = disallowPath.trim();
      //把不允许访问路径添加到不允许访问集合记录起来
      disallowList.add(disallowPath);
     }
    }
    //缓存此主机不允许访问的路径  
    disallowListCache.put(host, disallowList);    
   
   } catch (Exception e) {
  //web站点根目录下没有robots.txt文件,返回真
    return true;
   }
  }
  //从参数URL中得到文件
  String file = urlToCheck.getFile();
  //遍历不允许访问路径记录的集合
  for(int i = 0; i < disallowList.size(); i++) {
   //得到每一个不允许访问的路径
   String disallow = disallowList.get(i);
   //判断从给出的URL得到的文件如果为不被允许访问的文件
   if(file.startsWith(disallow)) {
    //返回false;
    return false;
   }
  }
  //否则,返回真
  return true;
   
 }
 //下载页面
 public String downloadPage(URL pageUrl) {
  try {
   //根据URL初始化缓冲字符流
   BufferedReader reader = new BufferedReader(new InputStreamReader(pageUrl.openStream()));
   String line;//设置字符串缓冲区
   StringBuffer pageBuffer = new StringBuffer();
   while((line = reader.readLine()) != null) {
    //把页面数据添加到字符串缓冲区
    pageBuffer.append(line);
   }
   //返回下载的数据
   return pageBuffer.toString();
   
  } catch (Exception e) {
   // TODO: handle exception
  }
  //否则,返回null
  return null;
  
 }
 //去除url中的"www"
 private String removeWwwFromUrl(String url) {
  //获取"://www."的位置
  int index = url.indexOf("://www.");
  if(index != -1) {
   //如果存在,刚好截取出:www
   return url.substring(0, index + 3) + url.substring(index + 7);
  }
  return (url);
 }
 //查找目标链接
 private ArrayList<String> retrieveLinks(URL pageUrl, String pageContents, HashSet crawledList, boolean limitHost) {
  //用正则表达式编译链接的匹配模式
  Pattern p = Pattern.compile("<a\\s+href\\s*=\\s*\"?(.*?)[\"|>]",Pattern.CASE_INSENSITIVE);
  //看页面内容是否和正则表达式匹配
  Matcher m = p.matcher(pageContents);
  //准备好链接集合
  ArrayList<String> linkList = new ArrayList<>();
  while(m.find()) {
   //得到匹配正则分组的第1组
   String link = m.group(1).trim();
   if(link.length() < 1) {
    //证明没有目标链接
    continue;
   }
   //跳过链到本页面的内链接
   if(link.charAt(0) == '#') {
    //敏感资源链接
    continue;
   }
   if(link.indexOf("mailto:") != -1) {
    //如果包含mailto:也跳过
    continue;
   }
   if(link.toLowerCase().indexOf("javascript") != -1) {
    //如果包含javascript也跳过
    continue;
   }
   //如果存在"://"字符串,则证明找到目标链接
   if(link.indexOf("://") == -1) {
    //处理绝对地址——如果是:以/开头的地址链接
    if(link.charAt(0) == '/') {
     //拼成完整路径
     link = "http://" + pageUrl.getHost() + ":" + pageUrl.getPort() + link;
    } else {
     String file = pageUrl.getFile();
     //处理相对地址——如果存在:/字符
     if(file.indexOf('/') == -1) {
      link = "http://" + pageUrl.getHost() + ":"+ pageUrl.getPort() + "/" +link;
     }
    }
   }
   int index = link.indexOf("#");
   //如果连接URL存在#——存在注解
   if(index != -1) {
    link = link.substring(0, index);
   }
   //去除www
   link = removeWwwFromUrl(link);
   //封装为已证实的url
   URL verifiedLink = verifyUrl(link);
   if(verifiedLink == null) {
    //不是目标链接
    continue;
   }
   //如果限定主机,排除那些不合条件的URL为未证实(非目标URL)
   if(limitHost && !pageUrl.getHost().toLowerCase().equals(verifiedLink.getHost().toLowerCase())) {
    
    continue;
   }
   //跳过已处理的连接
   if(crawledList.contains(link)) {
    //处理过的链接集合中包括正在处理的连接,直接跳过
    continue;
   }
   //把符合标准的链接放入集合中
   linkList.add(link);
  }
  //返回集合
  return (linkList);
 }
 //判断有无目标字符串
 private boolean searchStringMatches(String pageContents, String searchString, boolean caseSensitive) {
  String searchContents = pageContents;
  //大小写不敏感
  if(!caseSensitive) {
   searchContents = pageContents.toLowerCase();
  }
  Pattern p = Pattern.compile("[\\s]+");//这里查询s为核武
  String[] terms = p.split(searchString);
  for(int i = 0; i < terms.length; i++) {
   if(caseSensitive) {
    if(searchContents.indexOf(terms[i]) == -1) {
     return false;
    }
   }
  }
  return true;
 }
 
  //执行实际的搜索操作  
    public ArrayList<String> crawl(String startUrl, int maxUrls,  
            String searchString, boolean limithost, boolean caseSensitive) {  
     //初始化一个已处理连接的集合
        HashSet<String> crawledList = new HashSet<String>();  
        LinkedHashSet<String> toCrawlList = new LinkedHashSet<String>();  
        //最大URL处理数<1——这里链接的url里没有有用信息
        if (maxUrls < 1) {  
         //判断用户给定要获取的URL的处理数——如果输入错误数据,则记录在集合里
            errorList.add("Invalid Max URLs value.");  
            System.out.println("Invalid Max URLs value.");  
        }  
        //用户给定的目标字符串长度小于1
        if (searchString.length() < 1) { 
         //记录入错误链接集合
            errorList.add("Missing Search String.");  
            System.out.println("Missing search String");  
        }  
        //错误链接集合有元素,则返回该集合
        if (errorList.size() > 0) {  
            System.out.println("err!!!");  
            return errorList;  
        }  
 
        // 从开始URL中移出www  
        startUrl = removeWwwFromUrl(startUrl);  
        //把移除"www"的url添加到url等待队列
        toCrawlList.add(startUrl);  
        //等待队列有元素
        while (toCrawlList.size() > 0) { 
         //用户给定获取的URL数量
            if (maxUrls != -1) {  
             //当已获取的有效链接数和用户想要得到的链接数相同时——达到目标数目
                if (crawledList.size() == maxUrls) {
                 //终止循环
                    break;  
                }  
            }  
 
            // 从等待队列中获取url
            String url = toCrawlList.iterator().next();  
 
            //从等待队列中去除该url
            toCrawlList.remove(url);
           
            //url——>URL
            URL verifiedUrl = verifyUrl(url);  
 
            //查看是否被允许访问
            if (!isRobotAllowed(verifiedUrl)) {
             //如果是不允许被访问的URL
                continue;  
            }  
 
            // 增加已处理的URL到crawledList  
            crawledList.add(url);  
            //根据URL下载页面得到目的字符串
            String pageContents = downloadPage(verifiedUrl);  
 
            if (pageContents != null && pageContents.length() > 0) {  
                // 从页面中获取目的链接  
                ArrayList<String> links = retrieveLinks(verifiedUrl,  
                        pageContents, crawledList, limitHost);  
                //目的链接加入等待队列
                toCrawlList.addAll(links);  
 
                if (searchStringMatches(pageContents, searchString,  
                        caseSensitive)) {  
                 //如果有目标字符串,则把URL记录到结果集里边
                    result.add(url);  
                    System.out.println(url);  
                }  
            }  
 
        }  
        return result;  
    }

 
    // 主函数  
    public static void main(String[] args) {  
       
        SearchCrawler crawler = new SearchCrawler(
        "http://www.sohu.com",20,"N");  
        //创建线程    
        Thread search = new Thread(crawler);  
        System.out.println("Start searching...");  
        System.out.println("result:");  
        //启动线程
        search.start();  
        try {  
         //等待线程执行完毕后执行——执行完毕的标志
            search.join();  
        } catch (InterruptedException e) {  
            
            e.printStackTrace();  
        }  
    }  
 
}

运行截图如下:

 

虽然达到了预期的效果,但是还有很多不足的地方,也没有友好的界面,我将继续改进。

推荐阅读