nutch采集后的数据,以hadoop数据结构的形式保存,不方便直观的调用,虽然有对应的读取接口,但是可读性依然较差。nutch2.0已经在整合mysql等方面有很多优异表现。不过应对大数据场景下,mysql性能实在不容乐观。最近做贝叶斯算法,数据大范围的迁移到mongoDB,于是针对nutch1.6做轻度的二次开发,将爬取下的数据另存一份到mongoDB.。当然这个是轻量级的,重量级的应该是nutch全过程的写入和存储都指向到mongoDB。
1,nutch网页内容的存储过程
nutch的采集是通过Fetcher实现的。
JobConf job = new NutchJob(getConf());
job.setJobName("fetch " + segment);
job.setInt("fetcher.threads.fetch", threads);//设置线程数
job.set(Nutch.SEGMENT_NAME_KEY, segment.getName());
// for politeness, don't permit parallel execution of a single task
job.setSpeculativeExecution(false);
FileInputFormat.addInputPath(job, new Path(segment, CrawlDatum.GENERATE_DIR_NAME));
job.setInputFormat(InputFormat.class);
job.setMapRunnerClass(Fetcher.class);//设置map多线程类,非map类
FileOutputFormat.setOutputPath(job, segment);
job.setOutputFormat(FetcherOutputFormat.class);//主要是实现了getRecordWriter这个方法,用于得到相应的数据写出对象
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NutchWritable.class);
Fetcher类调用多线程进行采集,然后在FetcherOutputFormat类实现数据保存
FetcherOutputFormat实现了getRecordWriter方法
Path out = FileOutputFormat.getOutputPath(job);// 定义输出目录
final Path fetch = new Path(new Path(out, CrawlDatum.FETCH_DIR_NAME), name);// 定义抓取的输出目录
final Path content = new Path(new Path(out, Content.DIR_NAME), name);// 定义抓取内容的输出目录
final CompressionType compType = SequenceFileOutputFormat.getOutputCompressionType(job);// 定义数据压缩格式
final MapFile.Writer fetchOut = new MapFile.Writer(job, fs, fetch.toString(), Text.class, CrawlDatum.class, compType, progress);// 定义抓取的输出抽象类
最后调用write方法去保存相应的文件。
mongoDB在这块进行拦截即可。
2,nutch网页内容的保存到MongoDB
首先在FetcherOutputFormat的getRecordWriter方法初始化mongoDB的工具类
final MongoUtil mongoUtil = new MongoUtil("hadoop37", "nutch");
然后在write方法中进行存储
public void write(Text key, NutchWritable value) throws IOException {
// 对对象类型进行判断,调用相应的抽象输出,写到不同的文件中去
Writable w = value.get();
if (w instanceof CrawlDatum){
// System.out.println("((CrawlDatum) w).getStatus():"+((CrawlDatum) w).getStatus());
// System.out.println("CrawlDatum--"+key.toString()+":");
fetchOut.append(key, w);
//保存到mongoDB
Map<String, Object> map = ((CrawlDatum) w).toMap();
map.put("id", key.toString());
mongoUtil.insert("crawlDatum", map);
}
else if (w instanceof Content){
contentOut.append(key, w);
Map<String, Object> map = ((Content) w).toMap();
map.put("id", key.toString());
mongoUtil.insert("content", map);
}
else if (w instanceof Parse){//默认不开启
parseOut.write(key, (Parse) w);
}
}
实际上只需要保存CrawlDatum和Content的数据即可。由于mongodDB存储数据以MAP的形式保存,所以需要将CrawlDatum和Content的数据toMap,参考toString
比如CrawlDatum:
/**
* 返回map集合
* 2015年1月26日 joker4j自定义
* @return
*/
public Map<String,Object> toMap(){
Map<String, Object> map = new HashMap<String, Object>();
map.put("Version", CUR_VERSION);
map.put("Status", getStatus());
map.put("StatusName", getStatusName(getStatus()));
map.put("Fetch time", new Date(getFetchTime()));
map.put("Modified time", new Date(getModifiedTime()));
map.put("Retries since fetch", getRetriesSinceFetch());
map.put("Retry interval", getFetchInterval());
map.put("Score", getScore());
map.put("Signature", StringUtil.toHexString(getSignature()));
if (metaData != null) {
for (Entry<Writable, Writable> e : metaData.entrySet()) {
map.put(e.getKey().toString().replace(".", "_"), e.getValue().toString());
}
}
return map;
}
如此即可实现采集完毕一层即可保存在segment目录下的同时,也保存一份数据到mongoDB中。