详细代码请见
E:Project【重要】归档代码SearchEngine归档代码
或
https://code.csdn.net/jediael_lu/jediael/tree/10991c839c51d32f825708b09451b2618a20ee94
或
http://download.csdn.net/detail/jediael_lu/7402827
本版本完成以下功能:
(1)创建用于保存种子URL的配置文件及其数据结构
(2)创建用于保存Todo信息(未下载URL)的数据结构
(3)创建用于保存Visited信息(已下载的URL)的数据结构
(4)下载网页时同步更新Tode与Visited。下载网页前,判断某个网页是否已经下载过。
(5)从上述第3步下载的网页抽取链接并继续下载,直到Todo列表为空。
(6)为每个种子url创建一个独立的线程。
至此,搜索引擎已具体基本功能。
下一阶段工作:
(1)引入dao,使用数据库保存一些信息,如种子url等?
(2)使用Berkey DB保存Frontier?使用布隆过滤器保存已访问的url。
(3)继续学习后面内容,引入其它内容。如访问blog.csdn.net时,返回403。
1、创建用于保存种子url的数据结构
由于一般而言,种子url的数据量均小,因此先使用PriorityQueue,以图方便,今后视应用情况修改成其它数据结构。
package org.ljh.search.frontier; import java.util.PriorityQueue; public class SeekUrlQueue { <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//保存种子url的队列。由于一般而言,种子url的数据量均小,因此先使用PriorityQueue,以图方便,今后视应用情况修改成其它数据结构。 <span style="white-space:pre"> </span>private PriorityQueue<String> seekUrlQueue = new PriorityQueue<String>(); <span style="white-space:pre"> </span>//Getter <span style="white-space:pre"> </span>public PriorityQueue<String> getSeekUrlQueue() { <span style="white-space:pre"> </span>return seekUrlQueue; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//将url添加至种子url队列中 <span style="white-space:pre"> </span>public boolean add(String url){ <span style="white-space:pre"> </span>return seekUrlQueue.add(url); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>//判断种子url队列是否为空 <span style="white-space:pre"> </span>public boolean isEmpty(){ <span style="white-space:pre"> </span>return seekUrlQueue.isEmpty(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//从种子url队列中获取下一个种子url <span style="white-space:pre"> </span>public String getNext(){ <span style="white-space:pre"> </span>return seekUrlQueue.poll(); <span style="white-space:pre"> </span>}<span style="white-space:pre"> </span> }
2、创建用于保存已访问url的数据结构
(1)先创建一个接口,用于提供最基本功能
package org.ljh.search.frontier; public interface VisitedUrlQueue { //判断某个Url是否已经存在于已访问队列中 public boolean contains(String url); //将已下载的url放入已访问的列表 public boolean add(String url); }
(2)创建具体的实施类
package org.ljh.search.frontier; import java.util.HashSet; public class HashSetVisitedUrlQueue implements VisitedUrlQueue{ //用于保存已访问的Url的数据结构。由于已访问列表会被经常查询,因此,使用HashSet。由于只要其中任何一个线程下载过,此url即算做已经下载,因此使用了static。 private static HashSet<String> visitedUrlQueue = new HashSet<String>(); public HashSet<String> getVisitedUrlQueue() { return visitedUrlQueue; } @Override public boolean contains(String url) { return visitedUrlQueue.contains(url); } @Override public boolean add(String url) { visitedUrlQueue.add(url); return true; } }
3、创建待访问的url列表的数据结构
(1)先创建接口
package org.ljh.search.frontier; public interface Frontier { //获取下一个待访问的url public String getNextUrl(); //将从其它网页中提取出来的url放到待访问url中。 public boolean putUrlIntoTodoQueue(String url); }
(2)创建具体实现类
package org.ljh.search.frontier; import java.util.PriorityQueue; public class PriorityQueueFrontier implements Frontier { //保存待访问的url的列表。考虑到先入先出及可在一定程度上控制访问顺序,即带偏好的宽度优先搜索策略,使用了PriorityQueue。 private PriorityQueue<String> todoUrlQueue = new PriorityQueue<String>(); @Override public String getNextUrl() { return todoUrlQueue.poll(); } @Override public boolean add(String url) { todoUrlQueue.add(url); return true; } @Override public boolean isEmpty(){ return todoUrlQueue.isEmpty(); } }
4、修改主类
(1)下载网页前,判断某个网页是否已经下载过。
(2)分析刚下载的网页,提取链接,继续放入frontier。
(3)为每个种子url创建一个独立的线程。
package org.ljh.search; import java.io.IOException; import java.util.Iterator; import java.util.Set; import org.ljh.search.downloadpage.PageDownloader; import org.ljh.search.frontier.HashSetVisitedUrlQueue; import org.ljh.search.frontier.PriorityQueueFrontier; import org.ljh.search.frontier.SeekUrlQueue; import org.ljh.search.html.HtmlParserTool; import org.ljh.search.html.LinkFilter; public class MyCrawler { public static void main(String[] args) { // 种子url final SeekUrlQueue seekUrlQueue = new SeekUrlQueue(); seekUrlQueue.add("http://www.sohu.com"); seekUrlQueue.add("http://www.baidu.com"); seekUrlQueue.add("http://www.eoeandroid.com/"); // 已访问过的url final HashSetVisitedUrlQueue visitedUrl = new HashSetVisitedUrlQueue(); // 设定过滤器,用于指明搜索范围 final LinkFilter linkFilter = new LinkFilter() { @Override public boolean accept(String url) { if ((url.contains("baidu") || url.contains("sohu")||url.contains("eoe")) && !url.contains("baike") && !url.contains("@")) { return true; } else { return false; } } }; while (!seekUrlQueue.isEmpty()) { // 根据种子url对frontier进行初始化 final String nextSeek = seekUrlQueue.getNext(); System.out.println(nextSeek); //为每一个种子url,启动一个线程 Thread t = new Thread(new Runnable() { @Override public void run() { // 待访问 的url PriorityQueueFrontier frontier = new PriorityQueueFrontier(); frontier.add(nextSeek); //直到frontier为空,才会结束下载 while (!frontier.isEmpty()) { //获取下一个待访问的url,然后判断是否已经访问过,若否,则下载,并将其添加到已访问列表。 String nextUrl = frontier.getNextUrl(); if (!visitedUrl.contains(nextUrl)) { try { PageDownloader.downloadPageByGetMethod(nextUrl); } catch (IOException e) { e.printStackTrace(); } visitedUrl.add(nextUrl); //从刚下载的页面中提取链接,并将其放入frointier.正常而言,应该使用刚下载到本地的文件作参数,但此处还是使用了url,会导致再一次连接网络。 Set<String> urlSet = HtmlParserTool.extractLinks( nextUrl, linkFilter); Iterator<String> iterator = urlSet.iterator(); while (iterator.hasNext()) { frontier.add(iterator.next()); } } } } }); t.start(); } } }