欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Heritrix源码分析(十四) 如何让Heritrix不间断的抓取

程序员文章站 2022-03-02 11:38:37
...

     欢迎加入Heritrix群(QQ):109148319

 

     近段时间在搞定Lucene的一些问题,所以Heritrix源码分析暂时告一段落。今天下午在群里有同学提到了Heritrix异常终止的问题以及让Heritrix不停的抓取(就是抓完一遍后载入种子继续抓取,目前他是写个定时器,定时检查Heritrix是否停止,如果停止了则重新初始化Heritrix,让Heritrix重新抓取,但这个方法就不可避免的会导致抓取重复URl,除非从recover.gz导入)。我想这2个问题都可以用我下面这个方法,但对于Heritrix异常终止的问题这个方法也只是治标不治本,要改动的地方蛮多,涉及到Heritrix的设计以及结构,不是一时可以完成。下面就介绍原理以及方法:

       1.原理:

      Heritrix的自然停止(就是非人为以及不可抗力因素,如Web UI界面出发停止命令)的依据是判断调度器里面是否还有URL可抓取,如果没有则当前线程退出,如果是单线程抓取的话,则也意味着Heritrix的结束。如果是多线程抓取的话,则每个线程在获取不到URL的时候都会置不在活动状态,当最后一个活动状态线程也获取不到URL的时候则Heritrix也会进入自然停止。所以我们可以在判断Heritrix是否还有URL可抓取的时候做一些处理,比较简单的方法就是重新载入种子以及唤醒所有抓取线程。但由于处在多线程抓取模式中,每个线程都会判断,如此会导致多次载入种子,造成不必要的浪费,所以这里也要做一些同步控制。不要导致过分载入种子,而是每抓完一次则重新载入种子。

 

     2.方法:

    修改org.archive.crawler.frontier.WorkQueueFrontier中的public CrawlURI next()方法,这里也对该方法做一些介绍,具体请看源码注释,改成如下,红色部分为改动部分:

   

      

/**
  * 从调度中心获取下一个要抓取的URL
  * 
 */
public CrawlURI next() throws InterruptedException, EndedException {
	while (true) {//一直不停的循环,直到遇到异常或终止
		// 郭芸修改,用于当队列里没有可抓取的URL的时候去获取种子继续
		synchronized (this) {
			if (this.controller.getFrontier().isEmpty()) {	//如果没有可抓取的URL
				loadSeeds();	//重新载入种子
				this.controller.getToePool().notifyAll();	//唤醒所有抓取线程
			}

		}

		long now = System.currentTimeMillis();//开始获取时间

		// 检查是否有暂停命令、结束命令以及宽带控制,这里会导致Heritrix结束
		preNext(now);

		/*
		 * 允许最多一个线程去填充准备队列(readyClassQueues)
		 */
		if (readyFiller.tryAcquire()) {// 表示没有线程去使用当前变量,当前类1次只允许1个线程同时使用
			try {
				
				// 空闲队列数=目标队列数-准备队列数
				int activationsNeeded = targetSizeForReadyQueues()
						- readyClassQueues.size();
				// 如果空闲队列数大于0,并且不在活动状态的队列数不是空的,则表示需要将不在活动状态的队列转移到准备队列
				while (activationsNeeded > 0 && !inactiveQueues.isEmpty()) {
					activateInactiveQueue();//将不在活动状态队列的URL转移一定数目到活动状态队列
					activationsNeeded--;
				}
			} finally {
				readyFiller.release();// 必须释放,这样下次才可以继续使用
			}
		}
		
		WorkQueue readyQ = null;//准备工作队列
		// 获取并移除此准备队列表示的队列的头部(即准备队列的第一个元素)如果该队列没有可用元素,则等待指定的时间,这里是1000毫秒也就是1秒
		Object key = readyClassQueues.poll(DEFAULT_WAIT,TimeUnit.MILLISECONDS);// 获得classKey,然后再通过classKey去获得队列

		if (key != null) {
			readyQ = (WorkQueue) this.allQueues.get(key);// 获得工作队列WorkQueue
		}
		if (readyQ != null) {
			while (true) { // 一直循环,直到抛出异常或终止
				CrawlURI curi = null;
				synchronized (readyQ) {//锁定准备队列,让其他线程无法获取,避免脏读
					curi = readyQ.peek(this); // 从数据库pendingUrls中获取CrawlURI
					if (curi != null) {
						// 检查该curi是否属于不同的队列
						String currentQueueKey = getClassKey(curi);
						if (currentQueueKey.equals(curi.getClassKey())) {
							//在正确的队列,排放它
							noteAboutToEmit(curi, readyQ);
							inProcessQueues.add(readyQ);// 加入已处理队列
							return curi;
						}
						
						curi.setClassKey(currentQueueKey);
						readyQ.dequeue(this);		//从调度器中删除刚获取到的URL
						decrementQueuedCount(1);	//计数
						curi.setHolderKey(null);
					} else {
						readyQ.clearHeld();
						break;
					}
				}
				if (curi != null) {
					sendToQueue(curi);	//将获取到的URL发送到它该属于的队列
				}
			}
		} else {
			if (key != null) {
				logger.severe("Key " + key
						+ " in readyClassQueues but not allQueues");
			}
		}
		//如果该强烈退出,则抛异常结束循环
		if (shouldTerminate) {
			throw new EndedException("shouldTerminate is true");
		}
		//如果没有处理中的队列,则刷新该队列
		if (inProcessQueues.size() == 0) {
			this.alreadyIncluded.requestFlush();
		}
	}
}

 

更多技术文章、感悟、分享、勾搭,请用微信扫描:

Heritrix源码分析(十四)  如何让Heritrix不间断的抓取
            
    
    博客分类: 搜索引擎-爬虫-Heritrix 多线程lucene活动SolrQQ