最近项目中碰到一个需求,就是从数据库mongo中查出相应的url ,然后爬虫解析出字段,再indexer回mongodb中。
那么碰到的问题就是:如何将mongo中的url的主键传递到indexer中去。
--------------------------------------实现思路是:
修改源码Crawl.java,在
prepareInjectTxt(url);
injector.inject(crawlDb, rootUrlDir);
也就是说在inject之前,执行一个函数,函数的作用是从mongo中查出需要爬虫的url,写入到对应的url文件
这个文件就是injector.inject所需要的文件。
精华在于:写入每个url时,后面附带上
outputStream.write((url+" id="+id+"\n").getBytes());
注意:!!!这里的url+" id="的id之前的这个不是空格,而是\t.
这是Injector.java中代码所定义的分隔符,见下面的代码:
if (url.indexOf("\t")!=-1){
String[] splits = url.split("\t");
url = splits[0];
for (int s=1;s<splits.length;s++){
// find separation between name 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 if (metaname.equals(nutchFixedFetchIntervalMDName)) {
try {
fixedInterval = Integer.parseInt(metavalue);}
catch (NumberFormatException nfe){}
}
else metadata.put(metaname,metavalue);
}
}
也就是说,此刻,我们的主键存放到了metadata中!
----------继续跟踪这个metadata.
通过代码
datum.getMetaData().put(new Text(keymd), new Text(valuemd));
output.collect(value, datum);
我们可以知道datum.getMetaData()中存放了我们的自定义主键。
----------------------------------------------------经过inject和generate的过程后,来到fetch部分。
文件读取是由
private static class QueueFeeder extends Thread {
完成的。里面的具体读取代码见:
while (feed > 0 && hasMore) {
try {
Text url = new Text();
CrawlDatum datum = new CrawlDatum();
hasMore = reader.next(url, datum);
if (hasMore) {
queues.addFetchItem(url, datum);
我们打印出这个datum的内容如下:
MapWritable mw=datum.getMetaData();
Set<Entry<Writable,Writable>> kv= mw.entrySet();
Iterator<Entry<Writable,Writable>> iterator = kv.iterator();
while(iterator.hasNext()){
Entry<Writable,Writable> entry = iterator.next();
Writable key = entry.getKey();
Writable value = entry.getValue();
System.out.println("k/v---------------------------"+key +" "+value);
}
打印出来的结果是:
k/v---------------------------id 158134
k/v---------------------------_ngt_ 1414041495640
说明确实存在这个从数据库里读取的id.继续跟踪这个值。
再来看
private class FetcherThread extends Thread {
有这么一段代码:
output.collect(key, new NutchWritable(datum));
if (content != null && storingContent)
output.collect(key, new NutchWritable(content));
打印这个datum的值。
k/v---------------------------id 158134
k/v---------------------------_ngt_ 1414042449861
k/v---------------------------Content-Type text/html
k/v---------------------------_pst_ success(1), lastModified=0
至于怎么写入到磁盘的,请参考类FetcherOutputFormat.java类
代码如下:
if (w instanceof CrawlDatum)
fetchOut.append(key, w);
else if (w instanceof Content && contentOut != null)
contentOut.append(key, w);
else if (w instanceof Parse && parseOut != null)
parseOut.write(key, (Parse)w);
为了印证确实是这里写入了数据,我们打印它,结果如下:
[FetcherOutputFormat]k/v---------------------------id 158134
[FetcherOutputFormat]k/v---------------------------_ngt_ 1414043468795
[FetcherOutputFormat]k/v---------------------------Content-Type text/html
[FetcherOutputFormat]k/v---------------------------_pst_ success(1), lastModified=0
--------------------------------------------然后进入Parse环节。
这个环境的内容由以下2行代码提供
job.setMapperClass(ParseSegment.class);
job.setReducerClass(ParseSegment.class);
这个job的输入路径是
FileInputFormat.addInputPath(job, new Path(segment, Content.DIR_NAME));
跟fetch之后的结果没有影响,因为不会读文件夹crawl_fetch,只读文件夹content.
所以我们把parse忽略掉。update也忽略掉。
------------------------------index过程。
IndexerMapReduce.initMRJob(crawlDb, linkDb, segments, job);
跟踪这个函数,有以下的代码:
for (final Path segment : segments) {
LOG.info("IndexerMapReduces: adding segment: " + segment);
FileInputFormat.addInputPath(job, new Path(segment, CrawlDatum.FETCH_DIR_NAME));
FileInputFormat.addInputPath(job, new Path(segment, CrawlDatum.PARSE_DIR_NAME));
FileInputFormat.addInputPath(job, new Path(segment, ParseData.DIR_NAME));
FileInputFormat.addInputPath(job, new Path(segment, ParseText.DIR_NAME));
}
然后查看代码,发现:
job.setMapperClass(IndexerMapReduce.class);
job.setReducerClass(IndexerMapReduce.class);
----------------------------这里主要看它的reduce方法。
// don't index unmodified (empty) pages
if (datum.getStatus() != CrawlDatum.STATUS_FETCH_NOTMODIFIED) {
fetchDatum = datum;
应该我们需要的fetchDatum就在这个fetchDatum中,大胆猜测,小心验证,写代码输出值如下:
[IndexerMapReduce]k/v---------------------------id 158134
[IndexerMapReduce]k/v---------------------------_ngt_ 1414044736594
[IndexerMapReduce]k/v---------------------------Content-Type text/html
[IndexerMapReduce]k/v---------------------------_pst_ success(1), lastModified=0
果然是如此,很好,我们拿到了主键。下面就可以拿来用了!
-------------------------------------------------------------------------------------------
最关键的代码就是IndexeMapReduce中的
// run indexing filters
doc = this.filters.filter(doc, parse, key, fetchDatum, inlinks);
看来是要在索引过滤插件IndexingFilter中写代码提取这个参数了。
修改函数
// implements the filter-method which gives you access to important Objects
// like NutchDocument
public NutchDocument filter(NutchDocument doc, Parse parse, Text url,
CrawlDatum datum, Inlinks inlinks) {
我们从datum中获取这个_id.
String id = getValueFromMetaData(datum.getMetaData(),"id");
doc.add("_id", id);
public String getValueFromMetaData(MapWritable mw,String k){
Set<Entry<Writable,Writable>> kv= mw.entrySet();
Iterator<Entry<Writable,Writable>> iterator = kv.iterator();
while(iterator.hasNext()){
Entry<Writable,Writable> entry = iterator.next();
Writable key = entry.getKey();
Writable value = entry.getValue();
System.out.println("[IndexingFilterAll]k/v---------------------------"+key +" "+value);
if(key.toString().equals(k)) return value.toString();
}
return null;
}【这里或许有直接获取key的方法,对Hadoop暂时不是很熟悉】
大功告成!