Nutch教程——导入Nutch工程,执行完整爬取 by 逼格DATA

来源:互联网 发布:波士顿矩阵图 编辑:程序博客网 时间:2024/06/06 01:36

在使用本教程之前,需要满足条件:

1)有一台Linux或Linux虚拟机

2)安装JDK(推荐1.7)

3)安装Apache Ant


下载Nutch源码:

推荐使用Nutch 1.9,官方下载地址:http://mirrors.hust.edu.cn/apache/nutch/1.9/apache-nutch-1.9-src.zip


安装IDE:

推荐使用Intellij或者Netbeans,如果用eclipse也可以,不推荐。
Intellij官方下载地址:http://www.jetbrains.com/idea/download/

转换:

Nutch源码是用ant进行构建的,需要转换成eclipse工程才可以导入IDE正确使用,Intellij和Netbeans都可以支持ecilpse工程。
解压下载的apache-nutch-1.9-src.zip,得到文件夹apache-nutch-1.9。
在执行转换之前,我们先修改一下ivy中的一个源,将它改为开源中国的镜像,否则转换的过程会非常缓慢。(ant源码中并没有附带依赖jar包,ivy负责从网上自动下载jar包)。
修改apache-nutch-1.9文件夹中的ivy/ivysettings.xml:



找到:
[html] view plain copy
  1. <property name="repo.maven.org"  
  2.       value="http://repo1.maven.org/maven2/"  
  3.       override="false"/>  



将value修改为http://maven.oschina.net/content/groups/public/
修改后:
[html] view plain copy
  1. <property name="repo.maven.org"  
  2.       value="http://maven.oschina.net/content/groups/public/"  
  3.       override="false"/>  


保存并退出,保证当前目录为apache-nutch-1.9,执行命令:
[html] view plain copy
  1. ant eclipse -verbose  
然后耐心等待,这个过程ant会根据ivy从中心仓库下载各种依赖jar包,可能要十几分钟。


-verbose参数加上之后可以看到ant过程的详细信息。

10分钟左右,转换成功:



打开Intellij, File -> Import Project ->选择apache-nutch-1.9文件夹,确定后选择Import project from external model(Eclipse)



一直点击next到结束。成功将项目导入Intellij:




源码导入工程后,并不能执行完整的爬取。Nutch将爬取的流程切分成很多阶段,每个阶段分别封装在一个类的main函数中。在外面通过Linux Shell调用这些main函数,来完整爬取的流程。我们在后续教程中会对流程调度做一个详细的说明。
下面我们来运行Nutch中最简单的流程:Inject。我们知道爬虫在初始阶段,是需要人工给出一个或多个url,作为起始点(广度遍历树的树根)。Inject的作用,就是把用户写在文件里的种子(一行一个url,是TextInputFormat),插入到爬虫的URL管理文件(crawldb,是SequenceFile)中。
从src文件夹中找到org.apache.nutch.crawl.Injector类:



在阅读Nutch源码的过程中,最重要的就是找到每个类的main函数:

可以看到,main函数其实是利用ToolRunner,执行了run(String[] args)。这里ToolRunner.run会从第二个参数(new Injector())这个对象中,找到run(String[] args)这个方法执行。
从run方法中可以看出来,String[] args需要有2个参数,第一个参数表示爬虫的URL管理文件夹(输出),第二个参数表示种子文件夹(输入)。对hadoop中的map reduce程序来说,输入文件夹是必须存在的,输出文件夹应该不存在。我们创建一个文件夹 /tmp/urls,来存放种子文件(作为输入)。

在seed.txt中加入一个种子URL

[html] view plain copy
  1. http://www.cnbeta.com/  

指定一个文件夹/tmp/crawldb来作为URL管理文件夹(输出)
有一种简单的方法来指定args,直接在main函数下加一行:
[java] view plain copy
  1. args=new String[]{"/tmp/crawldb","/tmp/urls"};  



运行这个类,我们会发现报错了(下面只给了错误的一部分):
[plain] view plain copy
  1. Caused by: java.lang.RuntimeException: x point org.apache.nutch.net.URLNormalizer not found.  
  2.     at org.apache.nutch.net.URLNormalizers.<init>(URLNormalizers.java:123)  
  3.     at org.apache.nutch.crawl.Injector$InjectMapper.configure(Injector.java:84)  
  4.     ... 23 more  

这是因为用这种方式执行,按照Nutch默认的配置,不能正确地加载插件。我们需要修改Nutch的配置文件,为插件文件夹指定一个绝对路径,修改conf/nutch-default.xml文件,找到:
[html] view plain copy
  1. <property>  
  2.         <name>plugin.folders</name>  
  3.         <value>plugins</value>  
  4.         <description>Directories where nutch plugins are located.  Each  
  5.             element may be a relative or absolute path.  If absolute, it is used  
  6.             as is.  If relative, it is searched for on the classpath.</description>  
  7.     </property>  
将value修改为绝对路径  apache-nutch-1.9所在文件夹+"/src/plugin",比如我的配置:
[html] view plain copy
  1. <property>  
  2.   <name>plugin.folders</name>  
  3.   <value>/home/hu/apache/apache-nutch-1.9/src/plugin</value>  
  4.   <description>Directories where nutch plugins are located.  Each  
  5.   element may be a relative or absolute path.  If absolute, it is used  
  6.   as is.  If relative, it is searched for on the classpath.</description>  
  7. </property>  

建议在修改nutch-default.xml时,将原来的配置注释,并复制一份新的修改,方便还原:
现在再运行Injector.java,看到结果:

运行成功。


读取爬虫文件:

我们查看程序的输出 tree /tmp/crawldb   ,如果没有tree命令,就直接用资源管理器之类的查看吧:

查看里面的data文件:
[plain] view plain copy
  1. vim /tmp/crawldb/current/part-00000/data  

这是一个SequenceFile,Nutch中除了Inject的输入(种子)之外,其他文件  全部以SequenceFile的形式存储。SequenceFile的结构如下:
[plain] view plain copy
  1. key0 value0  
  2. key1 value1  
  3. key2  value2  
  4. ......  
  5. keyn  valuen  
以key value的形式,将对象序列(key value序列)存储到文件中。我们从SequenceFile头部可以看出来key value的类型。

上面的SequenceFile中,可以看出来,key的类型是org.apache.hadoop.io.Text,value的类型是org.apache.nutch.crawl.CrawlDatum。
下面教程给出如何读取SequenceFile的代码。
新建一个类org.apache.nutch.example.InjectorReader

[java] view plain copy
  1. package org.apache.nutch.example;  
  2.   
  3. import org.apache.hadoop.conf.Configuration;  
  4. import org.apache.hadoop.fs.FileSystem;  
  5. import org.apache.hadoop.fs.Path;  
  6. import org.apache.hadoop.io.SequenceFile;  
  7. import org.apache.hadoop.io.Text;  
  8. import org.apache.nutch.crawl.CrawlDatum;  
  9.   
  10. import java.io.IOException;  
  11.   
  12.   
  13. /** 
  14.  * Created by hu on 15-2-9. 
  15.  */  
  16. public class InjectorReader {  
  17.     public static void main(String[] args) throws IOException {  
  18.         Configuration conf=new Configuration();  
  19.         Path dataPath=new Path("/tmp/crawldb/current/part-00000/data");  
  20.         FileSystem fs=dataPath.getFileSystem(conf);  
  21.         SequenceFile.Reader reader=new SequenceFile.Reader(fs,dataPath,conf);  
  22.         Text key=new Text();  
  23.         CrawlDatum value=new CrawlDatum();  
  24.         while(reader.next(key,value)){  
  25.             System.out.println("key:"+key);  
  26.             System.out.println("value:"+value);  
  27.         }  
  28.         reader.close();  
  29.     }  
  30. }  

运行结果:
[plain] view plain copy
  1. key:http://www.cnbeta.com/  
  2. value:Version: 7  
  3. Status: 1 (db_unfetched)  
  4. Fetch time: Mon Feb 09 13:20:36 CST 2015  
  5. Modified time: Thu Jan 01 08:00:00 CST 1970  
  6. Retries since fetch: 0  
  7. Retry interval: 2592000 seconds (30 days)  
  8. Score: 1.0  
  9. Signature: null  
  10. Metadata:   
  11.     _maxdepth_=1000  
  12.     _depth_=1  

我们可以看到,程序读出了刚才Inject到crawldb的url,key是url,value是一个CrawlDatum对象,这个对象用来维护爬虫的URL管理信息,我们可以看到一行:
[plain] view plain copy
  1. Status: 1 (db_unfetched)  
表示当前url为未爬取状态,在后续流程中,爬虫会从crawldb取未爬取的url进行爬取。

完整爬取:


下面给出的是各位最期待的代码,就是如何用Nutch完成一次完整的爬取。官方代码在1.7之前(包括1.7),包含一个Crawl.java,这个代码的main函数可以执行一次完整的爬取,但是从1.7之后就取消了。只保留了使用Linux Shell来调用每个流程,来完成爬取的方法。但是好在取消的Crawl.java修改一下,还是可以使用的。
在爬取之前,我们先修改一下conf/nutch-default.xml中的一个地方,找到:
[html] view plain copy
  1. <property>  
  2.   <name>http.agent.name</name>  
  3.   <value></value>  
  4.   <description>HTTP 'User-Agent' request header. MUST NOT be empty -   
  5.   please set this to a single word uniquely related to your organization.  
  6.   
  7.   NOTE: You should also check other related properties:  
  8.   
  9.     http.robots.agents  
  10.     http.agent.description  
  11.     http.agent.url  
  12.     http.agent.email  
  13.     http.agent.version  
  14.   
  15.   and set their values appropriately.  
  16.   
  17.   </description>  
  18. </property>  

在<value></value>中随意添加一个值,修改为:
[html] view plain copy
  1. <property>  
  2.   <name>http.agent.name</name>  
  3.   <value>test</value>  
  4.   <description>HTTP 'User-Agent' request header. MUST NOT be empty -   
  5.   please set this to a single word uniquely related to your organization.  
  6.   
  7.   NOTE: You should also check other related properties:  
  8.   
  9.     http.robots.agents  
  10.     http.agent.description  
  11.     http.agent.url  
  12.     http.agent.email  
  13.     http.agent.version  
  14.   
  15.   and set their values appropriately.  
  16.   
  17.   </description>  
  18. </property>  

这个值会在发送http请求时,作为User-Agent字段。

下面给出代码:
[java] view plain copy
  1. package org.apache.nutch.crawl;  
  2.   
  3. import java.util.*;  
  4. import java.text.*;  
  5.   
  6. // Commons Logging imports  
  7. import org.apache.commons.lang.StringUtils;  
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10.   
  11. import org.apache.hadoop.fs.*;  
  12. import org.apache.hadoop.conf.*;  
  13. import org.apache.hadoop.mapred.*;  
  14. import org.apache.hadoop.util.Tool;  
  15. import org.apache.hadoop.util.ToolRunner;  
  16. import org.apache.nutch.parse.ParseSegment;  
  17. import org.apache.nutch.indexer.IndexingJob;  
  18. //import org.apache.nutch.indexer.solr.SolrDeleteDuplicates;  
  19. import org.apache.nutch.util.HadoopFSUtil;  
  20. import org.apache.nutch.util.NutchConfiguration;  
  21. import org.apache.nutch.util.NutchJob;  
  22.   
  23. import org.apache.nutch.fetcher.Fetcher;  
  24.   
  25. public class Crawl extends Configured implements Tool {  
  26.     public static final Logger LOG = LoggerFactory.getLogger(Crawl.class);  
  27.   
  28.     private static String getDate() {  
  29.         return new SimpleDateFormat("yyyyMMddHHmmss").format  
  30.                 (new Date(System.currentTimeMillis()));  
  31.     }  
  32.   
  33.   
  34.     /* Perform complete crawling and indexing (to Solr) given a set of root urls and the -solr 
  35.        parameter respectively. More information and Usage parameters can be found below. */  
  36.     public static void main(String args[]) throws Exception {  
  37.         Configuration conf = NutchConfiguration.create();  
  38.         int res = ToolRunner.run(conf, new Crawl(), args);  
  39.         System.exit(res);  
  40.     }  
  41.   
  42.     @Override  
  43.     public int run(String[] args) throws Exception {  
  44.   
  45.         /*种子所在文件夹*/  
  46.         Path rootUrlDir = new Path("/tmp/urls");  
  47.         /*存储爬取信息的文件夹*/  
  48.         Path dir = new Path("/tmp","crawl-" + getDate());  
  49.         int threads = 50;  
  50.         /*广度遍历时爬取的深度,即广度遍历树的层数*/  
  51.         int depth = 2;  
  52.         long topN = 10;  
  53.   
  54.         JobConf job = new NutchJob(getConf());  
  55.         FileSystem fs = FileSystem.get(job);  
  56.   
  57.         if (LOG.isInfoEnabled()) {  
  58.             LOG.info("crawl started in: " + dir);  
  59.             LOG.info("rootUrlDir = " + rootUrlDir);  
  60.             LOG.info("threads = " + threads);  
  61.             LOG.info("depth = " + depth);  
  62.             if (topN != Long.MAX_VALUE)  
  63.                 LOG.info("topN = " + topN);  
  64.         }  
  65.   
  66.         Path crawlDb = new Path(dir + "/crawldb");  
  67.         Path linkDb = new Path(dir + "/linkdb");  
  68.         Path segments = new Path(dir + "/segments");  
  69.         Path indexes = new Path(dir + "/indexes");  
  70.         Path index = new Path(dir + "/index");  
  71.   
  72.         Path tmpDir = job.getLocalPath("crawl"+Path.SEPARATOR+getDate());  
  73.         Injector injector = new Injector(getConf());  
  74.         Generator generator = new Generator(getConf());  
  75.         Fetcher fetcher = new Fetcher(getConf());  
  76.         ParseSegment parseSegment = new ParseSegment(getConf());  
  77.         CrawlDb crawlDbTool = new CrawlDb(getConf());  
  78.         LinkDb linkDbTool = new LinkDb(getConf());  
  79.   
  80.         // initialize crawlDb  
  81.         injector.inject(crawlDb, rootUrlDir);  
  82.         int i;  
  83.         for (i = 0; i < depth; i++) {             // generate new segment  
  84.             Path[] segs = generator.generate(crawlDb, segments, -1, topN, System  
  85.                     .currentTimeMillis());  
  86.             if (segs == null) {  
  87.                 LOG.info("Stopping at depth=" + i + " - no more URLs to fetch.");  
  88.                 break;  
  89.             }  
  90.             fetcher.fetch(segs[0], threads);  // fetch it  
  91.             if (!Fetcher.isParsing(job)) {  
  92.                 parseSegment.parse(segs[0]);    // parse it, if needed  
  93.             }  
  94.             crawlDbTool.update(crawlDb, segs, truetrue); // update crawldb  
  95.         }  
  96.         /* 
  97.         if (i > 0) { 
  98.             linkDbTool.invert(linkDb, segments, true, true, false); // invert links 
  99.  
  100.             if (solrUrl != null) { 
  101.                 // index, dedup & merge 
  102.                 FileStatus[] fstats = fs.listStatus(segments, HadoopFSUtil.getPassDirectoriesFilter(fs)); 
  103.  
  104.                 IndexingJob indexer = new IndexingJob(getConf()); 
  105.                 indexer.index(crawlDb, linkDb, 
  106.                         Arrays.asList(HadoopFSUtil.getPaths(fstats))); 
  107.  
  108.                 SolrDeleteDuplicates dedup = new SolrDeleteDuplicates(); 
  109.                 dedup.setConf(getConf()); 
  110.                 dedup.dedup(solrUrl); 
  111.             } 
  112.  
  113.         } else { 
  114.             LOG.warn("No URLs to fetch - check your seed list and URL filters."); 
  115.         } 
  116.         */  
  117.         if (LOG.isInfoEnabled()) { LOG.info("crawl finished: " + dir); }  
  118.         return 0;  
  119.     }  
  120.   
  121.   
  122. }  
运行成功,对网站进行了一个2层的爬取,爬取信息都保存在/tmp/crawl+时间的文件夹中。
[plain] view plain copy
  1. 2015-02-09 14:23:17,171 INFO  crawl.CrawlDb (CrawlDb.java:update(115)) - CrawlDb update: finished at 2015-02-09 14:23:17, elapsed: 00:00:01  
  2. 2015-02-09 14:23:17,171 INFO  crawl.Crawl (Crawl.java:run(117)) - crawl finished: /tmp/crawl-20150209142212  

有些时候爬虫爬一层就停止了,有几种原因:
1)种子对应的页面大小超过配置的上限,页面被忽略。
2)nutch默认遵循robots协议,有可能robots协议禁止了爬取,不过出现这种情况日志会给出相关信息。
3)网页没有被正确爬取(这种情况少)。
爬很多门户网站时容易出现第一种情况,这种情况只需要找到conf/nutch-default.xml中的:
[html] view plain copy
  1. <property>  
  2.   <name>http.content.limit</name>  
  3.   <value>65536</value>  
  4.   <description>The length limit for downloaded content using the http://  
  5.   protocol, in bytes. If this value is nonnegative (>=0), content longer  
  6.   than it will be truncated; otherwise, no truncation at all. Do not  
  7.   confuse this setting with the file.content.limit setting.  
  8.   </description>  
  9. </property>  

将value设置为-1即可
[html] view plain copy
  1. <property>  
  2.   <name>http.content.limit</name>  
  3.   <value>-1</value>  
  4.   <description>The length limit for downloaded content using the http://  
  5.   protocol, in bytes. If this value is nonnegative (>=0), content longer  
  6.   than it will be truncated; otherwise, no truncation at all. Do not  
  7.   confuse this setting with the file.content.limit setting.  
  8.   </description>  
  9. </property>  

如果看到日志中有说被robots协议阻拦,修改Fetcher.java的源码,找到:
[java] view plain copy
  1. if (!rules.isAllowed(fit.u.toString())) {  
  2.                 // unblock  
  3.                 fetchQueues.finishFetchItem(fit, true);  
  4.                 if (LOG.isDebugEnabled()) {  
  5.                   LOG.debug("Denied by robots.txt: " + fit.url);  
  6.                 }  
  7.                 output(fit.url, fit.datum, null, ProtocolStatus.STATUS_ROBOTS_DENIED, CrawlDatum.STATUS_FETCH_GONE);  
  8.                 reporter.incrCounter("FetcherStatus""robots_denied"1);  
  9.                 continue;  
  10.               }  

将整段代码注释即可。
0 0
原创粉丝点击