使用Django + Elasticsearch + Haystack构建燃烧的快速REST API
#python #django #elasticsearch #python3

搜索是您网站中最重要的部分,快速搜索更为重要。虽然用户无法手动找到东西或用完时间,但他使用搜索并进行缓慢的搜索会使您的用户更加沮丧

在此博客中,我们将使用

进行REST API
  1. Django REST Framework
  2. Django Haystack
  3. DRF - Haystack
  4. ElasticSearch 7.

在这里,我们正在为电影应用程序构建API,例如Netflix或任何其他流媒体网站。
如果您直接想检查代码then jump here.

到目前为止,Elasticsearch是搜索文本的最佳搜索引擎,并且非常快。与Django集成也很容易。在搜索应用程序时,您还可以按年度,流派和评级过滤结果。如果您使用
进行相同的功能 默认的Django搜索如使用icontainscontains,然后在我们击中数据库并击中数据库时,将花费大量时间搜索和过滤结果。但是,使用Elasticsearch,它非常快,因为我们没有击中数据库,而是命中弹性搜索数据,这是一个索引数据,因此比常规DB更快地查询。

我不会深入了解,因为可以为本教程中使用的每种技术写几个博客。

在继续前进之前,我认为您有一些基本知识

如果您符合上述要求,那么您就可以了,如果不这样做,让我们继续前进,您将了解在制作这种功能时如何导航。ð

所以让我们开始 -

Image description

为了节省时间,我已经用完整的说明制作了一个git repo。我将通过足够的解释来做一些事情。

1.克隆回购

git clone https://github.com/selftaughtdev-me/movie-search-api.git

2.安装所需的软件包

Mac用户。您需要自己找到此软件包的安装方法。不用担心,因为它们很容易安装。对于Windows,您可以安装WSL2或在Windows本身上安装这些软件包。但是我建议您迟早使用WSL2,如果您正在与Django一起工作,您将需要此功能
sudo apt update -y
# install postgresql as sqlite is not efficient enough to handle millions of records
sudo apt install libpq-dev postgresql postgresql-contrib -y
sudo service postgresql start
# install python3 & build-essential
sudo add-apt-repository ppa:deadsnakes/ppa  # for all python versions
sudo apt update -y
sudo apt-get install apt-transport-https
sudo apt install python3.8 python3.8-dev python3.8-venv build-essential -y
# install java as it is required for elasticsearch
sudo apt install openjdk-11-jdk openjdk-11-jre -y
# install ElasticSearch
curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list
sudo apt update
sudo apt install elasticsearch -y
sudo service elasticsearch start
sudo service elasticsearch status

上面,我们将弹性搜索与Java一起安装,这是运行此操作所需的。虽然你们可能主要使用Postgres或MySQL等关系数据库,该数据库将数据存储在表单中。弹性搜索是一个开源NOSQL数据库,可将数据存储在JSON格式中。此外,如果您将搜索时间与其他数据库进行了比较,则数据索引非常快。

弹性搜索是开源的,但是有一些服务
您可能需要像您在使用付费服务时为Mongodb付款一样付款。现在。我们正在使用Postgres存储电影数据和弹性搜索以存储电影数据的副本,但是这些数据将如上所述。

3.创建一个数据库ðIS

sudo -u postgres psql
CREATE DATABASE django_flix;
CREATE USER django_flix_user WITH PASSWORD 'html_programmer';
ALTER ROLE django_flix_user SET client_encoding TO 'utf8';
ALTER ROLE django_flix_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE django_flix_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE django_flix TO django_flix_user;
\q

3.安装要求和迁移

# inside project root directory
python3.8 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install wheel
pip install -r requirements.txt
# migrate
./manage.py migrate
./manage.py createsuperuser

等等..您是否将目录更改为Project文件夹。如果没有,请做。

现在,我们需要生成大量数据来测试我们的API。在您的终端中,键入

./manage.py generate_test_data 1000000

ð!此命令将花费一段时间并用1M随机电影填充数据库

Image description

  • ðâ¡为了节省时间,我只尝试了600k记录的API,这花费了很多时间。因此,我建议只尝试5000记录并在不同的终端窗口中运行此命令,以并行生成数据。

5.生成数据后,向下端到端点以下

./manage.py runserver
http://localhost:8000/api/?q=t
http://localhost:8000/api/search/?q=t&facets=year:1983
http://localhost:8000/api/search/?q=t&facets=year:1983,genre:rise

ð当您在生产中不使用它时,您可以忽略此Elasticsearch警告

warning.png

ð生成的数据是不现实的。它仅出于演示目的。但是,正如您在右侧的调试面板中看到的那样,SQL计数为0,这意味着它没有击中数据库。它直接击中Elasticsearch

Screenshot 2022-10-09 084412.png

现在,在您测试了API之后,让我们看看它的工作原理。打开settings.py
Django Haystack是一个DJANGO软件包,提供了SearchQuerySet和许多其他API,您可以使用它们有效地与Elasticsearch进行通信并搜索您的数据。

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django_extensions",
    "rest_framework",
    "haystack",  # haystack app needs to be put above all app
    "apps.core",
    "debug_toolbar",
]
# Haystack settings
HAYSTACK_CONNECTIONS = {
    "default": {
        "ENGINE": "haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine",
        "URL": "http://127.0.0.1:9200/",
        "INDEX_NAME": "django_flix",
    },
}
# auto index to elastic search when new data is created or when data is saved in the database
HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor"

之后,前往core/search_indexes.py。该文件包含像映射一样。就像您想如何将SQL DB的数据索引到Elasticsearch。

这是结构化的方式 -

from haystack import indexes
from .models import Movie


class MovieIndex(indexes.SearchIndex, indexes.Indexable):
    id = indexes.IntegerField(model_attr="id")
    text = indexes.CharField(document=True, use_template=True)
    title = indexes.CharField(model_attr="title")
    description = indexes.CharField(model_attr="description")
    year = indexes.IntegerField(model_attr="year")
    rating = indexes.FloatField(model_attr="rating")
    global_ranking = indexes.IntegerField(model_attr="global_ranking")
    length = indexes.CharField(model_attr="length", faceted=True)
    revenue = indexes.FloatField(model_attr="revenue", faceted=True)
    genre = indexes.CharField(model_attr="genre", faceted=True)
    country = indexes.CharField(model_attr="country", faceted=True)
    director = indexes.CharField(model_attr="director", faceted=True)

    def get_model(self):
        return Movie

    def prepare_director(self, obj):
        return obj.director.name

    def prepare_genre(self, obj):
        return obj.genre.name

    def prepare_country(self, obj):
        return obj.country.name

    def index_queryset(self, using=None):
        return self.get_model().objects.all()

这里要注意的一些事情 -

Facet->转到亚马逊,当您搜索任何产品时,您会在左侧看到一些过滤器。这些过滤器实际上是方面字段。

faceted=True->无论您在此Kwarg中输入哪个字段,该字段都可以用于过滤目的。因此,在搜索时,您可以这样搜索ð

model_attr->模型字段,您指的是地图。

base_api_url.com/?q=some_query&facets=year:1983,genre:rise

现在转到core/views.py和您看到的第一个视图,负责渲染此数据。

class SearchViewElk(APIView, LimitOffsetPagination):

    default_limit = 10
    serializer_class = MovieHayStackSerializer

    def get(self, request):

        # get the query params
        query = request.GET.get("q", None)
        highlight = request.GET.get("highlight", None)
        facets = request.GET.get("facets", None)

        # prepare a initial elk SearchQuerySet from Movie Model
        sqs = SearchQuerySet().models(Movie)

        if query:
            query_list = query.split(" ")  # split the query string
            qs_item = reduce(
                operator.and_, (Q(text__contains=item) for item in query_list)
            )  # filter by every item in query_list - ( using OR filter)
            sqs = sqs.filter(qs_item)

            if highlight:
                # if any value is passed to highlight then highlight the query
                sqs = sqs.highlight()

        if facets:
            sqs = self.filter_sqs_by_facets(sqs, facets)

        page = self.paginate_queryset(sqs, request, view=self)
        movie_serializer = self.serializer_class(page, many=True)
        facets = self.get_facet_fields(sqs)
        summary = self.prepare_summary(sqs)
        data = {"movies": movie_serializer.data, "facets": facets, "summary": summary}
        return Response(data, status=HTTP_200_OK)

    def filter_sqs_by_facets(self, sqs, facets):
        facet_list = facets.split(",")
        for facet in facet_list:
            facet_key, facet_value = facet.split(":")
            # narrow down the results by facet
            sqs = sqs.narrow(f"{facet_key}:{facet_value}")
        return sqs

    def get_facet_fields(self, sqs):
        # return all the possible facet fields from given SQS
        facet_fields = (
            sqs.facet("year")
            .facet("rating")
            .facet("global_ranking")
            .facet("length")
            .facet("revenue")
            .facet("country")
            .facet("genre")
        )
        return facet_fields.facet_counts()

    def prepare_summary(self, sqs):
        # return the summary of the search results
        summary = {
            "total": sqs.count(),
            "next_page": self.get_next_link(),
            "previous_page": self.get_previous_link(),
        }
        return summary

即使我提到了适当的评论,但是您有一些新的术语。

SearchQuerySet()->搜索类别是SearchQuerySet的抽象和SearchBackend的实际搜索之间的中介。鉴于SearchQuerySet提供的元数据,SearchQuery构建了实际查询,并代表SearchQuerySet上的搜索背景进行交互。任何SearchQuerySet obj都与django Queryset对象大致相同。因此,这意味着您可以使用过滤器和其他操作,例如使用Django Model QuerySet。

sqs.highlight()->这将使结果包含JSON响应中的highlighted键,并且突出显示的文本将包含在<em>标签中。您还可以将其自定义为自定义类,然后在给定类的前面进行样式。

sqs.facet('some_facetable_field')->获取所有可能的方面,例如如果您在year字段上也有faceted=true,则在search_indexes.py中也有faceted=true。您将获得一个包含信息的对象,例如每年创作了多少电影。

.facet_counts()->由于sqs.facet返回一个对象,我们需要从此对象中获取值,并且该属性完成了该作业。

还有一个序列化器将Haystack的SearchQuerySet转换为Json。

# 3rd party imports
from drf_haystack.serializers import HaystackSerializer

# local imports
from .search_indexes import MovieIndex


class MovieHayStackSerializer(HaystackSerializer):
    class Meta:
        # The `index_classes` attribute is a list of which search indexes
        # we want to include in the search.
        index_classes = [MovieIndex]

        # The `fields` contains all the fields we want to include.
        # NOTE: Make sure you don't confuse these with model attributes. These
        # fields belong to the search index!
        fields = [
            "title",
            "description",
            "year",
            "rating",
            "global_ranking",
            "length",
            "revenue",
            "genre",
            "country",
            "director",
        ]

Image description

现在您已经准备好了API。您可以执行其他自定义,例如AutoQuery,它允许像Google一样搜索。这意味着,如果您在任何查询的前面添加-,则将排除匹配查询的结果。而且还有其他太多的自定义选项可以在documentation上进行检查。

这是我的第一个博客,我知道这不是可以理解的,但是我会尝试改进。 ð

有用的链接

免责声明 - 我不是专家,仍在学习。因此,可能有一些我可能会错过或可能被错误解释的事情。当我收到有关错误或在某个地方发现的任何评论时,我将在博客中对其进行更正。