代码改变世界

【搜索引擎Jediael开发笔记】V0.1完整代码

2014-05-26 15:16  jediael  阅读(120)  评论(0编辑  收藏  举报

详细代码请见

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();
		}
	}

}