Heritrix爬虫 ----(3)优化

1 抓取任务中定制 URL 队列分配策略
2 主题抓取
3 增量爬虫
4 Heritrix工具化

1 优化Heritrix爬取速度

1.1 优化Frointier(边界控制器)

默认情况下,Heritrix使用域名分配策略(HostnameQueueAssignmentPolicy)将爬取的URL分配到不同的队列抓取。该策略的特点是:根据域名作为分组依据, 即同一域名下的URL将被分配进同一个抓取队列。这样在抓取时会单线程抓取。所以需要将要爬取的多个URL分散至多个队列中并行处理, 以提高爬取速度。
大部分解决该问题的方法都是使用ELFHash散列函数。

  //代码如下
    public 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 & 0xF0000000L) != 0) {
                hash ^= (x >> 24);
                hash &= ~x;
            }
        }
        return (hash & 0x7FFFFFFF);
    }

但通过实际测试(文献), 发现效果并不理想。而使用Hflp散列函数可以提高Heritrix的抓取效率, 实现负载均衡。

public static long Hflp (String str) {
        byte[] url = str.getBytes();
        long n = 0;
        byte[] bytes = intTobyte(n);
        for (int i= 0; i < url.length; i++) {
            bytes[i % 4] ^= url[i]; 
        }
        n = byteToInt(bytes);
        return n % 0x7FFFFFFF;        
    }

    public static byte[] intTobyte (long i) {
        byte[] bytes = new byte[4];
        bytes[0] = (byte) (i & 0xFF);
        bytes[1] = (byte) ((i >> 8) & 0xFF);
        bytes[2] = (byte) ((i >> 16) & 0xFF);
        bytes[3] = (byte) ((i >> 24) & 0xFF);
        return bytes;
    }

    public static int byteToInt (byte[] bytes) {
        int a = bytes[0] & 0xFF;
        int b = (bytes[1] & 0xFF) << 8;
        int c = (bytes[2] & 0xFF) << 16;
        int d = (bytes[3] & 0xFF) << 24;
        return a+b+c+d;
    }

上面代码要生效,还要进行如下配置:

  • 新建HflpQueueAssignmentPolicy,继承QueueAssignmentPolicy,实现其getClassKey方法。
public class HflpQueueAssignmentPolicy extends QueueAssignmentPolicy {
    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 = Hflp(uri);
                candidate = Long.toString(hash % 100);
                //                candidate =  cauri.getUURI().getAuthorityMinusUserinfo();
            }

            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(':','#');
    }
}
  • 代码需要写在新类中,然后在Heritrix.proprities文件中修改默认调用的 HostnameQueueAssignmentPolicy,而使用新的 HflpQueueAssignmentPolicy 配置如下:
# List here all queue assignment policies you'd have show as a
# queue-assignment-policy choice in AbstractFrontier derived Frontiers
# (e.g. BdbFrontier).
# HostnameQueueAssignmentPolicy
org.archive.crawler.frontier.AbstractFrontier.queue-assignment-policy = org.archive.crawler.frontier.ELFHashQueueAssignmentPolicy org.archive.crawler.frontier.IPQueueAssignmentPolicy org.archive.crawler.frontier.BucketQueueAssignmentPolicy org.archive.crawler.frontier.SurtAuthorityQueueAssignmentPolicy org.archive.crawler.frontier.TopmostAssignedSurtQueueAssignmentPolicy
org.archive.crawler.frontier.ELFHashQueueAssignmentPolicy \
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

1.2 优化Extractor(抽取链)

这一步的优化是通过实现抽象类Extractor,覆写其extract方法,完成对于抓取URL的过滤。主要分为两步:

(1)新建类,实现Extractor抽象类中的extract方法
public class Extractor_Cnpc extends Extractor{
    public static final String patternString1 = ".*href\\s*=\\s*(\"|'|)http://.*";
    public static final String patternString2 = "<\\s*[aA][\\s\\S]+(href\\s*=\\s*(\"|'|)(http|https)://[^>]+\\s*)>";
    public static final String patternString3 = "<\\s*[aA][\\s\\S]+(href\\s*=\\s*(\"|'|)((http|https)://[^\"]+(\")))";
    public static final String patternString4 = "";//Extractor_Cnpc-row      "])(.*)>"
    public static final String patternString5 = "(http://|https://)+((\\w|\\.|\\/|-|=|\\?|&)+)+";//抽取特征

    public Extractor_Cnpc(String name, String description) {
        super(name, description);
    }

    public Extractor_Cnpc(String name) {
        super(name, "Cnpc news extractor");
    }

    private static String URL_REGEX  = null;

    public static Pattern pattern1 = Pattern.compile(patternString1,
            Pattern.DOTALL);
    public static Pattern pattern2 = Pattern.compile(patternString2,
            Pattern.DOTALL);
    public static Pattern pattern3 = Pattern.compile(patternString3,
            Pattern.DOTALL);
    public static Pattern pattern4 = Pattern.compile(patternString4,
            Pattern.CASE_INSENSITIVE);
    public static Pattern pattern5 = Pattern.compile(patternString5,
            Pattern.DOTALL);

    @Override
    protected void extract(CrawlURI curi) {
        String url = "";
        try {
            HttpRecorder hr = curi.getHttpRecorder();
            if(hr == null){
                throw new IOException("HttpRecorder is null");
            }
            ReplayCharSequence cs = hr.getReplayCharSequence();
            if(cs == null){
                return;
            }
            String context = cs.toString();
            Pattern pattern = Pattern.compile(patternString4, Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(context);
            while(matcher.find()){
                url = matcher.group();//先将url截取出来
                writeUriIntoTxt(url,"F:\\data\\test\\output.txt");
                url = url.replace("\"", "");//替换",清除前后双引号
                URL_REGEX = extractor_Feature(readCrawlUrl());
                if(url.matches(URL_REGEX)){//和正则进行匹配
                    curi.createAndAddLinkRelativeToBase(url, context, Link.NAVLINK_HOP);
                    writeUriIntoTxt(url,getWriteUrlPath());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
(2)通过正则表达式过滤,抽取出符合规则的URL,加入抓取队列。
(3)其他抽取的方法
  • 使用正则表达式配合HTMLParser的方式抽取url
  • 配置模版
  • 抽取出JavaScript代码中的信息
    http://www.cnblogs.com/cczw/archive/2012/07/16/2593957.html
  • 抽取PDF文件
    Apache PDFbox是一个开源的、基于Java的、支持PDF文档生成的工具库,它可以用于创建新的PDF文档,修改现有的PDF文档,还可以从PDF文档中提取所需的内容。Apache PDFBox还包含了数个命令行工具。
    http://blog.csdn.net/chszs/article/details/9026439
  • 使用POI抽取office系列
    这个我也有写一些:https://www.jianshu.com/p/0a32d8bd6878
  • 区域控制
    纯真ip解析出ip地址,然后利用正则判断出指定区域的ip,如中国湖北、中国北京等,进而确定地址。

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

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

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

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

方法如下:

(1)修改PreconditionEnforcer

在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的返回值。这种方法完全可行,抓取的速度提高了至少一半以上!

2 主题抓取(增量爬虫)

  • URL:判断抓取的URL和种子URL是否属于一个主题(增量,则重复);
  • 内容:将爬取下来的网页抽取出内容,计算与种子网页的相似度,在阈值之内的则认为是一个主题的网页(增量,则重复);
  • Heritrix自带方法
    (1)在Heritrix爬取结果文件夹log文件中的recover.gz文件中,自动记录了上一次已抓取的URL信息。所以可以对该文件中记录的URL不断过滤累加, 每次爬取时, 将这些URL写入到已抓取队列, 避免重复信息的二次爬取。
    (2)将Heritrix的配置文件order.xml中的属性值表示的是当前以抓取的URL,Heritrix每次启动之前都会读取这个路径下的文件中的URL,不会抓取。所以,可以将这个地址设置为recover.gz文件地址, 在对recover.gz进行累加,从而实现对网站长时间监测过程中的增量爬取。

3 Heritrix工具化

将Heritrix做成一个工具包直接用。方法如下:

  • 将Heritrix做成一个jar包,或者是一个.bat文件。
    (1)将Heritrix代码写好,例如前面说的抓取优化。
    (2)使用固定的order.xml(爬取某网站的order.xml)覆盖原始的order.xml,这样每次进入爬取参数就已经配置好了,可以直接进入抓取。
    (3)修改war包(admin.war),修改web.xml中的拦截器,不进入login页面。替换原有。
    (4)统一输出路径:修改mirror文件输出地址。如下图,


    Heritrix爬虫 ----(3)优化_第1张图片
    image.png

    (5)生成jar包,并通过.bat文件启动jar文件。

  • 通过作业调度框架Quartz实现该功能

P.S. Heritrix三部曲真难写,不过总结、分享知识很开心。

END

你可能感兴趣的:(Heritrix爬虫 ----(3)优化)