.. _wagtailsearch_searching:
=========
搜索
=========
.. _wagtailsearch_searching_pages:
查询结果集
===================
Wagtail 搜索基于 Django 的 `QuerySet API `_ 。在开发过程中可以对加入查询索引的字段采用类似于
Django 模型及字段过滤的操作。
搜索页面
---------------
Wagtail 提供页面搜索的快捷方法:``.search()`` 和 ``QuerySet``。这两个方法可以任何 ``PageQuerySet`` 对象上使用。例如:
.. code-block:: python
# Search future EventPages 在未来事件页中搜索
>>> from wagtail.core.models import EventPage
>>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")
``PageQuerySet`` 其它方法也可以结合 ``search()`` 使用。例如:
.. code-block:: python
# Search all live EventPages that are under the events index 在事件索引页下属页面中搜索
>>> EventPage.objects.live().descendant_of(events_index).search("Event")
[, ]
.. note::
``search()`` 方法将原来 ``QuerySet`` 转换成 Wagtail 的 ``SearchResults`` 类实例 (依赖于底层实现平台)。这意味着过滤操作必须在 ``search()`` 执行之前。
.. _wagtailsearch_images_documents_custom_models:
搜索图片、文档及定制的模型
---------------------------------------------
Wagtail 的文档和图片各自模型 QuerySets 中定义了 ``search``,和页面类似:
.. code-block:: python
>>> from wagtail.images.models import Image
>>> Image.objects.filter(uploaded_by_user=user).search("Hello")
[, ]
:ref:`自定义模型 ` 可以通过底层平台 ``search`` 方法来调用:
.. code-block:: python
>>> from myapp.models import Book
>>> from wagtail.search.backends import get_search_backend
# Search books
>>> s = get_search_backend()
>>> s.search("Great", Book)
[, ]
调用 ``search`` 方法时可以传递一个 QuerySet 参数对搜索结果进行过滤:
.. code-block:: python
>>> 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))
[]
.. _wagtailsearch_specifying_fields:
只搜索特定字段
-------------------------------
缺省情况下,搜索会匹配 ``index.SearchField`` 中定义的所有字段。
如果只想对其中个别字段进行搜索,可使用 ``fields`` 参数定义所要搜索的字段集合:
.. code-block:: python
# Search just the title field
>>> EventPage.objects.search("Event", fields=["title"])
[, ]
.. _wagtailsearch_faceted_search:
分面检索(Faceted search)
--------------------------
Wagtail 支持分面检索,分面检索基于可分类字段(如分类或页面类型)。
``.facet(field_name)`` 方法返加一个 ``OrderedDict`` 对象。键是给定字段相关联对象的 ID,值是每个 ID 引用的次数。结果按引用次数降序排序。
例如,按页面类型查看搜索结果中每种类型有多少页面:
.. code-block:: python
>>> Page.objects.search("Test").facet("content_type_id")
# Note: The keys correspond to the ID of a ContentType object; the values are the
# number of pages returned for that type
OrderedDict([
('2', 4), # 4 pages have content_type_id == 2
('1', 2), # 2 pages have content_type_id == 1
])
改变搜索方式
-------------------------
查询操作符
^^^^^^^^^^^^^^^
查询操作符定义多个术语查找过程中的搜索方式。有两个取值:
- "or"(或) - 查询结果至少匹配所提供术语中的一个 (这是 Elasticsearch 搜索的缺省方式)
- "and" (与)- 查询结果要匹配所有提供术语 (这是数据库搜索的缺省方式)
这两种方式有各自的优缺点。 "or" 操作会返回尽可能多的集合或忽略了所有术语间的相关性。而 "and" 操作只返回严格匹配所有术语的记录,会此要求太严格而返回过少的记录集。
我们推荐在需要按相关性进行排序时使用 "or" 操作符。而按其它形式排序时使用 "and" 操作符(注意:底层平台为数据库时不支持按相关性进行排序)。
下面是一个使用 ``operator`` 关键字参数的例子:
.. code-block:: python
# 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"
[, , ]
# 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
[]
对于页面、图片和文档模型,在 QuerySet's ``search`` 方法中也支持 ``operator`` 参数:
.. code-block:: python
>>> Page.objects.search("Hello world", operator="or")
# All pages containing either "hello" or "world" are returned
[, , ]
词组搜索(Phrase)
^^^^^^^^^^^^^^^^^^^^
词组或短语搜索是将查找的术语集当作词组来查找。这样搜索出的结果应符合这些术语出现的顺序。
例如:
.. code-block:: python
>>> from wagtail.search.query import Phrase
>>> Page.objects.search(Phrase("Hello world"))
[]
>>> Page.objects.search(Phrase("World hello"))
[]
要了解更多使用术语双引号语法进行词组搜索的方法,请参考 :ref:`wagtailsearch_query_string_parsing`。
.. _wagtailsearch_complex_queries:
搜索中复杂查询
^^^^^^^^^^^^^^^^^^^^^^
通过使用查询类,Wagtail 还支持构造复杂 Python 对象查询方法。参考下面例子:
``PlainText(query_string, operator=None, boost=1.0)``
这个类将查询条件字符串分隔成几个术语。这是没使用查询类的缺省参数形式。
查询类包含查询字符串、分隔符及提升权重值(boost).
例如:
.. code-block:: python
>>> 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)``
这个类将字符串转换成词组。参考上一节的描述。
例如:
.. code-block:: python
# This example will match both the phrases "hello world" and "Hello earth"
>>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))
``Boost(query, boost)``
这个类提供查询的权重值。
例如:
.. code-block:: python
>>> 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 或数据库时不支持。
.. _wagtailsearch_query_string_parsing:
查询字符串的解析
^^^^^^^^^^^^^^^^^^^^
前面章节展示了程度中构造词组查询的方式,许多搜索引擎 (包括 Wagtail 台管理界面) 支持双引号让最终用户定义短语词组的搜索方式。
除了词组搜索的这种形式之外,还允许用户使用冒号形式的增加过滤条件。
这两个查询字符串的解析方法在实现时可通过调用 ``parse_query_string`` 工具函数实现,函数使用用户输入字符串做为参数,返回查询对象和过滤器的字典:
例如:
.. code-block:: python
>>> 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"),
])
下面是在搜索视图中使用此函数的示例:
.. code-block:: python
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``。
例如:
.. code-block:: python
# Get a list of events ordered by date
>>> EventPage.objects.order_by('date').search("Event", order_by_relevance=False)
# Events ordered by date
[, , ]
.. _wagtailsearch_annotating_results_with_score:
使用分数标注查询结果
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
对于每个匹配到的查询结果,Elasticsearch 计算一个 "分值",这个数值代表一个查询结果记录与用户查询相关度。查询结果就是这个分值进行排序的。
在一个情况下,程序中访问这个分值是有意义的(例如程序中同时搜索两个不同的模型)。使用 ``SearchQuerySet`` 的 ``.annotate_score(field)``
方法可以给每个搜索结果增加分值。
例如:
.. code-block:: python
>>> 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),
注意,分值是在查询过程中判定出来的,只有相同的查询,分值的大小才有意义。
.. _wagtailsearch_frontend_views:
页面搜索视图示例
===========================
下面是一个 Django 视图可用来在网站上增加"搜索"页面:
.. code-block:: python
# 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,
})
下面是相关的模板:
.. code-block:: html
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block title %}Search{% endblock %}
{% block content %}
{% if search_results %}
{% for result in search_results %}
-
{% if result.search_description %}
{{ result.search_description|safe }}
{% endif %}
{% endfor %}
{% elif search_query %}
No results found
{% else %}
Please type something into the search box
{% endif %}
{% endblock %}
提供查询结果
=======================
"提供查询结果" 目的是让编辑增加一些有助于搜索精确度的内容。这些功能可参考 :mod:`~wagtail.contrib.search_promotions` 模块说明。