Thông thường, kiến trúc chức năng tìm kiếm bao gồm: khung tìm kiếm + công cụ tìm kiếm (bao gồm phương pháp phân tách từ)
Ở đây, khung tìm kiếm được chọn:
django-haystack: là khung tìm kiếm mã nguồn mở cho Django, khung này hỗ trợ các công cụ tìm kiếm Solr, Elasticsearch, Whoosh, *Xapian*, không cần thay đổi mã, chỉ cần chuyển đổi công cụ, giảm lượng mã.
Công cụ tìm kiếm sử dụng:
Whoosh: Đây là công cụ tìm kiếm đầy đủ văn bản được triển khai hoàn toàn bằng Python, không có tệp nhị phân, khá nhỏ gọn, cấu hình đơn giản, hiệu suất tương đối thấp, nhưng đủ cho các trang web nhỏ.
Phương pháp phân tách từ sử dụng:
Jieba: Vì Whoosh có phân tách từ tiếng Anh, hỗ trợ phân tách từ tiếng Trung không tốt, nên sử dụng jieba để thay thế thành phần phân tách từ của whoosh.
Thông tin môi trường hệ thống: CentOS 7, Python 3.8.2, Django 3.0.3
django-haystack
Cài đặt
pip install django-haystack
Lưu ý, ở đây cài đặt django-haystack, không phải pip install haystack. Lần đầu tiên tôi cài đặt, tôi đã cài thành haystack, sau đó chạy lại pip install django-haystack, điều này có thể gây xung đột và báo lỗi "cannot import name 'connections' from 'haystack'" khi khởi động dự án. Cách giải quyết là gỡ cài đặt cả haystack và django-haystack, sau đó cài đặt lại django-haystack.
Sau khi cài đặt django-haystack, khi khởi động dự án lại báo lỗi khác: "cannot import name 'six' from 'django.utils", nguyên nhân là django 3.x đã xóa six.
Cách giải quyết:
1. Cài đặt six
pip install six
2. Vào thư mục môi trường ảo báo lỗi
cd /home/gong/.conda/envs/dailyfresh/lib/python3.8/site-packages/
Sao chép tệp six.py trong thư mục này vào thư mục django/utils
cp six.py django/utils
3. Chỉnh sửa tệp inputs.py trong thư mục haystack của môi trường ảo
vim /home/gong/.conda/envs/dailyfresh/lib/python3.8/site-packages/haystack/inputs.py
# Thay đổi từ
# from django.utils.encoding import force_text, python_2_unicode_compatible
# thành
from django.utils.encoding import force_text
from django.utils.six import python_2_unicode_compatible
Cấu hình haystack
1. Chỉnh sửa setting.py
Thêm ứng dụng haystack
INSTALLED_APPS = [
...
'haystack',# Khung tìm kiếm đầy đủ văn bản
...
]
Cấu hình công cụ tìm kiếm và tệp chỉ mục
import os
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(BASE_DIR), 'whoosh_index'),
},
}
'ENGINE': Công cụ tìm kiếm sử dụng WhooshEngine trong tệp whoosh_backend.py của đường dẫn môi trường ảo
'PATH': Chỉ đường dẫn đến thư mục chứa tệp chỉ mục được tạo, tức là đường dẫn đến thư mục whoosh_index trong thư mục gốc dự án. Thư mục này không cần tạo thủ công, nó sẽ được tạo tự động khi chạy lệnh tạo tệp chỉ mục
Đặt số lượng kết quả tìm kiếm hiển thị trên mỗi trang, mặc định là 20
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
2. Tạo tệp chỉ mục
Lưu ý rằng đường dẫn và tên tệp sau đây là cố định, trong đó các phần khác không cần thay đổi, chỉ cần thay thế tên model là được
Trong thư mục ứng dụng goods, tạo một tệp search_indexes.py mới, định nghĩa một lớp chỉ mục sản phẩm trong đó.
from haystack import indexes
from goods.models import Goods
class GoodsIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return Goods
def index_queryset(self, using=None):
"""Được sử dụng khi cập nhật toàn bộ chỉ mục cho model."""
return self.get_model().objects.all()
Tạo tệp templates/search/indexes/goods/good_text.txt
{{ object.name }}
{{ object.brief }}
{{ object.goodsspu.detail }}
Tạo tệp chỉ mục:
python manage.py rebuild_index
3. Cấu hình hộp tìm kiếm và url
Chỉnh sửa hộp tìm kiếm, lưu ý method phải là get, action có thể tùy chỉnh đường dẫn gửi biểu mẫu, tên của ô nhập liệu phải là "q"
<div class="search_con fl">
<form method="get" action="{% url 'goods:search' %}">
<input type="text" class="input_text fl" name="q" placeholder="Tìm kiếm sản phẩm">
<input type="submit" class="input_btn fr" name="" value="Tìm kiếm">
</form>
</div>
Chỉnh sửa url, theo địa chỉ action ở trên, chỉnh sửa url tương ứng, ở đây là chỉnh sửa urls.py trong ứng dụng goods.
from django.urls import path
from .views import GoodsSearchView
urlpatterns = [
...
# path('search/', include('haystack.urls')),
path('search/', GoodsSearchView.as_view(), name='search'),
...
]
Lưu ý có hai cách viết:
Cách thứ nhất là include('haystack.urls'), không tùy chỉnh view xử lý, sử dụng trực tiếp context mặc định của haystack trả về cho tệp template, nội dung của content là:
context = {
'query': self.query, # Từ khóa tìm kiếm
'form': self.form,
'page': page, # Đối tượng page của trang hiện tại, duyệt đối tượng page, lấy được là instance của SearchResult, thuộc tính object của đối tượng mới là đối tượng của model.
'paginator': paginator, # Đối tượng phân trang paginator
'suggestion': None,
}
Cách thứ hai là tùy chỉnh view xử lý, GoodsSearchView.as_view(), có thể thêm dữ liệu tùy chỉnh cần thiết cho tệp template
class GoodsSearchView(SearchView):
'''View tìm kiếm sản phẩm'''
def get_context_data(self, *args, **kwargs):
# Kế thừa lấy nội dung định trước
context = super(GoodsSearchView, self).get_context_data(*args, **kwargs)
# Lấy thông tin cơ sở dữ liệu mong muốn
# Lấy tất cả các loại sản phẩm
all_type = GoodsType.objects.all()
# Lấy số lượng giỏ hàng
cart_count = get_cart_count(self.request)
# Thêm context
context['all_type'] = all_type
context['cart_count'] = cart_count
context['page'] = context['page_obj']
print(context)
return context
Lưu ý ở đây thêm một thuộc tính có key là 'page', giá trị của nó là giá trị của key có sẵn là 'page_obj', lý do là khi chúng ta sử dụng cách trả về mặc định ở trên, key của đối tượng page là 'page', trong khi trong view tùy chỉnh, key của đối tượng page là 'page_obj'
Để nhất quán và thường chúng ta đều sử dụng key là 'page' để đại diện cho đối tượng page.
Khởi động dự án để tìm kiếm, phát hiện trang báo lỗi: TemplateDoesNotExist, nguyên nhân là haystack hiển thị kết quả tìm kiếm trong templates/search/search.html, vì vậy cần tạo và chỉnh sửa tệp này
4. Chỉnh sửa search.html
{{ query }} là điều kiện tìm kiếm, {{ page }} và {{ paginator }} có thuộc tính và phương thức giống như đối tượng phân trang của django.
Lưu ý rằng trang phân trang trỏ đến địa chỉ, cần nối ?q={{từ khóa tìm kiếm}}&page={{số trang}}
{% extends 'base_list.html'%}
{% block title %}Tìm kiếm kết quả - Bán hàng tươi sống{% endblock title%}
{% block detail%}
<div class="breadcrumb">
<a href="#">{{ query }}</a>
<span>></span>
<a href="#">Kết quả tìm kiếm</a>
</div>
Trường tìm kiếm: {{ query }}</br>
Đối tượng page:{{ page }}</br>
Đối tượng paginator:{{ paginator }}</br>
<div class="main_wrap clearfix">
<ul class="goods_type_list clearfix">
{% for result in page %}
<li>
result
<a href="{% url 'goods:detail' result.object.id %}"><img src="{{ result.object.image.url }}"></a>
<h4><a href="{% url 'goods:detail' result.object.id %}">{{ result.object.name }}</a></h4>
<div class="operate">
<span class="prize">¥{{ result.object.price }}</span>
<span class="unit">{{ result.object.price }}/{{ result.object.uom }}</span>
<a href="#" class="add_goods" title="Thêm vào giỏ hàng"></a>
</div>
</li>
{% empty %}
Không tìm thấy kết quả
{% endfor %}
</ul>
<div class="pagenation">
{% if page.has_previous %}
<a href="{% url 'goods:search' %}?q={{ query }}&page={{ page.previous_page_number }}"><Trang trước</a>
{% endif %}
{% for num in paginator.page_range %}
<a href="{% url 'goods:search' %}?q={{ query }}&page={{ num }}" {% if num == page.number %}class="active"{% endif %}>{{ num }}</a>
{% endfor %}
{% if page.has_next %}
<a href="{% url 'goods:search' %}?q={{ query }}&page={{ page.next_page_number }}">Trang sau></a>
{% endif %}
</div>
</div>
{% endblock detail %}
jieba module phân tách từ
Vì phương pháp phân tách từ của whoosh gốc không hỗ trợ tiếng Trung tốt, nên sử dụng module jieba có khả năng phân tách từ tiếng Trung tốt hơn
Cài đặt jieba
pip install jieba
Chỉnh sửa tệp engine
Vào thư mục backends trong môi trường ảo
cd /home/gong/.conda/envs/dailyfresh/lib/python3.8/site-packages/haystack/backends/
class ChineseTokenizer(Tokenizer): def call(self, value, positions=False, chars=False, keeporiginal=False, removestops=True, start_pos=0, start_char=0, mode='', **kwargs): t = Token(positions, chars, removestops=removestops, mode=mode, **kwargs) seglist = jieba.cut(value, cut_all=True) for w in seglist: t.original = t.text = w t.boost = 1.0 if positions: t.pos = start_pos + value.find(w) if chars: t.startchar = start_char + value.find(w) t.endchar = start_char + value.find(w) + len(w) yield t
def ChineseAnalyzer(): return ChineseTokenizer()
Sao chép whoosh_backend.py, đặt tên là whoosh_cn_backend.py, chỉnh sửa whoosh_cn_backend.py, nhập lớp phân tích tiếng Trung, sử dụng jieba để phân tách từ bên trong
cp whoosh_backend.py whoosh_cn_backend.py
from .ChineseAnalyzer import ChineseAnalyzer
...
Tìm
analyzer=StemmingAnalyzer()
Thay đổi thành
analyzer=ChineseAnalyzer()
Chỉnh sửa tệp settings.py
---------------
Thay đổi whoosh_backend gốc thành whoosh_cn_backend
Cấu hình haystack+whoosh, khung tìm kiếm đầy đủ văn bản
HAYSTACK_CONNECTIONS = { 'default': { # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), }, }
Đặt số lượng kết quả tìm kiếm hiển thị trên mỗi trang
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
Tạo lại dữ liệu chỉ mục
--------
python manage.py rebuild_index