作业第三步,统计URL的出度和入读

      这真是一件令人兴奋的事,终于在今天搞定了网页抓取的出入度统计工作!
      上周我们在抓取完所有的网页之后,得到了crawl.log文件,按照最初的计划,由于此文件中记录了全部的抓取信息,所以想利用此文件来分析该抓取任务的出入度,但是当打开这个庞大的文件的时候,我们实在是老虎吃螃蟹---无处可下爪!这个9M文件实在是令人生畏。但是我们起初并没有放弃这个想法,而是从中研究这个文件的生成过程,从而想利用这个思路企图寻求更好的解决方法,这边有了漫长地研究Heritrix源码的过程。
      1.研究Heritrix的工作机制
       Heritrix在启动的时候,有非常关键的几个类是:
             XMLSettingsHandler;
             CrawlController;
             CrawlOrder;
      这个几个类在搭配order.xml之后,可以创建一个自己的Heritrix,并且可以使其运行起来。其具体的过程大概是:

              test.xmlSettingsHandler = new XMLSettingsHandler(file);
              test.crawlController = new CrawlController();
              test.crawlController.initialize(test.xmlSettingsHandler);
              test.crawlController.requestCrawlStart();

      在Heritrix运行起来之后,开始协调Frontier、Extractor等进行抓取工作。并且利用Heritrix已经定制好的Queue-assignment-policy来执行任务,在这些策略之下,可以定制我们自己的ELFlush策略,这边是我们的下一步,也是最后一步的工作。然后Heritrix根据定制的工作设置,开始进行抓取工作, 在研究Heritrix生成crawl.log文件的过程中,我们发现,在这个文件中,对每一个URL的日志跟踪都具有相同的格式,我们想是不是这样可以根据这个跟踪来实现对每一个URL出入度的跟踪,于是我们开始研究这个文件。
      在这个crawl.log文件中,在每个URL之后都有几个大写字母的标示,如“L”,“LLL”,“E”,“XP”等,我们猜测这些标示可能会和这些被抓取的页面有关,但是在整个工程里面找了很久都没有找到,直到昨天晚上在分析Extrator是无意间看到“NAVLINK_HOP”这个常量,才通过寻其源头找到了
             org.archive.crawler.extractor.Link;
      这个过程相当痛苦,一度有一种山穷水尽的感觉,不要嘲笑我们没有使用Eclipse的全局搜索,我们用了无数遍Eclipse的搜索功能,都没有能够找到这些关于日志文件的表示声明。这些声明如下:
 
      自此,我们已经明白了crawl.log文件中的所有标示的含义,但是问题又来了,我们究竟该怎么样来统计这些标示呢?从标示的内容来看,"L"代表的就是抓取到了新的超链接,这个便是对我们统计出入度最有价值的东西,那究竟怎么来统计这些"L"呢?很多的URL之后都是打出好几个"L",这在一定程度上表明了这个URL的连接深度,但是庞大的日志文件为我们的分析带来了诸多的不便,于是我们想通过修改代码来修改日志文件的创建,从而使得我们只获取到对我们有用的信息。

      2.源代码的研究

      我们寻找到调用"NAVLINK_HOP"常量的

            org.archive.crawler.extractor.ExtractorHTML;

      这个类中,继承自Extractor这个抽象类,而且只实现了两个方法,一个便是重载父类Extractor的extract()方法,另一个是report()方法,很显然第二个方法是用来打报告的,而第一个方法便是最重要的抓取方法。然而当我们去阅读extract()方法的时候,发现它被多态重载了很多次,究竟哪一个才是我们需要的,这将又是一个很有挑战性的问题。

      面对这样的难题,而且主要是修改这个代码可能会带来风险,于是我们决定仿照ExtractorHTML重写一个继承自Extractor的类,将这个类配置到Heritrix的Extractor调用队列里,用它来完成我们想要完成的工作。

      3.自定义MyExtractor extends Extrator

      这个类放在我们自己的包

            com.own.heritrix.test

      在这个包中,同时定义了一个类ExtractorUriMap,这个类中存放了两个全局的HashMap---outDegree和inDegree,这两个HashMap就是用来统计每个URL的出度和入度的。

      在MyExtractor类中,我们定义了如下的方法

      首先是extrac()方法:

      在这个方法中完成了一下的工作:

       (1)将抓取到的CrawlURI对象进行解析,获取到其所对应的网页内容;

       (2)利用正则表达式,将网页的内容与"<a href="">"这样的超链接模式进行匹配,读出每个URL之下对应的所有的超链接数,而这些超链接数便是该URL所对应的出度;

       (3)对每一个URL下的超链接进行遍历。初始时该URL在ExtractorUriMap.outDegree中不存在,则用此超链接的名字字符串做key,将其出度做value,存入到ExtractorUriMap.outDegree中;而如果该URL已经存在,则将其出度加1保存。

      这样便完成了对每一个URL的出度的统计,并在ExtractorUriMap的PrintOutMapResult()中将结果存入到"D:/out_degree.txt"文件中,以便之后对其进行分析。

      在MyExtractor类中,还有一个方法是addLinkFromString()方法:

      从这个方法的private生命就可以看出,这个方法是被其类内部的extract方法调用的,没错,我们写它的目的就是用它来向下封装调用时更好处理。在这个方法中调用了一个方法:

      这个方法对我们而言简直是太重要了!

      由于早期试图在extract()方法中直接添加对入度的统计处理,但是发现,每个URL在抓取的时候,会在已经抓取过的URL中进行比对,如果该URL已经抓取过,则此次抓取不再执行extract()方法,这是我们非常头疼的。

      我们本来在考虑统计URL的入度的时候有两种策略:

      a.)试图在分析道每个页面的超链接的时候,将超链接存入到ExtractorUriMap.inDegree中,只要在整个抓取过程中该链接又被发现过,便将其入度加1保存。但是这有个问题,因为网页的超链接可能会有很多都是相对路径,而不同的父路径下可能会有相同的子路径,这样便会有不同的路径下会有相同的.html文件,这便是非常令人懊恼的事情。因为如果直接统计超链接,可能会使得某些不同路径下的相同.html文件被当做相同的.html文件处理,这样的统计就是错误的。所以,这个方法我们在讨论之后,决定放弃。

      b.)另外一种做法就是在统计出度的时候直接统计入度,因为每个网页都会被抓取并解析,所以我们想当然地想在每次进入解析的时候,将该网页URL的入度加1保存。然而,Heritrix在调用BdbFrontier时,是会对每个URL进行比对的,这些重复URL通过BdbUriUniqFilter被过滤掉了,所以统计出来的所有入度都是0。这就使得我们队这种方法也产生了质疑,如果我们企图去在BdbUriUniqFilter中添加新的代码,这将有可能是件灰常灰常痛苦的过程,因为其代码太多,而且我们还不知道它究竟是在什么地方被组合或者聚合的。就在这走投无路的时候,我们想到可不可以再去利用方法a,用某种方法获取到这些链接的绝对路径URL,因为我们在解读crawl.log文件的时候,发现的URL都是绝对路径的,所以应该会有这样的方法存在。

      于是我们去解读

            org.archive.crawler.datamodel.CrawlURI;

      这个类中有一个方法,也就是上面列出来被我们的addLinkFromString()方法所调用的

            CrawlURI.createAndAddLinkRelativeToBase();

      从这个方法的解释来看,这个方法是在每一个链接被发现的时候,将其创建并添加到相关链接队列中。在这个方法中调用了如下的内容:

      这个方法内部又一次调用了CrawlURI内部的方法addOutLink()方法,我们没有再去研究这个方法,而是看到其参数列表中有一个:

            UURIFactory.getInstance(getBaseURI(),url);

      这个调用返回的是一个UURI对象,这个对象是一个可用的URI对象,这表明这个调用的目的就是将url传入,将其可以调用的全局绝对路径URL返回,这个结果正是我们想要的结果!

      当发现这一点之后,我们立即在这个createAndAddLinkRelativeToBase()方法中添加对URL入度的存储,其存储方法也和出度的存储方法类似,然后将结果打印出来,并保存在"D:/in_degree.txt"文件中。

      但是当我们在分析这个打印结果的时候却发现,在分析每个页面的时候,这个方法会被调用两遍,经过研究讨论,我们认为这个方法的调用机制是这样的:

      1).首先,在进入一个网页之后,extract会将所有网页内容根据我们设定好的正则表达式进行一遍匹配,这一遍匹配就是将所有的超链接通过这个createAndAddLinkRelativeToBase()方法来创建一个可用的全局超链接;

      2).然后,这个createAndAddLinkRelativeToBase()方法会再次被调用,而这次调用的目的是进入到这个超链接,对这个链接进行访问。这也是我们目前根据打印出来的结果作出的一个判断,因为只有一个地方调用这个方法,而这个方法是执行两遍的。这样的结果是所有的URL的入度都被多算了,那究竟是多算了多少呢?我们根据打印出来的结果判断,所有的入度被*2+1,因为第一次在Extractor.inDegree中创建这个入度的时候,其入度的值是0,所以第一次遍历的时候并没有+1,所以根据这一点,我们将保存在文件中的入度的值只要+1再/2就可以了,这样即使入度为0,它算出的结果也还是0。

 

      最后一步的工作就是究竟在什么地方来调用将HashMap中的数据保存到文件中的,我们经过对BdbFrontier的类关系的研究,特别是它和BdbMultipleWorkQueue以及BdbWorkQueue的研究,得出在

            org.archive.crawler.frontier.BdbFrontier;

      有一个非常重要的方法

            BdbFrontier.crawlEnded()方法,这个方法的作用就是在抓取工作全部结束之后所要做的东西,显然我们的保存步骤就是在这个地方调用的。

    

      到这里,我们就已经全部解决了如何统计出度和入度的问题了,我们再测试的过程中,是利用我们自己手写的一个Html拓扑结构,并利用Apache发布,用另外一台机器去抓取访问的,最后我们统计的结果与我们最初设计的拓扑结构完全吻合,所以证明我们的做法是正确,这使得我们感到很欣慰。

 

      接下来的工作就是利用这些统计出来的出入度来进行Page Rank算法,很快将结束这次的作业,很期待!呵呵~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值