搜索

查询结果集

Wagtail 搜索基于 Django 的 QuerySet API 。在开发过程中可以对加入查询索引的字段采用类似于 Django 模型及字段过滤的操作。

搜索页面

Wagtail 提供页面搜索的快捷方法:.search()QuerySet。这两个方法可以任何 PageQuerySet 对象上使用。例如:

# Search future EventPages 在未来事件页中搜索
>>> from wagtail.core.models import EventPage
>>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")

PageQuerySet 其它方法也可以结合 search() 使用。例如:

# Search all live EventPages that are under the events index 在事件索引页下属页面中搜索
>>> EventPage.objects.live().descendant_of(events_index).search("Event")
[<EventPage: Event 1>, <EventPage: Event 2>]

注解

search() 方法将原来 QuerySet 转换成 Wagtail 的 SearchResults 类实例 (依赖于底层实现平台)。这意味着过滤操作必须在 search() 执行之前。

搜索图片、文档及定制的模型

Wagtail 的文档和图片各自模型 QuerySets 中定义了 search,和页面类似:

>>> from wagtail.images.models import Image

>>> Image.objects.filter(uploaded_by_user=user).search("Hello")
[<Image: Hello>, <Image: Hello world!>]

自定义模型 可以通过底层平台 search 方法来调用:

>>> from myapp.models import Book
>>> from wagtail.search.backends import get_search_backend

# Search books
>>> s = get_search_backend()
>>> s.search("Great", Book)
[<Book: Great Expectations>, <Book: The Great Gatsby>]

调用 search 方法时可以传递一个 QuerySet 参数对搜索结果进行过滤:

>>> from myapp.models import Book
>>> from wagtail.search.backends import get_search_backend

# Search books
>>> s = get_search_backend()
>>> s.search("Great", Book.objects.filter(published_date__year__lt=1900))
[<Book: Great Expectations>]

只搜索特定字段

缺省情况下,搜索会匹配 index.SearchField 中定义的所有字段。

如果只想对其中个别字段进行搜索,可使用 fields 参数定义所要搜索的字段集合:

# Search just the title field
>>> EventPage.objects.search("Event", fields=["title"])
[<EventPage: Event 1>, <EventPage: Event 2>]

改变搜索方式

查询操作符

查询操作符定义多个术语查找过程中的搜索方式。有两个取值:

  • “or”(或) - 查询结果至少匹配所提供术语中的一个 (这是 Elasticsearch 搜索的缺省方式)

  • “and” (与)- 查询结果要匹配所有提供术语 (这是数据库搜索的缺省方式)

这两种方式有各自的优缺点。 “or” 操作会返回尽可能多的集合或忽略了所有术语间的相关性。而 “and” 操作只返回严格匹配所有术语的记录,会此要求太严格而返回过少的记录集。

我们推荐在需要按相关性进行排序时使用 “or” 操作符。而按其它形式排序时使用 “and” 操作符(注意:底层平台为数据库时不支持按相关性进行排序)。

下面是一个使用 operator 关键字参数的例子:

# The database contains a "Thing" model with the following items:
# - Hello world
# - Hello
# - World


# Search with the "or" operator
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="or")

# All records returned as they all contain either "hello" or "world"
[<Thing: Hello World>, <Thing: Hello>, <Thing: World>]


# Search with the "and" operator
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="and")

# Only "hello world" returned as that's the only item that contains both terms
[<Thing: Hello world>]

对于页面、图片和文档模型,在 QuerySet’s search 方法中也支持 operator 参数:

>>> Page.objects.search("Hello world", operator="or")

# All pages containing either "hello" or "world" are returned
[<Page: Hello World>, <Page: Hello>, <Page: World>]

词组搜索(Phrase)

词组或短语搜索是将查找的术语集当作词组来查找。这样搜索出的结果应符合这些术语出现的顺序。

例如:

>>> from wagtail.search.query import Phrase

>>> Page.objects.search(Phrase("Hello world"))
[<Page: Hello World>]

>>> Page.objects.search(Phrase("World hello"))
[<Page: World Hello day>]

要了解更多使用术语双引号语法进行词组搜索的方法,请参考 查询字符串的解析

搜索中复杂查询

通过使用查询类,Wagtail 还支持构造复杂 Python 对象查询方法。参考下面例子:

PlainText(query_string, operator=None, boost=1.0)

这个类将查询条件字符串分隔成几个术语。这是没使用查询类的缺省参数形式。

查询类包含查询字符串、分隔符及提升权重值(boost).

例如:

>>> from wagtail.search.query import PlainText
>>> Page.objects.search(PlainText("Hello world"))

# Multiple plain text queries can be combined. This example will match both "hello world" and "Hello earth"
>>> Page.objects.search(PlainText("Hello") & (PlainText("world") | PlainText("earth")))

Phrase(query_string)

这个类将字符串转换成词组。参考上一节的描述。

例如:

# This example will match both the phrases "hello world" and "Hello earth"
>>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))

Boost(query, boost)

这个类提供查询的权重值。

例如:

>>> from wagtail.search.query import PlainText, Boost

# This example will match both the phrases "hello world" and "Hello earth" but matches for "hello world" will be ranked higher
>>> Page.objects.search(Boost(Phrase("Hello world"), 10.0) | Phrase("Hello earth"))

注意,底层平台使用 PostgreSQL 或数据库时不支持。

查询字符串的解析

前面章节展示了程度中构造词组查询的方式,许多搜索引擎 (包括 Wagtail 台管理界面) 支持双引号让最终用户定义短语词组的搜索方式。 除了词组搜索的这种形式之外,还允许用户使用冒号形式的增加过滤条件。

这两个查询字符串的解析方法在实现时可通过调用 parse_query_string 工具函数实现,函数使用用户输入字符串做为参数,返回查询对象和过滤器的字典:

例如:

>>> from wagtail.search.utils import parse_query_string
>>> filters, query = parse_query_string('my query string "this is a phrase" this-is-a:filter', operator='and')

>>> filters
{
    'this-is-a': 'filter',
}

>>> query
And([
    PlainText("my query string", operator='and'),
    Phrase("this is a phrase"),
])

下面是在搜索视图中使用此函数的示例:

from wagtail.search.utils import parse_query_string

def search(request):
    query_string = request.GET['query']

    # Parse query
    filters, query = parse_query_string(query_string, operator='and')

    # Published filter
    # An example filter that accepts either `published:yes` or `published:no` and filters the pages accordingly
    published_filter = filters.get('published')
    published_filter = published_filter and published_filter.lower()
    if published_filter in ['yes', 'true']:
        pages = pages.filter(live=True)
    elif published_filter in ['no', 'false']:
        pages = pages.filter(live=False)

    # Search
    pages = pages.search(query)

    return render(request, 'search_results.html', {'pages': pages})

设置顺序

缺省情况下,如果底层平台支持查询结果将按相关性的顺序进行排序。 如果不使用这种排序方法,需要将 search() 方法的 order_by_relevance 关键字参数设为 False

例如:

# Get a list of events ordered by date
>>> EventPage.objects.order_by('date').search("Event", order_by_relevance=False)

# Events ordered by date
[<EventPage: Easter>, <EventPage: Halloween>, <EventPage: Christmas>]

使用分数标注查询结果

对于每个匹配到的查询结果,Elasticsearch 计算一个 “分值”,这个数值代表一个查询结果记录与用户查询相关度。查询结果就是这个分值进行排序的。

在一个情况下,程序中访问这个分值是有意义的(例如程序中同时搜索两个不同的模型)。使用 SearchQuerySet.annotate_score(field) 方法可以给每个搜索结果增加分值。

例如:

>>> events = EventPage.objects.search("Event").annotate_score("_score")
>>> for event in events:
...    print(event.title, event._score)
...
("Easter", 2.5),
("Haloween", 1.7),
("Christmas", 1.5),

注意,分值是在查询过程中判定出来的,只有相同的查询,分值的大小才有意义。

页面搜索视图示例

下面是一个 Django 视图可用来在网站上增加”搜索”页面:

# views.py

from django.shortcuts import render

from wagtail.core.models import Page
from wagtail.search.models import Query


def search(request):
    # Search
    search_query = request.GET.get('query', None)
    if search_query:
        search_results = Page.objects.live().search(search_query)

        # Log the query so Wagtail can suggest promoted results
        Query.get(search_query).add_hit()
    else:
        search_results = Page.objects.none()

    # Render template
    return render(request, 'search_results.html', {
        'search_query': search_query,
        'search_results': search_results,
    })

下面是相关的模板:

{% extends "base.html" %}
{% load wagtailcore_tags %}

{% block title %}Search{% endblock %}

{% block content %}
    <form action="{% url 'search' %}" method="get">
        <input type="text" name="query" value="{{ search_query }}">
        <input type="submit" value="Search">
    </form>

    {% if search_results %}
        <ul>
            {% for result in search_results %}
                <li>
                    <h4><a href="{% pageurl result %}">{{ result }}</a></h4>
                    {% if result.search_description %}
                        {{ result.search_description|safe }}
                    {% endif %}
                </li>
            {% endfor %}
        </ul>
    {% elif search_query %}
        No results found
    {% else %}
        Please type something into the search box
    {% endif %}
{% endblock %}

提供查询结果

“提供查询结果” 目的是让编辑增加一些有助于搜索精确度的内容。这些功能可参考 search_promotions 模块说明。