Python中使用haystack + jieba做搜索,如何实现partial word搜索?
配置 haystack 的时候,如果设置为 CharField,然后 analyzer 指定为 jieba 的 ChineseAnalyzer,那可以做到中文分词搜索,
但是比如对应数字 [ 123456789 ] ,你搜索 3456 就搜不到,英文也是,对于单词 [ hello ] ,你搜 ello 搜不到。
查询文档后,说可以把 CharField 改为 NgramField 类型,改了之后确实可以了,但是中文分词就不行了。
请问大家都是怎么解决这个问题的呢?
感觉这个场景还是蛮多的。
Python中使用haystack + jieba做搜索,如何实现partial word搜索?
自己回答一发,目前解决了,但是不知道解决方案是否是最优:
使用 jieba 分词,目前看来是不支持 Ngram 的,也就是说,如果对 Document=True 的字段设置为 NgramField,则中英文分词会有问题,比如 [施华洛世奇 Swarovski ] ,你就搜不到了。
但是如果不用 NgramField,那对于 partial word 搜索,就不行,比如 Swarovski,你搜索 warov 就搜不到。
我现在的解决办法是,对于 Document=True 的字段还是用 CharField,然后另外增加几个你需要搜索的字段,类型为 NgramField,然后改写自己写 form:
class CustSearchForm(SearchForm):
def search(self):
sqs = super(CustSearchForm, self).search()
if ‘q’ in self.cleaned_data:
q = self.cleaned_data[‘q’]
sqs = sqs.filter_or(EAN=q).<br> filter_or(SKU=q).<br> filter_or(brand=q)
return sqs
这里的 trick 是使用 filter_or,这个可以查看下 haystack 的文档。
我理解你想在Haystack里用jieba做中文分词搜索时,支持部分词匹配。比如搜“程序员”也能匹配到“程序”或“员”。这确实是个常见需求,jieba默认的分词和Haystack的标准配置不太直接支持这个。
核心思路是自定义一个analyzer,结合jieba的搜索引擎模式分词和ngram tokenizer。下面是一个完整的实现示例:
# 首先,确保安装了必要的包:pip install jieba whoosh django-haystack
from haystack import indexes
from jieba.analyse import ChineseAnalyzer
from whoosh.analysis import NgramFilter, Filter
from whoosh.fields import TEXT
import jieba
# 1. 自定义一个结合jieba和ngram的analyzer
class JiebaNgramAnalyzer(ChineseAnalyzer):
def __init__(self, minsize=1, maxsize=4, *args, **kwargs):
# 调用父类初始化,使用jieba的搜索引擎模式分词
super().__init__(*args, **kwargs)
# 添加ngram过滤器,这里设置生成1到4个字的片段
self.token_filters.append(NgramFilter(minsize=minsize, maxsize=maxsize))
# 2. 在模型索引中使用这个自定义analyzer
class YourModelIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
# 其他字段...
def get_model(self):
return YourModel
def index_queryset(self, using=None):
return self.get_model().objects.all()
# 3. 在settings.py中配置Haystack使用Whoosh,并指定自定义analyzer
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
'EXCLUDED_INDEXES': [],
'INCLUDE_SPELLING': True,
},
}
# 4. 关键步骤:在创建Whoosh索引时使用自定义analyzer
# 需要在你的manage.py同目录下创建search_indexes.py,并重写build_schema方法
from whoosh.fields import Schema
from whoosh.analysis import StemmingAnalyzer
# 重写Whoosh的Schema创建,使用我们的JiebaNgramAnalyzer
def build_schema(self, fields):
schema_fields = {}
for field_name, field_class in fields.items():
if isinstance(field_class, TEXT):
# 对TEXT字段使用自定义的分词器
schema_fields[field_name] = TEXT(
analyzer=JiebaNgramAnalyzer(),
stored=field_class.stored,
field_boost=field_class.field_boost,
sortable=field_class.sortable
)
else:
schema_fields[field_name] = field_class
return Schema(**schema_fields)
# 将这个build_schema方法替换到WhooshEngine中
from haystack.backends.whoosh_backend import WhooshEngine
WhooshEngine.build_schema = build_schema
这个方案的工作原理:
- jieba搜索引擎模式分词:先按正常搜索习惯切分词语,比如“程序员”会被切分成“程序”和“员”
- ngram过滤:对每个分词结果再生成1-4个字的片段,这样“程序”会产生“程”、“程序”、“序”,“员”会产生“员”
- 索引和搜索:建立索引和搜索时都使用同样的分词逻辑,就能实现部分匹配
需要注意的几点:
- ngram的
maxsize可以根据你的需求调整,一般中文2-4个字比较合适 - 这会显著增加索引大小,因为存储了很多片段
- 搜索时同样使用这个analyzer,所以查询词也会被同样处理
另一种更轻量的替代方案是只用jieba的搜索引擎模式,然后对短词(比如<=2个字)进行特殊处理,但上面的ngram方案更通用。
简单说就是:用jieba切词后再加ngram来支持部分匹配。
刚好最近也在做搜索,也是用的 haystack + jieba,但 jieba 的问题就像你说的那样,支持了中文,反而有些英语和数字就搜索不出来了。
所以我就直接按自带的 engine,发现效果还是能接受。至于你说的 partial word 搜索,我测试了一下发现本身就支持的。
https://imgur.com/a/3GTYq
hello 你的意思是 你没有用 jieba 来作分词吗?就用的默认的?
是的,没用 jieba,中文搜索暂时还没遇到问题

