从今天开始陆续对Nutch 1.0的工作过程进行分析。从Crawl为起点,先分析爬虫从爬行到索引的过程,再分析查询检索的过程。如有错误,欢迎批评指正!
Crawl类是Nutch爬虫中的一个核心类,它的主要方法就是该类中的main方法,该方法包含了爬虫的整个运行阶段,包括Inject(将初始URL加入到网页库CrawlDb中),Generate(产生待爬行队列),Fetch(抓取网页)和Index(索引)。这里通过分析main方法的执行过程分析nutch爬虫从建立网页库到建立索引的全过程。Crawl类位于org.apache.nutch.crawl包中,接下来就对main方法进行分析。
1.初始化阶段
根据nutch-default.xml,nutch-site.xml,nutch-tool.xml 3个配置文件和用户在命令行输入的参数,配置和初始化爬行深度、线程数、爬行根目录、topN、crawlDb、linkDb、segments、indexes等目录,并初始化injector、generator、fetcher、parser、indexer等。
2.Inject
Inject阶段将初始URL进行合法性检查(UrlNomalizer)如:统一大小写,检查是否为合法URL等和过滤(UrlFilter),如过滤掉用户指定的不爬行的链接之后注入到网页数据库crawlDb中。调用inject(crawlDb, rootUrlDir)方法,该方法在org.apache.nutch.crawl.Injector类中,下面对其进行分析。
inject方法的两个参数,Path crawlDb代表crawlDb的路径,这里是crawl/crawlDb;Path rootUrlDir 代表初始URL的路径。
- // org.apache.nutch.crawl.Injector
- public void inject(Path crawlDb, Path urlDir) throws IOException {
- // crawlDb 网页数据库目录 // urlDir 种子URL目录
- ... ...
- Path tempDir =
- new Path(getConf().get("mapred.temp.dir", ".") + "/inject-temp-"+Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));
- // map text input file to a <url,CrawlDatum> file
- if (LOG.isInfoEnabled()) {
- LOG.info("Injector: Converting injected urls to crawl db entries.");
- }
- JobConf sortJob = new NutchJob(getConf());
- sortJob.setJobName("inject " + urlDir);
- FileInputFormat.addInputPath(sortJob, urlDir);
- sortJob.setMapperClass(InjectMapper.class);
- FileOutputFormat.setOutputPath(sortJob, tempDir);
- sortJob.setOutputFormat(SequenceFileOutputFormat.class);
- sortJob.setOutputKeyClass(Text.class);
- sortJob.setOutputValueClass(CrawlDatum.class);
- sortJob.setLong("injector.current.time",System.currentTimeMillis());
- JobClient.runJob(sortJob);
- ... ...
// org.apache.nutch.crawl.Injector
public void inject(Path crawlDb, Path urlDir) throws IOException {
// crawlDb 网页数据库目录 // urlDir 种子URL目录
... ...
Path tempDir =
new Path(getConf().get("mapred.temp.dir", ".") + "/inject-temp-"+Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));
// map text input file to a <url,CrawlDatum> file
if (LOG.isInfoEnabled()) {
LOG.info("Injector: Converting injected urls to crawl db entries.");
}
JobConf sortJob = new NutchJob(getConf());
sortJob.setJobName("inject " + urlDir);
FileInputFormat.addInputPath(sortJob, urlDir);
sortJob.setMapperClass(InjectMapper.class);
FileOutputFormat.setOutputPath(sortJob, tempDir);
sortJob.setOutputFormat(SequenceFileOutputFormat.class);
sortJob.setOutputKeyClass(Text.class);
sortJob.setOutputValueClass(CrawlDatum.class);
sortJob.setLong("injector.current.time",System.currentTimeMillis());
JobClient.runJob(sortJob);
... ...
程序的前半部分将输入文件map成<url, CrawlDatum>的文件。输入文件是用户指定的初始URL,使用的Mapper类是InjectMapper,Reduce阶段不做任何事情,原样输出。输出的目录是刚刚生成的tempDir临时目录,输出的键值对类型是<Text, CrawlDatum>,将injector.current.time属性设置为当前时间。
这里介绍一下CrawlDatum的作用。它是记录数据库crawlDb中每个链接爬行状态的一个类,每次爬行的时候判断这个链接是否爬行过,目前正处于爬行的哪个阶段等等信息都记录在这个类的标志中。
那么在Map阶段具体都做了哪些事呢?需要进一步分析InjectMapper类。该类的configure方法对urlNormalizer、interval、scfiliter、scoreInjected等域进行初始化,为后面的map做准备。其中interval是网页抓取的时间间隔,默认情况下是30天,scfilter是当前nutch使用的ScoringFilter,scoreInjected是inject到crawlDb中的链接得分,默认是1.0。接下来是最重要的map方法:
- public void map(WritableComparable key, Text value,
- OutputCollector<Text, CrawlDatum> output, Reporter reporter)
- throws IOException {
- String url = value.toString(); // value is line of text
- try {
- url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT);
- url = filters.filter(url); // filter the url
- } catch (Exception e) {
- if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
- url = null;
- }
- if (url != null) { // if it passes
- value.set(url); // collect it
- CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);
- datum.setFetchTime(curTime);
- datum.setScore(scoreInjected);
- try {
- scfilters.injectedScore(value, datum);
- } catch (ScoringFilterException e) {
- if (LOG.isWarnEnabled()) {
- LOG.warn("Cannot filter injected score for url " + url +
- ", using default (" + e.getMessage() + ")");
- }
- datum.setScore(scoreInjected);
- }
- output.collect(value, datum); // 一个URL对应一个状态信息,key是URL,值是状态信息
- }
- }
public void map(WritableComparable key, Text value,
OutputCollector<Text, CrawlDatum> output, Reporter reporter)
throws IOException {
String url = value.toString(); // value is line of text
try {
url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT);
url = filters.filter(url); // filter the url
} catch (Exception e) {
if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
url = null;
}
if (url != null) { // if it passes
value.set(url); // collect it
CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);
datum.setFetchTime(curTime);
datum.setScore(scoreInjected);
try {
scfilters.injectedScore(value, datum);
} catch (ScoringFilterException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Cannot filter injected score for url " + url +
", using default (" + e.getMessage() + ")");
}
datum.setScore(scoreInjected);
}
output.collect(value, datum); // 一个URL对应一个状态信息,key是URL,值是状态信息
}
}
这个map方法的主要功能是将用户输入的初始URL从文本转换为<url, CrawlDatum>键值对类型,CrawlDatum是一个链接的爬行状态信息。首先使用nomalizer对输入的URL进行规范化,如果不能通过规范化则返回,否则向crawldatum中打入链接的相关信息:当前的爬行状态、链接得分(如果ScoringFilter中指定了在inject阶段怎样计算注入链接的得分则使用这个得分,否则使用默认的1.0)、抓取时间。最后,收集url和与它对应的爬行状态。
程序返回到inject方法中继续执行:
- // merge with existing crawl db
- if (LOG.isInfoEnabled()) {
- LOG.info("Injector: Merging injected urls into crawl db.");
- }
- JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);
- FileInputFormat.addInputPath(mergeJob, tempDir);
- mergeJob.setReducerClass(InjectReducer.class);
- JobClient.runJob(mergeJob);
- CrawlDb.install(mergeJob, crawlDb);
- // clean up
- FileSystem fs = FileSystem.get(getConf());
- fs.delete(tempDir, true);
- if (LOG.isInfoEnabled()) { LOG.info("Injector: done"); }
- } // inject方法结束
// merge with existing crawl db
if (LOG.isInfoEnabled()) {
LOG.info("Injector: Merging injected urls into crawl db.");
}
JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);
FileInputFormat.addInputPath(mergeJob, tempDir);
mergeJob.setReducerClass(InjectReducer.class);
JobClient.runJob(mergeJob);
CrawlDb.install(mergeJob, crawlDb);
// clean up
FileSystem fs = FileSystem.get(getConf());
fs.delete(tempDir, true);
if (LOG.isInfoEnabled()) { LOG.info("Injector: done"); }
} // inject方法结束
这部分首先调用CrawlDb的createJob静态方法对接下来的将injected URL合并到crawlDb的MapReduce任务进行配置。createJob将FileInputPath设置为crawl/crawlDb/current,Mapper类设置为CrawlDbFilter,Reducer类设置为CrawlDbReducer,输出路径(outputPath)设置为crawl/crawlDb/下的一个以随机数命名的目录,输出的键值对类型是<Text, CrawlDatum>,此后creatJob返回。
在该方法后,inject方法继续对接下来的MapReduce任务进行配置,增加一个输入路径tempDir(即上次Map任务的输出。这样,Map任务实际有两个输入路径),设置Reducer类为InjectReducer(此处待确认:覆盖creatJob中设置的Reducer?????)。
Map阶段CrawlDbFilter的作用是将crawl/crawldb/current和上一步Map处理(读入初始URL并map)后的<url, crawldatum>键值对经过UrlNormalizer和UrlFilter规范化(如字母大小写等)和过滤(根据用户配置,决定是否保留这个url)后,然后输出到tempDir中。
Reduce阶段InjectReducer的任务是将已存在于current中的网页数据库和新加入在outputPath中的数据库进行合并。如果新加入的网页已存在于current中,则不修改其状态,否则将新加入链接datum的状态从STATUS_INJECTED改为STATUS_DB_UNFETCHED。这样就完成了新旧数据库的合并
最后,通过CrawlDb.install(mergeJob, crawlDb)方法将tempDir删除。将新的数据库命名为current。
至此,Inject阶段的任务完成,该阶段数据处理的流程图如下:
从今天开始陆续对Nutch 1.0的工作过程进行分析。从Crawl为起点,先分析爬虫从爬行到索引的过程,再分析查询检索的过程。如有错误,欢迎批评指正!
Crawl类是Nutch爬虫中的一个核心类,它的主要方法就是该类中的main方法,该方法包含了爬虫的整个运行阶段,包括Inject(将初始URL加入到网页库CrawlDb中),Generate(产生待爬行队列),Fetch(抓取网页)和Index(索引)。这里通过分析main方法的执行过程分析nutch爬虫从建立网页库到建立索引的全过程。Crawl类位于org.apache.nutch.crawl包中,接下来就对main方法进行分析。
1.初始化阶段
根据nutch-default.xml,nutch-site.xml,nutch-tool.xml 3个配置文件和用户在命令行输入的参数,配置和初始化爬行深度、线程数、爬行根目录、topN、crawlDb、linkDb、segments、indexes等目录,并初始化injector、generator、fetcher、parser、indexer等。
2.Inject
Inject阶段将初始URL进行合法性检查(UrlNomalizer)如:统一大小写,检查是否为合法URL等和过滤(UrlFilter),如过滤掉用户指定的不爬行的链接之后注入到网页数据库crawlDb中。调用inject(crawlDb, rootUrlDir)方法,该方法在org.apache.nutch.crawl.Injector类中,下面对其进行分析。
inject方法的两个参数,Path crawlDb代表crawlDb的路径,这里是crawl/crawlDb;Path rootUrlDir 代表初始URL的路径。
- // org.apache.nutch.crawl.Injector
- public void inject(Path crawlDb, Path urlDir) throws IOException {
- // crawlDb 网页数据库目录 // urlDir 种子URL目录
- ... ...
- Path tempDir =
- new Path(getConf().get("mapred.temp.dir", ".") + "/inject-temp-"+Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));
- // map text input file to a <url,CrawlDatum> file
- if (LOG.isInfoEnabled()) {
- LOG.info("Injector: Converting injected urls to crawl db entries.");
- }
- JobConf sortJob = new NutchJob(getConf());
- sortJob.setJobName("inject " + urlDir);
- FileInputFormat.addInputPath(sortJob, urlDir);
- sortJob.setMapperClass(InjectMapper.class);
- FileOutputFormat.setOutputPath(sortJob, tempDir);
- sortJob.setOutputFormat(SequenceFileOutputFormat.class);
- sortJob.setOutputKeyClass(Text.class);
- sortJob.setOutputValueClass(CrawlDatum.class);
- sortJob.setLong("injector.current.time",System.currentTimeMillis());
- JobClient.runJob(sortJob);
- ... ...
// org.apache.nutch.crawl.Injector
public void inject(Path crawlDb, Path urlDir) throws IOException {
// crawlDb 网页数据库目录 // urlDir 种子URL目录
... ...
Path tempDir =
new Path(getConf().get("mapred.temp.dir", ".") + "/inject-temp-"+Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));
// map text input file to a <url,CrawlDatum> file
if (LOG.isInfoEnabled()) {
LOG.info("Injector: Converting injected urls to crawl db entries.");
}
JobConf sortJob = new NutchJob(getConf());
sortJob.setJobName("inject " + urlDir);
FileInputFormat.addInputPath(sortJob, urlDir);
sortJob.setMapperClass(InjectMapper.class);
FileOutputFormat.setOutputPath(sortJob, tempDir);
sortJob.setOutputFormat(SequenceFileOutputFormat.class);
sortJob.setOutputKeyClass(Text.class);
sortJob.setOutputValueClass(CrawlDatum.class);
sortJob.setLong("injector.current.time",System.currentTimeMillis());
JobClient.runJob(sortJob);
... ...
程序的前半部分将输入文件map成<url, CrawlDatum>的文件。输入文件是用户指定的初始URL,使用的Mapper类是InjectMapper,Reduce阶段不做任何事情,原样输出。输出的目录是刚刚生成的tempDir临时目录,输出的键值对类型是<Text, CrawlDatum>,将injector.current.time属性设置为当前时间。
这里介绍一下CrawlDatum的作用。它是记录数据库crawlDb中每个链接爬行状态的一个类,每次爬行的时候判断这个链接是否爬行过,目前正处于爬行的哪个阶段等等信息都记录在这个类的标志中。
那么在Map阶段具体都做了哪些事呢?需要进一步分析InjectMapper类。该类的configure方法对urlNormalizer、interval、scfiliter、scoreInjected等域进行初始化,为后面的map做准备。其中interval是网页抓取的时间间隔,默认情况下是30天,scfilter是当前nutch使用的ScoringFilter,scoreInjected是inject到crawlDb中的链接得分,默认是1.0。接下来是最重要的map方法:
- public void map(WritableComparable key, Text value,
- OutputCollector<Text, CrawlDatum> output, Reporter reporter)
- throws IOException {
- String url = value.toString(); // value is line of text
- try {
- url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT);
- url = filters.filter(url); // filter the url
- } catch (Exception e) {
- if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
- url = null;
- }
- if (url != null) { // if it passes
- value.set(url); // collect it
- CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);
- datum.setFetchTime(curTime);
- datum.setScore(scoreInjected);
- try {
- scfilters.injectedScore(value, datum);
- } catch (ScoringFilterException e) {
- if (LOG.isWarnEnabled()) {
- LOG.warn("Cannot filter injected score for url " + url +
- ", using default (" + e.getMessage() + ")");
- }
- datum.setScore(scoreInjected);
- }
- output.collect(value, datum); // 一个URL对应一个状态信息,key是URL,值是状态信息
- }
- }
public void map(WritableComparable key, Text value,
OutputCollector<Text, CrawlDatum> output, Reporter reporter)
throws IOException {
String url = value.toString(); // value is line of text
try {
url = urlNormalizers.normalize(url, URLNormalizers.SCOPE_INJECT);
url = filters.filter(url); // filter the url
} catch (Exception e) {
if (LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
url = null;
}
if (url != null) { // if it passes
value.set(url); // collect it
CrawlDatum datum = new CrawlDatum(CrawlDatum.STATUS_INJECTED, interval);
datum.setFetchTime(curTime);
datum.setScore(scoreInjected);
try {
scfilters.injectedScore(value, datum);
} catch (ScoringFilterException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Cannot filter injected score for url " + url +
", using default (" + e.getMessage() + ")");
}
datum.setScore(scoreInjected);
}
output.collect(value, datum); // 一个URL对应一个状态信息,key是URL,值是状态信息
}
}
这个map方法的主要功能是将用户输入的初始URL从文本转换为<url, CrawlDatum>键值对类型,CrawlDatum是一个链接的爬行状态信息。首先使用nomalizer对输入的URL进行规范化,如果不能通过规范化则返回,否则向crawldatum中打入链接的相关信息:当前的爬行状态、链接得分(如果ScoringFilter中指定了在inject阶段怎样计算注入链接的得分则使用这个得分,否则使用默认的1.0)、抓取时间。最后,收集url和与它对应的爬行状态。
程序返回到inject方法中继续执行:
- // merge with existing crawl db
- if (LOG.isInfoEnabled()) {
- LOG.info("Injector: Merging injected urls into crawl db.");
- }
- JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);
- FileInputFormat.addInputPath(mergeJob, tempDir);
- mergeJob.setReducerClass(InjectReducer.class);
- JobClient.runJob(mergeJob);
- CrawlDb.install(mergeJob, crawlDb);
- // clean up
- FileSystem fs = FileSystem.get(getConf());
- fs.delete(tempDir, true);
- if (LOG.isInfoEnabled()) { LOG.info("Injector: done"); }
- } // inject方法结束
// merge with existing crawl db
if (LOG.isInfoEnabled()) {
LOG.info("Injector: Merging injected urls into crawl db.");
}
JobConf mergeJob = CrawlDb.createJob(getConf(), crawlDb);
FileInputFormat.addInputPath(mergeJob, tempDir);
mergeJob.setReducerClass(InjectReducer.class);
JobClient.runJob(mergeJob);
CrawlDb.install(mergeJob, crawlDb);
// clean up
FileSystem fs = FileSystem.get(getConf());
fs.delete(tempDir, true);
if (LOG.isInfoEnabled()) { LOG.info("Injector: done"); }
} // inject方法结束
这部分首先调用CrawlDb的createJob静态方法对接下来的将injected URL合并到crawlDb的MapReduce任务进行配置。createJob将FileInputPath设置为crawl/crawlDb/current,Mapper类设置为CrawlDbFilter,Reducer类设置为CrawlDbReducer,输出路径(outputPath)设置为crawl/crawlDb/下的一个以随机数命名的目录,输出的键值对类型是<Text, CrawlDatum>,此后creatJob返回。
在该方法后,inject方法继续对接下来的MapReduce任务进行配置,增加一个输入路径tempDir(即上次Map任务的输出。这样,Map任务实际有两个输入路径),设置Reducer类为InjectReducer(此处待确认:覆盖creatJob中设置的Reducer?????)。
Map阶段CrawlDbFilter的作用是将crawl/crawldb/current和上一步Map处理(读入初始URL并map)后的<url, crawldatum>键值对经过UrlNormalizer和UrlFilter规范化(如字母大小写等)和过滤(根据用户配置,决定是否保留这个url)后,然后输出到tempDir中。
Reduce阶段InjectReducer的任务是将已存在于current中的网页数据库和新加入在outputPath中的数据库进行合并。如果新加入的网页已存在于current中,则不修改其状态,否则将新加入链接datum的状态从STATUS_INJECTED改为STATUS_DB_UNFETCHED。这样就完成了新旧数据库的合并
最后,通过CrawlDb.install(mergeJob, crawlDb)方法将tempDir删除。将新的数据库命名为current。
至此,Inject阶段的任务完成,该阶段数据处理的流程图如下:
浙公网安备 33010602011771号