Nutch爬蟲配置及簡單使用

北京大學軟件與微電子學院 曹路 2018/03/20 https://github.com/andrewcao95/nutch-crawlerhtml

1 引言

1.1 爬蟲的基本分類

爬蟲基本能夠分3類:java

  • 分佈式爬蟲:Nutch
  • JAVA單機爬蟲:Crawler4j、WebMagic、WebCollector
  • 非JAVA單機爬蟲:Scrapy

1.2 Nutch簡介

Nutch是apache旗下的一個用Java實現的開源索引引擎項目,經過nutch,誕生了hadoop、tika、gora。Nutch的設計初衷主要是爲了解決下述兩個問題:git

  • 商業搜索引擎存在商業利益的考慮。 有的商業搜索引擎容許競價排名(好比百度),搜索結果不是純粹的根據網頁自己的價值進行排序,這樣有的搜索結果不全是和站點內容相關。
  • 商業搜索引擎不開源。 Nutch是開放源代碼,所以任何人均可以查看它的排序算法是如何工做的。Nutch對學術搜索和政府類站點的搜索來講是個好選擇。由於一個公平的排序結果是很是重要的。

1.3 Nutch的版本

Nutch1.2版本以後,Nutch已經從搜索引擎演化爲網絡爬蟲,演化爲兩大分支版本:1.X和2.X,最大區別在於2.X對底層的數據存儲進行了抽象以支持各類底層存儲技術,其中:github

  • Nutch1.2以後是一個完整的搜索引擎
  • Nutch1.7以後是一個基於HDFS的網絡爬蟲
  • Nutch2.2.1以後是一個基於Gora的網絡爬蟲

2 環境搭建與配置

2.1 環境和工具

Nutch的編譯安裝須要JDK、Ant等環境,爲此本次使用的環境和工具以下:算法

  • 一、操做系統:Ubuntu 16.04 LTS 64位
  • 二、JDK版本: JDK1.8.0_161
  • 三、Nutch版本:nutch-1.9(源碼)
  • 四、IDE:Eclipse
2.1.1 JDK配置

本次JDK使用1.8.0_161,在Oracle官網http://www.oracle.com/technetwork/java/javase/downloads/index.html 能夠下載apache

首次使用須要設置環境變量,在~/.bashrc的末尾加入如下內容:vim

#set jdk environment
export JAVA_HOME=/java/jdk1.8.0_161
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH

而後使用source ~/.bashrc命令使得環境變量生效。bash


2.1.2 Nutch源碼下載

Nutch分爲bin和src,bin是運行包、src是源碼包,本次咱們使用nutch-1.9源碼包本身編譯,推薦使用SVN進行安裝,SVN checkout出來的有pom.xml文件,即maven文件。網絡

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

2.1.3 安裝Ant

到Ant官網 http://ant.apache.org/bindownload.cgi 下載最新版的Ant。oracle

一樣也須要設置環境變量

#set ant environment
export ANT_HOME=/ant/ant-1.9.10
export PATH=$PATH:$ANT_HOME/bin


2.1.4 安裝IDE

2.2 配置

把 conf/下的 nutch-site.xml.template複製一份,命名爲nutch-site.xml,在裏面添加配置:

<property>
  <name>http.agent.name</name>
  <value>My Nutch Spider</value>
</property>
<property>
  <name>plugin.folders</name>
  <value>$NUTCH_HOME/build/plugins</value>
</property>

$NUTCH_HOME是指nutch源碼的根目錄,例如個人是/home/andrewcao95/Desktop/release-1.9



2.3 編譯Nutch源碼

生成Eclipse項目文件,即.project文件,使用以下命令

ant eclipse

耐心等待,這個過程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函數下加一行:

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

運行該類,能夠看到運行成功。

讀取爬蟲文件:

查看裏面的data文件

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

這是一個SequenceFile,Nutch中除了Inject的輸入(種子)以外,其餘文件所有以SequenceFile的形式存儲。SequenceFile的結構以下:

key0 value0  
key1 value1  
key2  value2  
......  
keyn  valuen

以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,代碼以下:

package org.apache.nutch.example;  

import org.apache.hadoop.conf.Configuration;  
import org.apache.hadoop.fs.FileSystem;  
import org.apache.hadoop.fs.Path;  
import org.apache.hadoop.io.SequenceFile;  
import org.apache.hadoop.io.Text;  
import org.apache.nutch.crawl.CrawlDatum;  

import java.io.IOException;  

public class InjectorReader {  
    public static void main(String[] args) throws IOException {  
        Configuration conf=new Configuration();  
        Path dataPath=new Path("/home/andrewcao95/Desktop/release-1.9/crawldb/current/part-00000/data");  
        FileSystem fs=dataPath.getFileSystem(conf);  
        SequenceFile.Reader reader=new SequenceFile.Reader(fs,dataPath,conf);  
        Text key=new Text();  
        CrawlDatum value=new CrawlDatum();  
        while(reader.next(key,value)){  
            System.out.println("key:"+key);  
            System.out.println("value:"+value);  
        }  
        reader.close();  
    }  
}

獲得的運行結果如圖所示:

咱們能夠看到,程序讀出了剛纔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文件,代碼以下所示

package org.apache.nutch.crawl;  

import java.util.*;  
import java.text.*;  
import org.apache.commons.lang.StringUtils;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.apache.hadoop.fs.*;  
import org.apache.hadoop.conf.*;  
import org.apache.hadoop.mapred.*;  
import org.apache.hadoop.util.Tool;  
import org.apache.hadoop.util.ToolRunner;  
import org.apache.nutch.parse.ParseSegment;  
import org.apache.nutch.indexer.IndexingJob;   
import org.apache.nutch.util.HadoopFSUtil;  
import org.apache.nutch.util.NutchConfiguration;  
import org.apache.nutch.util.NutchJob;  

import org.apache.nutch.fetcher.Fetcher;  

public class Crawl extends Configured implements Tool {  
    public static final Logger LOG = LoggerFactory.getLogger(Crawl.class);  

    private static String getDate() {  
        return new SimpleDateFormat("yyyyMMddHHmmss").format  
                (new Date(System.currentTimeMillis()));  
    }  

    public static void main(String args[]) throws Exception {  
        Configuration conf = NutchConfiguration.create();  
        int res = ToolRunner.run(conf, new Crawl(), args);  
        System.exit(res);  
    }  

    @Override  
    public int run(String[] args) throws Exception {  
        /*種子所在文件夾*/  
        Path rootUrlDir = new Path("/home/andrewcao95/Desktop/release-1.9/urls");  
        /*存儲爬取信息的文件夾*/  
        Path dir = new Path("/home/andrewcao95/Desktop/release-1.9","crawl-" + getDate());  
        int threads = 50;  
        /*廣度遍歷時爬取的深度,即廣度遍歷樹的層數*/  
        int depth = 2;  
        long topN = 10;  

        JobConf job = new NutchJob(getConf());  
        FileSystem fs = FileSystem.get(job);  

        if (LOG.isInfoEnabled()) {  
            LOG.info("crawl started in: " + dir);  
            LOG.info("rootUrlDir = " + rootUrlDir);  
            LOG.info("threads = " + threads);  
            LOG.info("depth = " + depth);  
            if (topN != Long.MAX_VALUE)  
                LOG.info("topN = " + topN);  
        }  

        Path crawlDb = new Path(dir + "/crawldb");  
        Path linkDb = new Path(dir + "/linkdb");  
        Path segments = new Path(dir + "/segments");  
        Path indexes = new Path(dir + "/indexes");  
        Path index = new Path(dir + "/index");  

        Path tmpDir = job.getLocalPath("crawl"+Path.SEPARATOR+getDate());  
        Injector injector = new Injector(getConf());  
        Generator generator = new Generator(getConf());  
        Fetcher fetcher = new Fetcher(getConf());  
        ParseSegment parseSegment = new ParseSegment(getConf());  
        CrawlDb crawlDbTool = new CrawlDb(getConf());  
        LinkDb linkDbTool = new LinkDb(getConf());  

        // initialize crawlDb  
        injector.inject(crawlDb, rootUrlDir);  
        int i;  
        for (i = 0; i < depth; i++) {             // generate new segment  
            Path[] segs = generator.generate(crawlDb, segments, -1, topN, System  
                    .currentTimeMillis());  
            if (segs == null) {  
                LOG.info("Stopping at depth=" + i + " - no more URLs to fetch.");  
                break;  
            }  
            fetcher.fetch(segs[0], threads);  // fetch it  
            if (!Fetcher.isParsing(job)) {  
                parseSegment.parse(segs[0]);    // parse it, if needed  
            }  
            crawlDbTool.update(crawlDb, segs, true, true); // update crawldb  
        }  

        if (LOG.isInfoEnabled()) { LOG.info("crawl finished: " + dir); }  
        return 0;  
    }  
}

經過上述代碼能夠執行一次完整的爬取,程序運行結果以下所示:



運行成功,對網站(http://pku.edu.cn/)進行了2層爬取,爬取信息都保存在/tmp/crawl+時間的文件夾中

參考資料

  • https://wiki.apache.org/nutch/NutchTutorial
  • http://yukinami.github.io/2016/09/23/Nutch%E7%88%AC%E8%99%AB%E5%BC%80%E5%8F%91/
  • http://www.voidcn.com/article/p-gcnehxms-sg.html
  • https://wiki.apache.org/nutch/RunNutchInEclipse
  • http://datahref.com/archives/41
  • http://cn.soulmachine.me/2014-01-20-Running-Nutch-in-Eclipse/
  • https://www.kancloud.cn/kancloud/step-by-step-nutch/48722
相關文章
相關標籤/搜索