【问题标题】:Can't find strings that aren't words in Django Haystick/Elasticsearch在 Django Haystick/Elasticsearch 中找不到不是单词的字符串
【发布时间】:2025-11-23 07:55:02
【问题描述】:

我正在使用带有 Elasticsearch 的 Django Haystack 作为实时航班地图服务的后端。

我已正确设置所有搜索索引,但是,我无法返回非完整字词的搜索结果(例如航空呼号,其中一些采用 N346IF 样式,其他包含完整字词例如Speedbird 500)。 N346IF 样式的查询不会产生任何结果,而对于后一个示例,我可以轻松返回结果。

我的查询如下:

queryResults = SearchQuerySet().filter(content=q) # where q is the query in string format

(请注意,过去我使用了AutoQuery 查询集,但文档列出了它只跟踪单词,所以我现在传递一个原始字符串)。

我的搜索索引字段设置为EdgeNgramField 和搜索模板。

我有一个具有以下索引设置的自定义后端(以及snowball 分析器和pattern 分析器):

ELASTICSEARCH_INDEX_SETTINGS = {
    'settings': {
        "analysis": {
            "analyzer": {
                "ngram_analyzer": {
                    "type": "custom",
                    "tokenizer": "lowercase",
                    "filter": ["haystack_ngram"]
                },
                "edgengram_analyzer": {
                    "type": "custom",
                    "tokenizer": "lowercase",
                    "filter": ["haystack_edgengram"]
                }
            },
            "tokenizer": {
                "haystack_ngram_tokenizer": {
                    "type": "nGram",
                    "min_gram": 4,
                    "max_gram": 15,
                },
                "haystack_edgengram_tokenizer": {
                    "type": "edgeNGram",
                    "min_gram": 4,
                    "max_gram": 15,
                    "side": "front"
                }
            },
            "filter": {
                "haystack_ngram": {
                    "type": "nGram",
                    "min_gram": 4,
                    "max_gram": 15
                },
                "haystack_edgengram": {
                    "type": "edgeNGram",
                    "min_gram": 4,
                    "max_gram": 15
                }
            }
        }
    }
}

ELASTICSEARCH_DEFAULT_ANALYZER = "pattern"

我的后端配置为:

class ConfigurableElasticBackend(ElasticsearchSearchBackend):

    def __init__(self, connection_alias, **connection_options):
        super(ConfigurableElasticBackend, self).__init__(
                                connection_alias, **connection_options)
        user_settings = getattr(settings, 'ELASTICSEARCH_INDEX_SETTINGS')
        if user_settings:
            setattr(self, 'DEFAULT_SETTINGS', user_settings)

class ConfigurableElasticBackend(ElasticsearchSearchBackend):

    DEFAULT_ANALYZER = "pattern"

    def __init__(self, connection_alias, **connection_options):
        super(ConfigurableElasticBackend, self).__init__(
                                connection_alias, **connection_options)

        user_settings = getattr(settings, 'ELASTICSEARCH_INDEX_SETTINGS')
        user_analyzer = getattr(settings, 'ELASTICSEARCH_DEFAULT_ANALYZER')

        if user_settings:
            setattr(self, 'DEFAULT_SETTINGS', user_settings)
        if user_analyzer:
            setattr(self, 'DEFAULT_ANALYZER', user_analyzer)

    def build_schema(self, fields):
        content_field_name, mapping = super(ConfigurableElasticBackend,
                                              self).build_schema(fields)

        for field_name, field_class in fields.items():
            field_mapping = mapping[field_class.index_fieldname]

            if field_mapping['type'] == 'string' and field_class.indexed:
                if not hasattr(field_class, 'facet_for') and not \
                                  field_class.field_type in('ngram', 'edge_ngram'):
                    field_mapping['analyzer'] = self.DEFAULT_ANALYZER
            mapping.update({field_class.index_fieldname: field_mapping})
        return (content_field_name, mapping)

class ConfigurableElasticSearchEngine(ElasticsearchSearchEngine):
    backend = ConfigurableElasticBackend

为了成功地为既是和/或N346IF-style 字符串的搜索模式生成结果,正确的设置是什么?

感谢任何输入,如果这与另一个问题相似(找不到任何相关内容),我们深表歉意。


编辑: solarissmoke 请求,此模型的架构:

class FlightIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.EdgeNgramField(document=True, use_template=True)
    flight = indexes.CharField(model_attr='flightID')
    callsign = indexes.CharField(model_attr='callsign')
    displayName = indexes.CharField(model_attr='displayName')
    session = indexes.CharField(model_attr='session')

    def prepare_session(self, obj):
        return obj.session.serverId

    def get_model(self):
        return Flight

文本被索引为:

flight___{{ object.callsign }}___{{ object.displayName }}

【问题讨论】:

  • 我认为我们需要查看您正在索引的字段的架构。请发布索引定义。
  • @solarissmoke - 刚刚编辑它。如果您认为还有其他需要,请告诉我。
  • 我有理由确定我知道问题出在哪里,但为了帮助我确认您能否提供您可以成功搜索的样本Flight 数据(callsigndisplayName),以及一些你不能,以及相关的搜索查询?
  • 没问题!有效的查询:callsignUnited 55displayNameTsuyoshi Hiroi - 使用查询 UnitedUnited 55,将返回结果。一个不起作用:callsignN133TCdisplayNameShahrul Nizam,并且通过查询呼号(其中查询内容为N133TC),此查询不起作用。但是,如果使用显示名称,则此查询有效,Shahrul 产生结果)。
  • 嗯,这不是我所期望的。您能否确认在您的text 文档中___ 是下划线而不是空格?

标签: django elasticsearch django-haystack


【解决方案1】:

它并不能完全解释您所看到的行为,但我认为问题在于您如何索引数据 - 特别是 text 字段(这是在您过滤 content 时搜索的内容)。

以您提供的示例数据为例,呼号N133TC,航班名称Shahrul Nizam。此数据的text 文档变为:

flight___N133TC___Shahrul Nizam

您已将此字段设置为 EdgeNgramField(最少 4 个字符,最多 15 个字符)。以下是索引此文档时生成的 ngram(为简单起见,我忽略了小写过滤器):

flig
fligh
flight
flight_
flight___
flight___N
flight___N1
flight___N13
flight___N133
flight___N133T
flight___N133TC
Niza
Nizam

请注意,分词器不会按下划线拆分。现在,如果您搜索N133TC,以上标记都不会匹配。 (我无法解释为什么 Shahrul 有效……它不应该,除非我遗漏了什么,或者该字段的开头有空格)。

如果您将 text 文档更改为:

flight N133TC Shahrul Nizam

那么索引标记将是:

flig
flight
N133
N133T
N133TC
Shah
Shahr
Shahru
Shahrul
Niza
Nizam

现在,N133TC 的搜索应该匹配。

另请注意,您文档中的 flight___ 字符串会生成一大堆(很可能)无用的标记 - 除非这是故意的,否则您可能会更好。

【讨论】:

  • 现在这更有意义了,感谢您的回答。我不知道标记器是如何分裂的。然而,这并不能解决问题,N133TC 呼号模式仍然不匹配。不确定这是否与过滤器设置有关...顺便说一下,作为参考,我设置了 flight__ 前缀,因为搜索处理不同的模型,我想在前端更轻松地与它们区分开来.我将切换并使用另一个键来定义这些。
  • 我怀疑问题在于您的数据是如何被索引的,但看不到它可能是什么。您可能需要使用分析 API 来准确查看分析数据的样子。
  • 感谢您的帮助 - 非常感谢。只是一个快速的更新,尝试这个来找到正确的模式; github.com/polyfractal/elasticsearch-inquisitor
【解决方案2】:

解决我自己的问题 - 感谢 solarissmoke 的意见,因为它帮助我找到了导致此问题的原因。

我的回答基于 Greg Baker 对问题的回答 ElasticSearch: EdgeNgrams and Numbers

问题似乎与在搜索文本中使用数值有关(在我的例子中,N133TC 模式)。请注意,我最初使用的是 snowball 分析器,然后才切换到 pattern - 这些都不起作用。

我在settings.py中调整了我的分析器设置:

"edgengram_analyzer": {
    "type": "custom",
    "tokenizer": "standard",
    "filter": ["haystack_edgengram"]
}

因此将 tokenizer 值从原来使用的 lowercase 分析器更改为 standard

然后我将后端使用的默认分析器设置为edgengram_analyzer(也在settings.py):

ELASTICSEARCH_DEFAULT_ANALYZER = "edgengram_analyzer"

这就是诀窍!它仍然可以作为 EdgeNgram 字段使用,但也可以正确返回我的数值。

我还遵循了 solarissmoke 答案中的建议,并从我的索引文件中删除了所有下划线。

【讨论】: