技術情報 Nutch 0.9 でのプラグインの実装例
ほとんどの文章とオリジナルのコードは WritingPluginExample からのものです。トランクのリビジョン 506842 で動作し、ユニットテストを追加するように更新されました。
例
プラグインの例として、与えられた検索語に対して、特定のウェブページをレコメンドする機能が欲しいと仮定します。この例では、ここのサイトをインデックスしていると仮定します。お気づきのように、プラグインについて話題にしているページがたくさんあります。やりたいことは、もしだれかが "plugins" という単語で検索をしたとき、PluginCentral ページから始めるように薦めることとします。しかしまた、すべての通常の期待されるランキングの検索結果を表示したいとします。検索結果ページを、レコメンデーション部分と、通常検索部分とに分割します。
自分のサイトへ行って、レコメンドの結果を表示するページのメタタグに、次のようなタグを追加してください:
<meta name="recommended" content="plugins" />
これをやるには、3箇所の拡張ポイントを拡張するプラグインを実装する必要があります。メタタグから推奨された単語を取り出すために HTMLParser を拡張する必要がある。レコメンドされたフィールドをインデックスにするために IndexingFilter を拡張する必要があります。インデックスの新しいフィールドを検索できるように QueryFilter を実装する必要があります。
セットアップ
まずNutch ソースコードをダウンロードすることから始めます。変更を加える前に、一度そのままの状態でコンパイルできることを確かめてください。ソースコードをダウンロードしたディレクトリで、ant を実行しコンパイルできるはずです。もし問題があれば、メーリングリストに報告してください。
参考として Nutch に同梱されているプラグインのソースコードを使ってください。[チェックアウトしたディレクトリ]/src/plugin にあります。
たとえば、このプラグインを Nutch コミュニティに寄稿すると仮定したら、ディレクトリ/パッケージ構造を "org/apache/nutch" を使うようにしてください。もしプラグインを一人で使う場合は、"org/my_organization/nutch" などのように適当に置き換えてください。
必要なファイル
プラグインディレクトリの中に、プラグインの名前(この場合 'recommended') という名前のディレクトリを作る必要があります。そして、その中に、以下のものが必要です:
- plugin.xml ファイル - nutch にこのプラグインについて情報を提供します。
- build.xml ファイル - ビルド方法を記述します。
- recommended/src/java/org/apache/nutch/parse/recommended/[ソースをここに] というディレクトリ構造でプラグインのソースコードを置きます。
Plugin.xml
plugin.xml ファイルは以下のようなものになります:
<?xml version="1.0" encoding="UTF-8"?>
<plugin
id="recommended"
name="Recommended Parser/Filter"
version="0.0.1"
provider-name="nutch.org">
<runtime>
<!-- As defined in build.xml this plugin will end up bundled as recommended.jar -->
<library name="recommended.jar">
<export name="*"/>
</library>
</runtime>
<!-- The RecommendedParser extends the HtmlParseFilter to grab the contents of
any recommended meta tags -->
<extension id="org.apache.nutch.parse.recommended.recommendedfilter"
name="Recommended Parser"
point="org.apache.nutch.parse.HtmlParseFilter">
<implementation id="RecommendedParser"
class="org.apache.nutch.parse.recommended.RecommendedParser"/>
</extension>
<!-- TheRecommendedIndexer extends the IndexingFilter in order to add the contents
of the recommended meta tags (as found by the RecommendedParser) to the lucene
index. -->
<extension id="org.apache.nutch.parse.recommended.recommendedindexer"
name="Recommended identifier filter"
point="org.apache.nutch.indexer.IndexingFilter">
<implementation id="RecommendedIndexer"
class="org.apache.nutch.parse.recommended.RecommendedIndexer"/>
</extension>
<!-- The RecommendedQueryFilter gets called when you perform a search. It runs a
search for the user's query against the recommended fields. In order to get
add this to the list of filters that gets run by default, you have to use
"fields=DEFAULT". -->
<extension id="org.apache.nutch.parse.recommended.recommendedSearcher"
name="Recommended Search Query Filter"
point="org.apache.nutch.searcher.QueryFilter">
<implementation id="RecommendedQueryFilter"
class="org.apache.nutch.parse.recommended.RecommendedQueryFilter">
<parameter name="fields" value="recommended"/>
</implementation>
</extension>
</plugin>
Build.xml
もっとも単純な書き方で:
<?xml version="1.0"?> <project name="recommended" default="jar"> <import file="../build-plugin.xml"/> </project>
このファイルを [チェックアウトしたディレクトリ]/src/plugin/recommended に保存してください。
HTML Parser エクステンション
これは HTML Parser エクステンションのソースコードです。これは、レコメンドメタタグをコンテンツから取得し、パースされたドキュメントに追加しようとしています。このディレクトリ上に、RecommendedParser.java という名前で以下のような内容のファイルを作ります:
package org.apache.nutch.parse.recommended;
// JDK imports
import java.util.Enumeration;
import java.util.Properties;
import java.util.logging.Logger;
// Nutch imports
import org.apache.hadoop.conf.Configuration;
import org.apache.nutch.parse.HTMLMetaTags;
import org.apache.nutch.parse.Parse;
import org.apache.nutch.parse.HtmlParseFilter;
import org.apache.nutch.protocol.Content;
// Commons imports
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// W3C imports
import org.w3c.dom.DocumentFragment;
public class RecommendedParser implements HtmlParseFilter {
private static final Log LOG = LogFactory.getLog(RecommendedParser.class.getName());
private Configuration conf;
/** The Recommended meta data attribute name */
public static final String META_RECOMMENDED_NAME="Recommended";
/**
* Scan the HTML document looking for a recommended meta tag.
*/
public Parse filter(Content content, Parse parse,
HTMLMetaTags metaTags, DocumentFragment doc) {
// Trying to find the document's recommended term
String recommendation = null;
Properties generalMetaTags = metaTags.getGeneralTags();
for (Enumeration tagNames = generalMetaTags.propertyNames(); tagNames.hasMoreElements(); ) {
if (tagNames.nextElement().equals("recommended")) {
recommendation = generalMetaTags.getProperty("recommended");
LOG.info("Found a Recommendation for " + recommendation);
}
}
if (recommendation == null) {
LOG.info("No Recommendation");
} else {
LOG.info("Adding Recommendation for " + recommendation);
parse.getData().getContentMeta().set(META_RECOMMENDED_NAME, recommendation);
}
return parse;
}
public void setConf(Configuration conf) {
this.conf = conf;
}
public Configuration getConf() {
return this.conf;
}
}
Indexer エクステンション
これは Indexing フィルタのエクステンションのコードです。レコメンドメタタグを持つドキュメントがインデックスされるとき、このエクステンションは メタタグの内容を "recommended" という名前の lucene のテキストフィールドを追加します。ソースコードディレクトリの中に、RecommendedIndexer.java というファイルを作ってください:
package org.apache.nutch.parse.recommended;
// JDK import
import java.util.logging.Logger;
// Commons imports
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// Nutch imports
import org.apache.nutch.util.LogUtil;
import org.apache.nutch.fetcher.FetcherOutput;
import org.apache.nutch.indexer.IndexingFilter;
import org.apache.nutch.indexer.IndexingException;
import org.apache.nutch.parse.Parse;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.Text;
import org.apache.nutch.crawl.CrawlDatum;
import org.apache.nutch.crawl.Inlinks;
// Lucene imports
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Document;
public class RecommendedIndexer implements IndexingFilter {
public static final Log LOG = LogFactory.getLog(RecommendedIndexer.class.getName());
private Configuration conf;
public RecommendedIndexer() {
}
public Document filter(Document doc, Parse parse, Text url,
CrawlDatum datum, Inlinks inlinks)
throws IndexingException {
String recommendation = parse.getData().getMeta("Recommended");
if (recommendation != null) {
Field recommendedField =
new Field("recommended", recommendation,
Field.Store.YES, Field.Index.UN_TOKENIZED);
recommendedField.setBoost(5.0f);
doc.add(recommendedField);
LOG.info("Added " + recommendation + " to the recommended Field");
}
return doc;
}
public void setConf(Configuration conf) {
this.conf = conf;
}
public Configuration getConf() {
return this.conf;
}
}リコメンドタグはトークナイザで分割したくないので、フィールドを UN_TOKENIZED にしていることに注意してください。もし、例えば1つのタグに複数のレコメンド単語を入れるなど、タグの一部に対して検索したい場合は、TOKENIZED に変えてください。
QueryFilter
QueryFilter はユーザーが検索を実行したときに呼び出されます。検索結果にリコメンドフィールドの影響を増やすために、boost を増やします。
package org.apache.nutch.parse.recommended;
import org.apache.nutch.searcher.FieldQueryFilter;
import java.util.logging.Logger;
// Commons imports
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class RecommendedQueryFilter extends FieldQueryFilter {
private static final Log LOG = LogFactory.getLog(RecommendedParser.class.getName());
public RecommendedQueryFilter() {
super("recommended", 5f);
LOG.info("Added a recommended query");
}
}
プラグインのコンパイル
プラグイン―もしくは Nutch ―をビルドするには ant が必要です。もし mac os を使っているなら fink で簡単に入手できます。ついでに junit も入手しましょう。
fink install ant ant-junit junit
ビルドには、build.xml を保存したプラグインのディレクトリ(おそらく[チェックアウトしたディレクトリ]/src/plugin/recommended)に移動し、こうタイプします:
ant
うまくいけば、長いテキストが出力され、続いてビルドが成功したことを知らせるメッセージが表示されるでしょう。
プラグインを Ant でビルドできるようにする
ant でプラグインをグローバルビルド上にコンパイル・デプロイできるようにするには、src/plugin/build.xml を編集します(チェックアウトしたディレクトリのルートの build.xml ではありません)。次のような行がたくさんあるとおもいます:
<ant dir="[plugin-name]" target="deploy" />
</target> タグの直前に、ブロックを編集してこのプラグイン用に1行追加します。
<ant dir="recommended" target="deploy" />
チェックアウトしたディレクトリのルートで、'ant' を実行するとすべてコンパイルされ jar になるはずです。次回クロールを実行するときには、あなたのパーサーとインデックスフィルタが使われるはずです。
新しい ROOT.war ファイルをコンパイルするには、'ant war' を実行してください。それを配備すれば検索実行時に、あなたのクエリフィルタが使われるでしょう。
ユニットテスト
ユニットテストには2つのファイルを作る必要があります: テストが行われるページと、テストを行うクラスです。もう一度、プラグインのディレクトリが [チェックアウトしたディレクトリ]/src/plugin で、テストプラグインがこのディレクトリの下にあることを確認してください。 recommended/data というディレクトリを作り、その中に recommended.html というファイルを作ってください。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>recommended</title>
<meta name="generator" content="TextMate http://macromates.com/">
<meta name="author" content="Ricardo J. Méndez">
<meta name="recommended" content="recommended-content"/>
<!-- Date: 2007-02-12 -->
</head>
<body>
Recommended meta tag test.
</body>
</html>このファイルは、現在のところ解析しようとしている "recommended-content" という値のメタタグを含んでいます。では私のお気に入りのエディタの宣伝をしたところで、テストクラスに移りましょう。
今回はテストコードのためにディレクトリを作ります。例えば recommended/src/test/org/apache/nutch/parse/recommended/[テストソースをここに]。 TestRecommendedParser.java というファイルを作ります。
package org.apache.nutch.parse.recommended;
import org.apache.nutch.metadata.Metadata;
import org.apache.nutch.parse.Parse;
import org.apache.nutch.parse.ParseUtil;
import org.apache.nutch.protocol.Content;
import org.apache.hadoop.conf.Configuration;
import org.apache.nutch.util.NutchConfiguration;
import java.util.Properties;
import java.io.*;
import java.net.URL;
import junit.framework.TestCase;
/*
* Loads test page recommended.html and verifies that the recommended
* meta tag has recommended-content as its value.
*
*/
public class TestRecommendedParser extends TestCase {
private static final File testDir =
new File(System.getProperty("test.data"));
public void testPages() throws Exception {
pageTest(new File(testDir, "recommended.html"), "http://foo.com/",
"recommended-content");
}
public void pageTest(File file, String url, String recommendation)
throws Exception {
String contentType = "text/html";
InputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream((int)file.length());
byte[] buffer = new byte[1024];
int i;
while ((i = in.read(buffer)) != -1) {
out.write(buffer, 0, i);
}
in.close();
byte[] bytes = out.toByteArray();
Configuration conf = NutchConfiguration.create();
Content content =
new Content(url, url, bytes, contentType, new Metadata(), conf);
Parse parse = new ParseUtil(conf).parseByExtensionId("parse-html",content);
Metadata metadata = parse.getData().getContentMeta();
assertEquals(recommendation, metadata.get("Recommended"));
assertTrue("somesillycontent" != metadata.get("Recommended"));
}
}このように、このコードはまずドキュメントをパースし、contentMeta オブジェクト (これはRecommendedParser のときに保存しています) に Recommended アイテムがあるか探します。そして、その値が recommended-content にセットされているか確認します。
そして、[チェックアウトしたディレクトリ]/src/plugin/recommended に、ある build.xml に数行追加します。最小限ですますには次のようにします:
<?xml version="1.0"?>
<project name="recommended" default="jar">
<import file="../build-plugin.xml"/>
<!-- for junit test -->
<mkdir dir="${build.test}/data"/>
<copy file="data/recommended.html" todir="${build.test}/data"/>
</project>これらの行では、テストデータをテストのために適切なディレクトリにコピーします。
テストケースを実行するには、プラグインのルートディレクトリに戻り、次のように実行します:
ant test
Nutch がプラグインを使用するようにする
Nutch があなたのプラグインを使うようにするには、conf/nutch-site.xml を編集し、このようにブロックに追加します:
<property> <name>plugin.includes</name> <value>nutch-extensionpoints|protocol-http|urlfilter-regex|parse-(text|html)|index-basic|query-(basic|site|url)</value> <description>Regular expression naming plugin id names to include. Any plugin not matching this expression is excluded. In any case you need at least include the nutch-extensionpoints plugin. By default Nutch includes crawling just HTML and plain text via HTTP, and basic indexing and search plugins. </description> </property>
プラグインのidがインクルードされるように正規表現を編集してください。
<value>recommended|protocol-http|urlfilter-regex|parse-(text|html|js)|index-basic|query-(basic|site|url)|summary-basic|scoring-opic|urlnormalizer-(pass|regex|basic)</value>




