zoukankan      html  css  js  c++  java
  • Heritrix源码分析(十四) 如何让Heritrix不间断的抓取(转)

      欢迎加入Heritrix群(QQ):109148319,10447185 , Lucene/Solr群(QQ) :  118972724  

        本博客已迁移到本人独立博客: http://www.yun5u.com/

         近段时间在搞定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()方法,这里也对该方法做一些介绍,具体请看源码注释,改成如下,红色部分为改动部分:

       

          

    Java代码  收藏代码
    1. /** 
    2.   * 从调度中心获取下一个要抓取的URL 
    3.   *  
    4.  */  
    5. public CrawlURI next() throws InterruptedException, EndedException {  
    6.     while (true) {//一直不停的循环,直到遇到异常或终止  
    7.         <span style="color: #ff0000;">// 郭芸修改,用于当队列里没有可抓取的URL的时候去获取种子继续  
    8.         synchronized (this) {  
    9.             if (this.controller.getFrontier().isEmpty()) {  //如果没有可抓取的URL  
    10.                 loadSeeds();    //重新载入种子  
    11.                 this.controller.getToePool().notifyAll();   //唤醒所有抓取线程  
    12.             }  
    13.   
    14.         }</span>  
    15.   
    16.         long now = System.currentTimeMillis();//开始获取时间  
    17.   
    18.         // 检查是否有暂停命令、结束命令以及宽带控制,这里会导致Heritrix结束  
    19.         preNext(now);  
    20.   
    21.         /* 
    22.          * 允许最多一个线程去填充准备队列(readyClassQueues) 
    23.          */  
    24.         if (readyFiller.tryAcquire()) {// 表示没有线程去使用当前变量,当前类1次只允许1个线程同时使用  
    25.             try {  
    26.                   
    27.                 // 空闲队列数=目标队列数-准备队列数  
    28.                 int activationsNeeded = targetSizeForReadyQueues()  
    29.                         - readyClassQueues.size();  
    30.                 // 如果空闲队列数大于0,并且不在活动状态的队列数不是空的,则表示需要将不在活动状态的队列转移到准备队列  
    31.                 while (activationsNeeded > 0 && !inactiveQueues.isEmpty()) {  
    32.                     activateInactiveQueue();//将不在活动状态队列的URL转移一定数目到活动状态队列  
    33.                     activationsNeeded--;  
    34.                 }  
    35.             } finally {  
    36.                 readyFiller.release();// 必须释放,这样下次才可以继续使用  
    37.             }  
    38.         }  
    39.           
    40.         WorkQueue readyQ = null;//准备工作队列  
    41.         // 获取并移除此准备队列表示的队列的头部(即准备队列的第一个元素)如果该队列没有可用元素,则等待指定的时间,这里是1000毫秒也就是1秒  
    42.         Object key = readyClassQueues.poll(DEFAULT_WAIT,TimeUnit.MILLISECONDS);// 获得classKey,然后再通过classKey去获得队列  
    43.   
    44.         if (key != null) {  
    45.             readyQ = (WorkQueue) this.allQueues.get(key);// 获得工作队列WorkQueue  
    46.         }  
    47.         if (readyQ != null) {  
    48.             while (true) { // 一直循环,直到抛出异常或终止  
    49.                 CrawlURI curi = null;  
    50.                 synchronized (readyQ) {//锁定准备队列,让其他线程无法获取,避免脏读  
    51.                     curi = readyQ.peek(this); // 从数据库pendingUrls中获取CrawlURI  
    52.                     if (curi != null) {  
    53.                         // 检查该curi是否属于不同的队列  
    54.                         String currentQueueKey = getClassKey(curi);  
    55.                         if (currentQueueKey.equals(curi.getClassKey())) {  
    56.                             //在正确的队列,排放它  
    57.                             noteAboutToEmit(curi, readyQ);  
    58.                             inProcessQueues.add(readyQ);// 加入已处理队列  
    59.                             return curi;  
    60.                         }  
    61.                           
    62.                         curi.setClassKey(currentQueueKey);  
    63.                         readyQ.dequeue(this);       //从调度器中删除刚获取到的URL  
    64.                         decrementQueuedCount(1);    //计数  
    65.                         curi.setHolderKey(null);  
    66.                     } else {  
    67.                         readyQ.clearHeld();  
    68.                         break;  
    69.                     }  
    70.                 }  
    71.                 if (curi != null) {  
    72.                     sendToQueue(curi);  //将获取到的URL发送到它该属于的队列  
    73.                 }  
    74.             }  
    75.         } else {  
    76.             if (key != null) {  
    77.                 logger.severe("Key " + key  
    78.                         + " in readyClassQueues but not allQueues");  
    79.             }  
    80.         }  
    81.         //如果该强烈退出,则抛异常结束循环  
    82.         if (shouldTerminate) {  
    83.             throw new EndedException("shouldTerminate is true");  
    84.         }  
    85.         //如果没有处理中的队列,则刷新该队列  
    86.         if (inProcessQueues.size() == 0) {  
    87.             this.alreadyIncluded.requestFlush();  
    88.         }  
    89.     }  
    90. }  
  • 相关阅读:
    fatal: unable to access ‘https://github.com/Homebrew/brew/‘: Error in the HTTP2 framing layer
    leetcode题目两数和、最大字符串长度、中位数、最长回文子串
    zabbix5.x版本添加自定义监控+263邮件报警
    php过滤特殊字符方法
    js中e相关位置
    少有人走的路-心智成熟的旅程-斯科特·派克
    ios脱壳
    docker 容器开机自启
    Java8 Stream的使用
    Entity Frameworker 的核心 EDM(Entity Data Model)模式
  • 原文地址:https://www.cnblogs.com/seurain/p/3200684.html
Copyright © 2011-2022 走看看