开源JAVA爬虫crawler4j源码分析 - 4 URL管理、URL队列(一)

2014-11-24 07:32:22 · 作者: · 浏览: 3

爬虫在工作过程中,会有大量的URL需要存储和分配,如何高效的管理这些URL,是一个爬虫系统的重中之重。

crawler4j默认运行最多每小时解析几千个URL,在修改过后可以达到每小时几十万个(后面的文章中介绍),这么多的URL,应该如何管理呢?

crawler4j使用嵌入式数据库Berkeley DB JE 进行URL的临时存储和分配管理,关于Berkeley DB JE ,我在另一篇文章里做了简单介绍:

海量简单数据不想用SQL?试试高效的嵌入式数据库Berkeley DB JE吧!

WebURL:

还是先从BasicCrawlController的main函数开始,看程序是如何添加入口URL的:

		controller.addSeed("http://www.ics.uci.edu/");
		controller.addSeed("http://www.ics.uci.edu/~lopes/");
		controller.addSeed("http://www.ics.uci.edu/~welling/");

再看CrawlController的addSeed()方法:

	public void addSeed(String pageUrl) {
		addSeed(pageUrl, -1);
	}

	public void addSeed(String pageUrl, int docId) {
		String canonicalUrl = URLCanonicalizer.getCanonicalURL(pageUrl);
		if (canonicalUrl == null) {
			logger.error("Invalid seed URL: " + pageUrl);
			return;
		}
		if (docId < 0) {
			docId = docIdServer.getDocId(canonicalUrl);
			if (docId > 0) {
				// This URL is already seen.
				return;
			}
			docId = docIdServer.getNewDocID(canonicalUrl);
		} else {
			try {
				docIdServer.addUrlAndDocId(canonicalUrl, docId);
			} catch (Exception e) {
				logger.error("Could not add seed: " + e.getMessage());
			}
		}

		WebURL webUrl = new WebURL();
		webUrl.setURL(canonicalUrl);
		webUrl.setDocid(docId);
		webUrl.setDepth((short) 0);
		if (!robotstxtServer.allows(webUrl)) {
			logger.info("Robots.txt does not allow this seed: " + pageUrl);
		} else {
			frontier.schedule(webUrl);
		}
	}

这里定义了一个WebURL作为URL的Model类,存储了一些URL的属性:域、子域、路径、锚、URL地址,这些在调用setURL方法时就会被解析出来,setURL主要是字符串的截取,还用到了TLDList.getInstance().contains(domain),就是从域名列表文件tld-names.txt里查找判断URL里哪部分是域名,因为域名包括的部分可能不太一样,如.cn、.com.cn、.gov、.gov.cn;还有一些爬虫属性:分配的ID、父URLID、父URL、深度、优先级,这些会在爬虫工作时指定,所谓父URL就是在哪个页面发现的该地址,深度是第几级被发现的,如入口URL是0,从入口URL页面发现的地址是1,从1发现的新的是2,依此类推,优先级高的(数字小的)会优先分配爬取。


DocIDServer:

addSeed里面setDocid是给URL分配一个惟一的ID,默认是从1开始自动增长:1 2 3 4 5... 虽然这里可以使用JAVA自带的集合类来管理和存储这些ID,但是为了确保惟一且保证在ID增长到了几十上百万时依然高效,crawler4j使用了前面说的BDB JE来存储,当然还有一个原因是为了可恢复,即系统挂了恢复后爬虫可以继续,但我并不打算讨论这种情况,因为在这种情况下,crawler4j的运行效率相当低!

用docIdServer.getDocId()来检查该URL是否已经存储,如果没有则docId = docIdServer.getNewDocID(canonicalUrl);获取新ID。看下docIdServer是怎么工作的,首先在CrawlController构造函数中初始化并传入Environment(关于Env,请参考文章开头BDB JE链接):

docIdServer = new DocIDServer(env, config);

DocIdServer类只负责管理URL的ID,构造函数:

	public DocIDServer(Environment env, CrawlConfig config) throws DatabaseException {
		super(config);
		DatabaseConfig dbConfig = new DatabaseConfig();
		dbConfig.setAllowCreate(true);
		dbConfig.setTransactional(config.isResumableCrawling());
		dbConfig.setDeferredWrite(!config.isResumableCrawling());
		docIDsDB = env.openDatabase(null, "DocIDs", dbConfig);
		if (config.isResumableCrawling()) {
			int docCount = getDocCount();
			if (docCount > 0) {
				logger.info("Loaded " + docCount + " URLs that had been detected in previous crawl.");
				lastDocID = docCount;
			}
		} else {
			lastDocID = 0;
		}
	}

这里只是简单的创建了一个名叫DocIDs的DB(有关可恢复不做讨论,这里和下面涉及resumable都是false)。这个DB是以URL为key,以ID为value存储的,因为key的惟一性,可保证URL不重复,且更好的用URL来进行ID查询。

再看getDocId():

	public int getDocId(String url) {
		synchronized (mutex) {
			if (docIDsDB == null) {
				return -1;
			}
			OperationStatus result;
			DatabaseEntry value = new DatabaseEntry();
			try {
				DatabaseEntry key = new D