heritrix多线程 加速(定制Queue-assignment-policy两个问题)

来源:互联网 发布:怎么做好淘宝手机端 编辑:程序博客网 时间:2024/06/05 11:00

10.3.2 定制Queue-assignment-policy两个问题

首先提出两个问题:

l 什么是Queue-assignment-policy

l 为什么要改变Queue-assignment-policy

在10.2节中,向读者介绍过了 Heritrix的架构。其中,讲解了Heritrix使用了Berkeley DB来构建链接队列。这些队列被置放于BdbMultipleWorkQueues中时,总是先给予一个Key,然后将那些Key值相同的链接放在一起, 成为一个队列,也就是一个Queue。

这里就出现了一个问题,这个Key值到底该如何计算呢?事实上,这里也说的Key值,应该是做为一种标识符的形式存在。也就是说,它要与URL之间有一种内在的联系。

在Heritrix中,为每个队列赋上Key值的策略,也就是它的queue-assignment-policy。这就解答了第一个问题。

在默认的情况下,Heritrix使用 HostnameQueueAssignmentPolicy来解决Key值生成的问题。仔细看一下这个策略的名称就知道,这种策略其实是以链接的 Host名称为Key值来解决这个问题的。换句话也就是说,相同Host名称的所有URL都会被置放于同一个队列中间。

这种方式在很大程度上可以解决广域网中信息抓取 时队列的键值问题。但是,它对于某个单独网站的网页抓取,就出现了很大的问题。以Sohu的新闻网页为例,其中大部分的URL都来自于sohu网站的内 部,因此,如果使用了HostnameQueueAssignmentPolicy,则会造成有一个队列的长度非常长的情况。

在Heritrix中,一个线程从一个队列中取URL链接时,总是会先从队列的头部取出第一个链接,在这之后,这个被取出链接的队列会进入阻塞状态,直到待该链接处理完,它才会从阻塞状态中恢复。

假如使用 HostnameQueueAssignmentPolicy策略来应对抓取一个网站中内容的情况,很有可能造成仅有一个线程在工作,而其他所有线程都在 等待。这是因为那个装有绝大多数URL链接的队列几乎会永远处于阻塞状态,因此,别的线程根本获取不到其中的URI,在这种情况下,抓取工作会进入一种类 似于休眠的状态。因此,需要改变queue-assignment-policy来避免发生这种情况,这也就回答了第二个问题。


10.3.3 定制Queue-assignment-policy继承QueueAssignmentPolicy类

那么,被改变的Key值的生成方式,应该具有什么样的要求呢?从上面的叙述中可以知道,这个Key值最重要的一点就是应该能够有效的将所有的URL散列到不同的队列中,最终能使所有的队列的长度的方差较小,在这种情况下,才能保证工作线程的最大效率。

任何扩展queue-assignment- policy的默认实现的类,均继承自QueueAssignmentPolicy并覆写了其getClassKey()方法,getClassKey方 法的参数为一个链接对象,而我们的散列算法,正是要根据这个链接对象来返回一个值。

具体的算法就不说了,有许多种方法可以实现的。

heritrix多线程的方法,我在网上找了很多文章,试着配置,但是基本上都不合适,唯独这个合适了。

1.在org.archive.crawler.frontier下新建一个ELFHashQueueAssignmentPo

licy 类,这个类要注意继承自 QueueAssignmentPolicy。

2.在该类下编写代码如下:

package org.archive.crawler.frontier;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.httpclient.URIException;
import org.archive.crawler.datamodel.CandidateURI;
import org.archive.crawler.framework.CrawlController;
import org.archive.net.UURI;
import org.archive.net.UURIFactory;
public class ELFHashQueueAssignmentPolicy extends QueueAssignmentPolicy{
    private static final Logger logger = Logger
            .getLogger(ELFHashQueueAssignmentPolicy.class.getName());
        /**
         * When neat host-based class-key fails us
         */
        private static String DEFAULT_CLASS_KEY = "default...";
        
        private static final String DNS = "dns";

        public String getClassKey(CrawlController controller, CandidateURI cauri) {
            String scheme = cauri.getUURI().getScheme();
            String candidate = null;
            try {
                if (scheme.equals(DNS)){
                    if (cauri.getVia() != null) {
                        // Special handling for DNS: treat as being
                        // of the same class as the triggering URI.
                        // When a URI includes a port, this ensures
                        // the DNS lookup goes atop the host:port
                        // queue that triggered it, rather than
                        // some other host queue
                        UURI viaUuri = UURIFactory.getInstance(cauri.flattenVia());
                        candidate = viaUuri.getAuthorityMinusUserinfo();
                        // adopt scheme of triggering URI
                        scheme = viaUuri.getScheme();
                    } else {
                        candidate= cauri.getUURI().getReferencedHost();
                    }
                } else {
                    String uri = cauri.getUURI().toString();
                    long hash = ELFHash(uri);
//                    candidate =  cauri.getUURI().getAuthorityMinusUserinfo();
                    candidate =  Long.toString(hash % 100);
                }
                
                if(candidate == null || candidate.length() == 0) {
                    candidate = DEFAULT_CLASS_KEY;
                }
            } catch (URIException e) {
                logger.log(Level.INFO,
                        "unable to extract class key; using default", e);
                candidate = DEFAULT_CLASS_KEY;
            }
            if (scheme != null && scheme.equals(UURIFactory.HTTPS)) {
                // If https and no port specified, add default https port to
                // distinguish https from http server without a port.
                if (!candidate.matches(".+:[0-9]+")) {
                    candidate += UURIFactory.HTTPS_PORT;
                }
            }
            // Ensure classKeys are safe as filenames on NTFS
            return candidate.replace(':','#');
        }
        
        //hash散列
        public static long ELFHash(String str){
            long hash = 0;
            long x = 0;
            for (int i = 0; i < str.length(); i++) {
                hash = (hash << 4) + str.charAt(i);
                if ((x = hash & 0xF000000L) != 0) {
                    hash ^=(x >> 24);
                    hash &= ~x;
                }
            }
            return (hash & 0X7FFFFFF);
        }

    }


3.  修改AbstractFrontier 类的AbstractFrontier方法 

   关键代码段是:

   StringqueueStr = System.getProperty(AbstractFrontier.class.getName()+
                 "."+ ATTR_QUEUE_ASSIGNMENT_POLICY,
               
  ELFHashQueueAssignmentPolicy.class.getName()+ " " +

           //   HostnameQueueAssignmentPolicy.class.getName() + " "+

                IPQueueAssignmentPolicy.class.getName()+ " " +

                BucketQueueAssignmentPolicy.class.getName()+ " " +

                SurtAuthorityQueueAssignmentPolicy.class.getName());

    Patternp = Pattern.compile("\s*,\s*|\s+");

    String[] queues = p.split(queueStr);

    其中红色部分是新加的代码。

4.  修改heritrix.properties 中的配置

 

       #############################################################################
       # F R O N T I ER                                                          
       #############################################################################

       # List here all queue assignment policies you'd have show asa
       # queue-assignment-policy choice in AbstractFrontier derivedFrontiers
       # (e.g. BdbFrontier).
       org.archive.crawler.frontier.AbstractFrontier.queue-assignment-policy=

           org.archive.crawler.frontier.ELFHashQueueAssignmentPolicy

        //     org.archive.crawler.frontier.HostnameQueueAssignmentPolicy

           org.archive.crawler.frontier.IPQueueAssignmentPolicy

           org.archive.crawler.frontier.BucketQueueAssignmentPolicy

           org.archive.crawler.frontier.SurtAuthorityQueueAssignmentPolicy

           org.archive.crawler.frontier.TopmostAssignedSurtQueueAssignmentPolicy

       org.archive.crawler.frontier.BdbFrontier.level = INFO

   红色部分为新加部分。


10.3.5 在Prefetcher中取消robots.txt的限制

Robots.txt是一种专门用于搜索引擎网 络爬虫的文件,当构造一个网站时,如果作者希望该网站的内容被搜索引擎收录,就可以在网站中创建一个纯文本文件robots.txt,在这个文件中,声明 该网站不想被robot访问的部分。这样,该网站的部分或全部内容就可以不被搜索引擎收录了,或者指定搜索引擎只收录指定的内容。

Heritrix在其说明文档中,表明它是一个 完全遵守robots.txt协议的网络爬虫。这一点固然在宣传上起到了一定的作用。但是,在实际的网页采集过程中,这并不是一种最好的作法。因为大部分 的网站并不会放置一个robots.txt文件以供搜索引擎读取,在互联网信息以几何级数增长的今天,网站总是在希望自己的内容不被人所利用的同时,又希 望自己能够被更多的用户从搜索引擎上检索到。

不过幸好,robots.txt协议本身只是一种附加的协议,网站本身并不能了解究竟哪些Socket联接属于爬虫哪些属于正常的浏览器连接。所以,不遵守robos.txt协议成为了更多搜索引擎的首选。

使用过Heritrix的朋友就会发现这样一个 问题,如果当一个网站没有放置robots.txt文件时,Heritrix总是要花上大量的时间试图去访问这样一个文件,甚至可能retry很多次。这 无疑很大的降低了抓取效率。因此,为了提高抓取的效率,可以试着将对robots.txt的访问部分去除。

在Heritrix中,对robots.txt 文件的处理是处于PreconditionEnforcer这个Processor中的。PreconditionEnforcer是一个 Prefetcher,当处理时,总是需要考虑一下当前这个链接是否有什么先决条件要先被满足的,而对robots.txt的访问则正好是其中之一。在 PreconditionEnforcer中,有一个private类型的方法,它的方法签名为:

private boolean considerRobotsPreconditions(CrawlURI curi)

该方法的含义为:在进行对参数所表示的链接的抓取前,看一下是否存在一个由robots.txt所决定的先决条件。很显然,如果对每个链接都有这样的处理。那么,很有可能导致整个抓取任务的失败。因此,需要对它进行调整。

这个方法返回true时的含义为需要考虑 robots.txt文件,返回false时则表示不需要考虑robots.txt文件,可以继续将链接传递给后面的处理器。所以,最简单的修改办法就是 将这个方法整个注释掉,只留下一个false的返回值。经过笔者的试验,这种方法完全可行,抓取的速度提高了至少一半以上!