1. 主要功能
将抓取起始URLs写入crawlDb中。
2. 涉及到的主要的类
org.apache.nutch.crawl.Injector
org.apache.nutch.crawl.CrawlDbFilter
org.apache.nutch.crawl.CrawlDbReducer
3. 具体介绍
(1) Injector功能流程图
该流程图的前两步为Map/Reduce实现。
(2) 具体分析
1) org.apache.nutch.crawl.Injector介绍
主要的方法:
inject(Path crawlDb, Path urlDir).其中urlDir为种子文件,也就是我们自己提供的url.txt文本。
该方法实现上述Injector功能流程图中的所有功能。具体看代码分析:
public void inject(Path crawlDb, PathurlDir) throws IOException {
//首先生成一个以随机数结尾的临时目录来存放中间结果
Path tempDir =
new Path(getConf().get("mapred.temp.dir", ".") +
"/inject-temp-"+
Integer.toString(newRandom().nextInt(Integer.MAX_VALUE)));
/** 第一个Map/Reduce */
JobConf sortJob = new NutchJob(getConf()); //创建排序任务
sortJob.setJobName("inject " + urlDir); //设置Job名
//设置相关属性
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/Reduce */
JobConfmergeJob = CrawlDb.createJob(getConf(), crawlDb); //创建合并任务
//设置属性
FileInputFormat.addInputPath(mergeJob,tempDir);
mergeJob.setReducerClass(InjectReducer.class);
JobClient.runJob(mergeJob); //运行任务
//替换
CrawlDb.install(mergeJob,crawlDb);
//删除临时目录
FileSystem fs =FileSystem.get(getConf());
fs.delete(tempDir,true);
}
其中在第一个Map/Reduce中的Mapper.class主要是(org.apache.nutch.crawl.Injector的内部类):InjectMapper
第二个Map/Reduce中的Reduce.class主要是(org.apache.nutch.crawl.Injector的内部类):InjectReducer .
下边我们分别来看这四个步骤:
2) 第一个Map/Reduce----InjectMapper
InjectMapper implementsMapper<WritableComparable, Text, Text, CrawlDatum>
主要的方法:
public void map(WritableComparable key, Text value,
OutputCollector<Text,CrawlDatum> output, Reporter reporter)
该方法实现的主要功能是:规整化和过滤注入的url,并将结果保存到临时文件中。
下边是具体的代码实现:
public voidmap(WritableComparable key, Text value,
OutputCollector<Text,CrawlDatum> output, Reporter reporter)
throws IOException {
/** 获得一行url地址 */
String url =value.toString();
/** 规范化和过滤URL */
try {
url = urlNormalizers.normalize(url,URLNormalizers.SCOPE_INJECT);
url = filters.filter(url);
} catch(Exception e) {
if(LOG.isWarnEnabled()) { LOG.warn("Skipping " +url+":"+e); }
url = null;
}
/**
* 元数据一定是以key=value的形式被存储的,并且被制表符(“\t”)分割
*/
float customScore = -1f;
intcustomInterval = interval;
Map<String,String>metadata = new TreeMap<String,String>();
if (url.indexOf("\t")!=-1){//判断读取的url是不是元数据,即判断是否是第一轮爬取
String[] splits =url.split("\t");
url =splits[0];
for (int s=1;s<splits.length;s++){
// find separation betweenname and value
int indexEquals =splits[s].indexOf("=");
if (indexEquals==-1) {
// skip anything without a =
continue;
}
String metaname =splits[s].substring(0, indexEquals);
String metavalue =splits[s].substring(indexEquals+1);
if (metaname.equals(nutchScoreMDName)) {
try {
customScore= Float.parseFloat(metavalue);
}catch (NumberFormatException nfe){}
}else if(metaname.equals(nutchFetchIntervalMDName)) {
try {
customInterval =Integer.parseInt(metavalue);
} catch (NumberFormatException nfe){}
}else metadata.put(metaname,metavalue);
}
}
/**
* 以<Text, CrawlDatum>的形式保存结果
* 其中Text指的是注入的url,CrawlDatum指封装了分值score、爬取的时间间隔* customInterval、当前爬取时间curTime等信息的CrawlDatum对象。
*/
if (url !=null) {
value.set(url); //将url设置为value(value是Text的文本对象)
CrawlDatum datum=newCrawlDatum(CrawlDatum.STATUS_INJECTED, customInterval); //将爬取时间封装到CrawlDatum对象中
datum.setFetchTime(curTime); //将当前爬取时间封装CrawlDatum对象中
//将分数封装到CrawlDatum对象中
if(customScore != -1) datum.setScore(customScore);
else datum.setScore(scoreInjected);
try { //将最新的分值添加到CrawlDatum对象中
scfilters.injectedScore(value,datum);
} catch (ScoringFilterException e) {
if(LOG.isWarnEnabled()) {
LOG.warn("Cannotfilter injected score for url " + url
+", using default (" + e.getMessage() + ")");
}
}
output.collect(value, datum); //存储结果
}
}
由上述代码实现我们可以看出,InjectMapper类功能实现主要经过了4个步骤:(看图)
详细过程如下:
先把urlDir(保存种子url的文件路径)中的url(一行一个url)一个一个读出来,然后根据相应的规则规整化和过滤url,判断是不是元数据。最后将url加载到value中,然后整合配置信息中的fetch信息到一个新建的CrawlDatum对象datum中,然后将<value,datum>作为键值通过output.collect(value,datum)将第一个任务的结果保存到临时文件tempDir中。
总结:
这里为什么要判断是不是元数据?
根据Map/Reduce的编程模式,在进入Map之前,应该将数据的数据分解成<key,value>的形式,这里如何实现的?
有分析显示,第一个任务的流程如下:
在Map之前,先把urlDir(保存种子url的文件路径)中的url(一行一个url)一个一个地读出,然后把url保存为value(value为Text类型对象),key为WritableComparable类型,值为0。然后将<key,value>作为Map过程的输入。
Map过程(Injector.InjectMapper类中定义):这个过程主要和urlDir有关,该过程主要的任务是判断输入的<key, value>中,value的值是否是应该爬取的页面的url,如果是,则将url重新加载到value中,然后整合配置信息中的fetch信息到一个新建的CrawlDatum对象datum中,然后将<value,
datum>作为键值通过output.collect(value, datum)保存第一个任务的结果临时保存文件tempDir中。
3) 第二个Map/Reduce
A. 第二个Mpa/Reduce主要是执行合并的任务,该合并任务是通过调用“CrawlDb.createJob()”方法来创建的,我们先来看一下这个方法:
public static JobConfcreateJob(Configuration config, Path crawlDb)
throwsIOException {
// 为即将生成的新“CrawlDb”产生一个临时的随机路径
Path newCrawlDb=
new Path(crawlDb,
Integer.toString(newRandom().nextInt(Integer.MAX_VALUE)));
//创建任务并设置任务名
JobConf job =new NutchJob(config);
job.setJobName("crawldb" + crawlDb);
//获得已有的“CrawlDb”路径
Path current =new Path(crawlDb, CURRENT_NAME);
//如果该文件存在,则把它加入到该任务的输入路径中
if(FileSystem.get(job).exists(current)) {
FileInputFormat.addInputPath(job,current);
}
/** 设置任务参数 */
job.setInputFormat(SequenceFileInputFormat.class);
job.setMapperClass(CrawlDbFilter.class); //设置Map类
job.setReducerClass(CrawlDbReducer.class); //设置reduce类,但被后面覆盖
FileOutputFormat.setOutputPath(job,newCrawlDb); //设置输出路径
job.setOutputFormat(MapFileOutputFormat.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(CrawlDatum.class);
//返回任务
return job;
}
根据
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(CrawlDatum.class);
可以看出合并任务的输出是个Map类型的文件,和之前的”CrawlDb”(如果存在)类型一致。该任务的map类是“org.apache.nutch.crawl.CrawlDbFilter” ,主要是规整化和过滤url。
B. 由以上代码可以看出,第二个M/R实现的主要步骤是:(如图)
C. 详细过程如下:
从第一个任务产生的tempDir文件和crawlDB中读<url, datum>键值对,并对这些键值
进行urlurl规整化和过滤处理,并按照同一个url为一组的原则组成一个<value,datums>(datums不是datum,datums是CrawlDatum的一个迭代器,其中包含了所有属于url的datum) 然后将从这些对应同一个url的众多CrawlDatum对象中选择一个CrawlDatum对象作为合适的<value, datum>作为输出,输出到crawlDb中。
该合并的实现是由“InjectReducer”(org.apache.nutch.crawl.Injector的静态内部类)具体实施的。
D. InjectReducer介绍
InjectReducer implements Reducer<Text,CrawlDatum, Text, CrawlDatum>
主要的方法:
publicvoid reduce(Text key, Iterator<CrawlDatum> values,
OutputCollector<Text,CrawlDatum> output, Reporter reporter)
该方法实现的功能主要是:合并对应同一个url的CrawlDatum对象。
下边是该方法的具体的代码实现:
private CrawlDatum old = newCrawlDatum(); //表示是CrawlDb中的url
private CrawlDatum injected = newCrawlDatum(); //表示是注入的种子url
public void reduce(Text key,Iterator<CrawlDatum> values,
OutputCollector<Text,CrawlDatum> output, Reporter reporter)
throws IOException {
booleanoldSet = false;
while (values.hasNext()) {
CrawlDatum val = values.next();
//新url,并初始化其未爬取的状态
if (val.getStatus() == CrawlDatum.STATUS_INJECTED) {
injected.set(val);
injected.setStatus(CrawlDatum.STATUS_DB_UNFETCHED);
} else { //原”CrawlDb”中已有的url
old.set(val);
oldSet = true;
}
}
/**
* 优先保存CrawlDB中存在的old,
* 即如果CrawlDB中存在当前url(key)对应的CrawlDatum对象,在爬取时,
* 则优先根据当前url在CrawlDB中存在的CrawlDatum对象信息,对该url进行
* 相关的处理(是爬取还是不爬取)
*/
CrawlDatum res= null;
if (oldSet) res= old; // don't overwrite existing value
else res = injected;
/** 以<key,CrawlDatum>的形式保存结果 */
output.collect(key,res);
}
4) 第三步:新旧文件替换
两个M/R实现以后,出现一新一旧两个“crawlDb”文件(如果crawlDb存在),现在将两个文件互换。相应的函数为CrawlDb.install(mergeJob, crawlDb)。下面我们看具体的实现代码:
public static void install(JobConf job,Path crawlDb) throws IOException {
//获取新“crawlDb”路径
Path newCrawlDb = FileOutputFormat.getOutputPath(job);
FileSystem fs = new JobClient(job).getFs();
Path old = newPath(crawlDb, "old");
//获取当前(旧)“crawlDb”路径
Path current =new Path(crawlDb, CURRENT_NAME);
//如果当前存在“crawlDb”,将其重命名为“old”
if (fs.exists(current)) {
if (fs.exists(old)) fs.delete(old, true);
fs.rename(current, old);
}
fs.mkdirs(crawlDb);
//把新的“crawlDb”重命名为当前“crawlDb”
fs.rename(newCrawlDb,current);
//删除“old”
if (fs.exists(old)) fs.delete(old, true);
Path lock = new Path(crawlDb, LOCK_NAME);
LockUtil.removeLockFile(fs,lock);
}
5) 第四步:删除原来的临时文件tempDir
好啦,Injector过程至此结束啦,哈哈哈哈哈哈。
本文详细介绍Apache Nutch的Injector模块工作原理。Injector负责将初始URL列表导入CrawlDb中,涉及两个MapReduce作业,用于规整化和过滤URL,以及合并更新CrawlDb。
1551

被折叠的 条评论
为什么被折叠?



