Nutch爬虫配置及简单使用

释放双眼,带上耳机,听听看~!

北京大学软件与微电子学院 曹路 2018/03/20 https://github.com/andrewcao95/nutch-crawler

1 引言

1.1 爬虫的基本分类

爬虫基本可以分3类:

  • 分布式爬虫:Nutch
  • JAVA单机爬虫:Crawler4j、WebMagic、WebCollector
  • 非JAVA单机爬虫:Scrapy

1.2 Nutch简介

Nutch是apache旗下的一个用Java实现的开源索引引擎项目,通过nutch,诞生了hadoop、tika、gora。Nutch的设计初衷主要是为了解决下述两个问题:

  • 商业搜索引擎存在商业利益的考虑。 有的商业搜索引擎允许竞价排名(比如百度),搜索结果不是纯粹的根据网页本身的价值进行排序,这样有的搜索结果不全是和站点内容相关。
  • 商业搜索引擎不开源。 Nutch是开放源代码,因此任何人都可以查看它的排序算法是如何工作的。Nutch对学术搜索和政府类站点的搜索来说是个好选择。因为一个公平的排序结果是非常重要的。

1.3 Nutch的版本

Nutch1.2版本之后,Nutch已经从搜索引擎演化为网络爬虫,演化为两大分支版本:1.X和2.X,最大区别在于2.X对底层的数据存储进行了抽象以支持各种底层存储技术,其中:

  • Nutch1.2之后是一个完整的搜索引擎
  • Nutch1.7之后是一个基于HDFS的网络爬虫
  • Nutch2.2.1之后是一个基于Gora的网络爬虫

2 环境搭建与配置

2.1 环境和工具

Nutch的编译安装需要JDK、Ant等环境,为此本次使用的环境和工具如下:

  • 1、操作系统:Ubuntu 16.04 LTS 64位
  • 2、JDK版本: JDK1.8.0_161
  • 3、Nutch版本:nutch-1.9(源码)
  • 4、IDE:Eclipse

2.1.1 JDK配置

本次JDK使用1.8.0_161,在Oracle官网http://www.oracle.com/technetwork/java/javase/downloads/index.html 可以下载

首次使用需要设置环境变量,在~/.bashrc的末尾加入以下内容:


1
2
3
4
5
6
1#set jdk environment
2export JAVA_HOME=/java/jdk1.8.0_161
3export JRE_HOME=$JAVA_HOME/jre
4export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
5export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
6

然后使用source ~/.bashrc命令使得环境变量生效。

2.1.2 Nutch源码下载

Nutch分为bin和src,bin是运行包、src是源码包,本次我们使用nutch-1.9源码包自己编译,推荐使用SVN进行安装,SVN checkout出来的有pom.xml文件,即maven文件。


1
2
3
1$ sudo apt install subversion
2$ svn co https://svn.apache.org/repos/asf/nutch/tags/release-1.9
3

2.1.3 安装Ant

到Ant官网 http://ant.apache.org/bindownload.cgi 下载最新版的Ant。

同样也需要设置环境变量


1
2
3
4
1#set ant environment
2export ANT_HOME=/ant/ant-1.9.10
3export PATH=$PATH:$ANT_HOME/bin
4

2.1.4 安装IDE

2.2 配置

把 conf/下的 nutch-site.xml.template复制一份,命名为nutch-site.xml,在里面添加配置:


1
2
3
4
5
6
7
8
9
1<property>
2  <name>http.agent.name</name>
3  <value>My Nutch Spider</value>
4</property>
5<property>
6  <name>plugin.folders</name>
7  <value>$NUTCH_HOME/build/plugins</value>
8</property>
9

$NUTCH_HOME是指nutch源码的根目录,例如我的是/home/andrewcao95/Desktop/release-1.9

2.3 编译Nutch源码

生成Eclipse项目文件,即.project文件,使用如下命令


1
2
1ant eclipse
2

耐心等待,这个过程ant会根据ivy从中心仓库下载各种依赖jar包,可能要几分钟。这里特别要注意网络通畅,学院的网络可能存在一定的问题,导致很多jar包无法访问,最终会导致编译失败。同时要注意原来的配置文件中包的下载地址会发生变化,为此需要根据报错指令进行相对应的调整。

解决了上述问题之后,很快就能编译成功

2.4 导入Nutch到Eclipse

按照RunNutchInEclipse的教程指导,很快就能导入项目。之后我们就能看到Nutch项目的完整源代码

2.5 测试Nutch源码

源码导入工程后,并不能执行完整的爬取。Nutch将爬取的流程切分成很多阶段,每个阶段分别封装在一个类的main函数中。我们首先运行Nutch中最简单的流程:Inject。

我们知道爬虫在初始阶段是需要人工给出一个或多个url,作为起始点(广度遍历树的树根)。Inject的作用,就是把用户写在文件里的种子(一行一个url,是TextInputFormat),插入到爬虫的URL管理文件(crawldb,是SequenceFile)中。

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

指定一个文件夹crawldb来作为URL管理文件夹(输出)。有一种简单的方法来指定args,直接在main函数下加一行:


1
2
1args=new String[]{"/home/andrewcao95/Desktop/release-1.9/crawldb","/home/andrewcao95/Desktop/release-1.9/urls"};
2

运行该类,可以看到运行成功。

读取爬虫文件:

查看里面的data文件


1
2
1vim /home/andrewcao95/Desktop/release-1.9/crawldb/current/part-00000/data  
2

这是一个SequenceFile,Nutch中除了Inject的输入(种子)之外,其他文件全部以SequenceFile的形式存储。SequenceFile的结构如下:


1
2
3
4
5
6
1key0 value0  
2key1 value1  
3key2  value2  
4......  
5keyn  valuen
6

以key value的形式,将对象序列(key value序列)存储到文件中。我们从SequenceFile头部可以看出来key value的类型。

上面的SequenceFile中,可以看出key的类型是org.apache.hadoop.io.Text,value的类型是org.apache.nutch.crawl.CrawlDatum。

3 爬虫爬取过程分析

3.1 分析爬虫文件

首先我们需要读取SequenceFile的代码,在src/java里新建一个类org.apache.nutch.example.InjectorReader,代码如下:


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

得到的运行结果如图所示:

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

3.2 一次完整的爬虫爬取过程

在爬取之前,我们先修改一下conf/nutch-default.xml中的一个地方,这个值会在发送http请求时,作为User-Agent字段。

在org.apache.nutch.crawl中新建一个Crawl.java文件,代码如下所示


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
1package org.apache.nutch.crawl;  
2
3import java.util.*;  
4import java.text.*;  
5import org.apache.commons.lang.StringUtils;  
6import org.slf4j.Logger;  
7import org.slf4j.LoggerFactory;  
8import org.apache.hadoop.fs.*;  
9import org.apache.hadoop.conf.*;  
10import org.apache.hadoop.mapred.*;  
11import org.apache.hadoop.util.Tool;  
12import org.apache.hadoop.util.ToolRunner;  
13import org.apache.nutch.parse.ParseSegment;  
14import org.apache.nutch.indexer.IndexingJob;  
15import org.apache.nutch.util.HadoopFSUtil;  
16import org.apache.nutch.util.NutchConfiguration;  
17import org.apache.nutch.util.NutchJob;  
18
19import org.apache.nutch.fetcher.Fetcher;  
20
21public class Crawl extends Configured implements Tool {  
22    public static final Logger LOG = LoggerFactory.getLogger(Crawl.class);  
23
24    private static String getDate() {  
25        return new SimpleDateFormat("yyyyMMddHHmmss").format  
26                (new Date(System.currentTimeMillis()));  
27    }  
28
29    public static void main(String args[]) throws Exception {  
30        Configuration conf = NutchConfiguration.create();  
31        int res = ToolRunner.run(conf, new Crawl(), args);  
32        System.exit(res);  
33    }  
34
35    @Override  
36    public int run(String[] args) throws Exception {  
37        /*种子所在文件夹*/  
38        Path rootUrlDir = new Path("/home/andrewcao95/Desktop/release-1.9/urls");  
39        /*存储爬取信息的文件夹*/  
40        Path dir = new Path("/home/andrewcao95/Desktop/release-1.9","crawl-" + getDate());  
41        int threads = 50;  
42        /*广度遍历时爬取的深度,即广度遍历树的层数*/  
43        int depth = 2;  
44        long topN = 10;  
45
46        JobConf job = new NutchJob(getConf());  
47        FileSystem fs = FileSystem.get(job);  
48
49        if (LOG.isInfoEnabled()) {  
50            LOG.info("crawl started in: " + dir);  
51            LOG.info("rootUrlDir = " + rootUrlDir);  
52            LOG.info("threads = " + threads);  
53            LOG.info("depth = " + depth);  
54            if (topN != Long.MAX_VALUE)  
55                LOG.info("topN = " + topN);  
56        }  
57
58        Path crawlDb = new Path(dir + "/crawldb");  
59        Path linkDb = new Path(dir + "/linkdb");  
60        Path segments = new Path(dir + "/segments");  
61        Path indexes = new Path(dir + "/indexes");  
62        Path index = new Path(dir + "/index");  
63
64        Path tmpDir = job.getLocalPath("crawl"+Path.SEPARATOR+getDate());  
65        Injector injector = new Injector(getConf());  
66        Generator generator = new Generator(getConf());  
67        Fetcher fetcher = new Fetcher(getConf());  
68        ParseSegment parseSegment = new ParseSegment(getConf());  
69        CrawlDb crawlDbTool = new CrawlDb(getConf());  
70        LinkDb linkDbTool = new LinkDb(getConf());  
71
72        // initialize crawlDb  
73        injector.inject(crawlDb, rootUrlDir);  
74        int i;  
75        for (i = 0; i < depth; i++) {             // generate new segment  
76            Path[] segs = generator.generate(crawlDb, segments, -1, topN, System  
77                    .currentTimeMillis());  
78            if (segs == null) {  
79                LOG.info("Stopping at depth=" + i + " - no more URLs to fetch.");  
80                break;  
81            }  
82            fetcher.fetch(segs[0], threads);  // fetch it  
83            if (!Fetcher.isParsing(job)) {  
84                parseSegment.parse(segs[0]);    // parse it, if needed  
85            }  
86            crawlDbTool.update(crawlDb, segs, true, true); // update crawldb  
87        }  
88
89        if (LOG.isInfoEnabled()) { LOG.info("crawl finished: " + dir); }  
90        return 0;  
91    }  
92}  
93

通过上述代码可以执行一次完整的爬取,程序运行结果如下所示:

运行成功,对网站(http://pku.edu.cn/)进行了2层爬取,爬取信息都保存在/tmp/crawl+时间的文件夹中

参考资料

转载于:https://www.cnblogs.com/andrewcao95/p/8734068.html

给TA打赏
共{{data.count}}人
人已打赏
安全经验

职场中的那些话那些事

2021-9-24 20:41:29

安全经验

IM服务器的架构

2021-11-28 16:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索