Python中使用haystack + jieba做搜索,如何实现partial word搜索?

配置 haystack 的时候,如果设置为 CharField,然后 analyzer 指定为 jieba 的 ChineseAnalyzer,那可以做到中文分词搜索,

但是比如对应数字 [ 123456789 ] ,你搜索 3456 就搜不到,英文也是,对于单词 [ hello ] ,你搜 ello 搜不到。

查询文档后,说可以把 CharField 改为 NgramField 类型,改了之后确实可以了,但是中文分词就不行了。

请问大家都是怎么解决这个问题的呢?
感觉这个场景还是蛮多的。
Python中使用haystack + jieba做搜索,如何实现partial word搜索?


5 回复

自己回答一发,目前解决了,但是不知道解决方案是否是最优:

使用 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

这个方案的工作原理:

  1. jieba搜索引擎模式分词:先按正常搜索习惯切分词语,比如“程序员”会被切分成“程序”和“员”
  2. ngram过滤:对每个分词结果再生成1-4个字的片段,这样“程序”会产生“程”、“程序”、“序”,“员”会产生“员”
  3. 索引和搜索:建立索引和搜索时都使用同样的分词逻辑,就能实现部分匹配

需要注意的几点:

  • ngram的maxsize可以根据你的需求调整,一般中文2-4个字比较合适
  • 这会显著增加索引大小,因为存储了很多片段
  • 搜索时同样使用这个analyzer,所以查询词也会被同样处理

另一种更轻量的替代方案是只用jieba的搜索引擎模式,然后对短词(比如<=2个字)进行特殊处理,但上面的ngram方案更通用。

简单说就是:用jieba切词后再加ngram来支持部分匹配。

刚好最近也在做搜索,也是用的 haystack + jieba,但 jieba 的问题就像你说的那样,支持了中文,反而有些英语和数字就搜索不出来了。

所以我就直接按自带的 engine,发现效果还是能接受。至于你说的 partial word 搜索,我测试了一下发现本身就支持的。
https://imgur.com/a/3GTYq

hello 你的意思是 你没有用 jieba 来作分词吗?就用的默认的?

是的,没用 jieba,中文搜索暂时还没遇到问题

回到顶部