rainyzz's blog

编写Ansj的Solr插件

在一个基于Solr搜索的项目中之前使用的IKAnalyzer分词效果不是很好,师兄推荐我用Ansj,但是当时Ansj没有Solr的插件,在作者Github项目主页的issue中作者也说不准备支持Solr,当时就放弃了。前几天一个同学说Solr有第三方的Solr插件ansj4solr,下载使用发现该插件不支持1.1版本以上的Ansj,因为Ansj在1.1的时候修改了分词的调用接口,而且ansj4solr还有一些我使用不到的功能,所以决定自己写一个最简单的具备最基本功能的插件。

实现分词的Solr插件主要是实现TokenizerFactory类和Tokenizer类,前者负责接受Solr中schema.xml配置文件的调用,读取xml文件中的配置并返回对应的Tokenizer类,后者负责接受Solr传送过来的数据流,调用分词,产生最后分好词的Term流。

在Ansj项目中作者提供了Ansj在Lucene下的插件,这个插件包含了Analyzer类的实现和Tokenizer类的实现,由于Solr是基于Lucene,Solr中的TokenizerFactory就相当于Lucene中的Analyzer,Tokenizer类是可以共用的,因此我就基于作者主页中的Lucene4插件中的Tokenizer类实现对应的TokenizerFactory类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package me.rainystars.ansj.solr.plugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.ansj.lucene.util.AnsjTokenizer;
import org.ansj.splitWord.analysis.IndexAnalysis;
import org.ansj.splitWord.analysis.ToAnalysis;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.util.TokenizerFactory;
import org.apache.lucene.util.AttributeSource.AttributeFactory;
public class AnsjTokenizerFactory extends TokenizerFactory{
boolean pstemming;
boolean isQuery;
private String stopwordsDir;
public Set<String> filter;
public AnsjTokenizerFactory(Map<String, String> args) {
super(args);
assureMatchVersion();
isQuery = getBoolean(args, "isQuery", true);
pstemming = getBoolean(args, "pstemming", false);
stopwordsDir = get(args,"words");
addStopwords(stopwordsDir);
}
//add stopwords list to filter
private void addStopwords(String dir) {
if (dir == null){
System.out.println("no stopwords dir");
return;
}
//read stoplist
System.out.println("stopwords: " + dir);
filter = new HashSet<String>();
File file = new File(dir);
InputStreamReader reader;
try {
reader = new InputStreamReader(new FileInputStream(file),"UTF-8");
BufferedReader br = new BufferedReader(reader);
String word = br.readLine();
while (word != null) {
filter.add(word);
word = br.readLine();
}
} catch (FileNotFoundException e) {
System.out.println("No stopword file found");
} catch (IOException e) {
System.out.println("stopword file io exception");
}
}
@Override
public Tokenizer create(AttributeFactory factory, Reader input) {
if(isQuery == true){
//query
return new AnsjTokenizer(new ToAnalysis(new BufferedReader(input)), input, filter, pstemming);
} else {
//index
return new AnsjTokenizer(new IndexAnalysis(new BufferedReader(input)), input, filter, pstemming);
}
}
}

编写TokenizerFactory只需要覆盖create方法,在该方法内调用对应的Tokenizer。其他要做的就是需要在TokenizerFactory的构造函数中读取schema.xml配置fieldType时提供的参数,将对应的参数传给Tokenizer或做对应的处理,我这里因为Tokenizer调用的是原作者的部分,所以总共支持三个参数isQuerypstemmingwords

其中的isQuery是用来判断使用分词的策略是检索时需要的比较精确的分词方式还是建立索引时所需要的比较不精确但是产生词语较多的分词方式,根据选择调用不同的分词器。

其中的pstemming是原作者提供的参数,用来判断是否需要处理英文名词的单复数,第三人称等。

其中的words是停止词的路径,在我的使用中Solr服务器所在的目录为D:\work_solr\example\,如果把停止词放置在D:\work_solr\example\solr\collection1\conf文件夹下,就应该添加参数如下:

1
<tokenizer class="me.rainystars.ansj.solr.plugin.AnsjTokenizerFactory" isQuery="false" words="solr/collection1/conf/stopwords_ch.txt"/>

然后就可以读取文件中的停止词列表,传递给原作者的Tokenizer进行停止词过滤,停止词文件请使用UTF-8格式。

该插件使用时请将插件的jar包与Ansj项目中的ansj_seg.jartree_split.jaransj_lucene4_plugin.jar放置在Solr的Web文件夹的lib目录中,上述文件下载地址请访问作者主页

在schema.xml中对应的配置文件如下:

1
2
3
4
5
6
7
8
<fieldType name="text_ch" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="me.rainystars.ansj.solr.plugin.AnsjTokenizerFactory" isQuery="false"/>
</analyzer>
<analyzer type="query">
<tokenizer class="me.rainystars.ansj.solr.plugin.AnsjTokenizerFactory"/>
</analyzer>
</fieldType>

该插件的更多信息请见我Github的项目主页

插件下载地址:点击我