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( ""); //创建线程 Thread search = new Thread(crawler); System.out.println("Start searching..."); System.out.println("result:"); //启动线程 search.start(); try { //等待线程执行完毕后执行——执行完毕的标志 search.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行截图如下:
虽然达到了预期的效果,但是还有很多不足的地方,也没有友好的界面,我将继续改进。