【django】京東等大型網站的混合搜索是怎麼實現的?

混合搜索在各大網站如京東、淘寶都有應用,他們的原理都是什麼呢?本博文將爲你介紹它們的實現過程。css

混合搜索的原理,用一句話來講就是:關鍵字id進行拼接。html

混合搜索示例:

數據庫設計:

視頻方向:前端

class Direction(models.Model):
    weight = models.IntegerField(verbose_name='權重(按從大到小排列)', default=0)
    name = models.CharField(verbose_name='名稱', max_length=32)

    classification = models.ManyToManyField('Classification')

    class Meta:
        db_table = 'Direction'
        verbose_name_plural = u'方向(視頻方向)'

    def __str__(self):
        return self.name

 視頻分類:python

class Classification(models.Model):
    weight = models.IntegerField(verbose_name='權重(按從大到小排列)', default=0)
    name = models.CharField(verbose_name='名稱', max_length=32)

    class Meta:
        db_table = 'Classification'
        verbose_name_plural = u'分類(視頻分類)'

    def __str__(self):
        return self.name

  視頻:數據庫

class Video(models.Model):

    status_choice = (
        (0, u'下線'),
        (1, u'上線'),
    )
    level_choice = (
        (1, u'初級'),
        (2, u'中級'),
        (3, u'高級'),
    )
    status = models.IntegerField(verbose_name='狀態', choices=status_choice, default=1)#可用於管理員的審覈
    level = models.IntegerField(verbose_name='級別', choices=level_choice, default=1)
    classification = models.ForeignKey('Classification', null=True, blank=True)

    weight = models.IntegerField(verbose_name='權重(按從大到小排列)', default=0)

    title = models.CharField(verbose_name='標題', max_length=32)
    summary = models.CharField(verbose_name='簡介', max_length=32)
    img = models.ImageField(verbose_name='圖片', upload_to='./static/images/Video/')
    href = models.CharField(verbose_name='視頻地址', max_length=256)

    create_date = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'Video'
        verbose_name_plural = u'視頻'

    def __str__(self):
        return self.title

  備註:django

  • 視頻方向Direction類和視頻分類Classification多對多關係,即一個視頻方向對應多個視頻分類,一個視頻分類也能夠對應多個視頻方向。——classification = models.ManyToManyField('Classification')
  • 視頻分類Classification類和視頻Video類是一對多關係,即一個視頻分類對應多個視頻
  • 視頻Video類中level_choice 與視頻也是一對多關係,這裏爲了簡化表關係,直接使用choices=level_choice來代替

混合搜索url設計:

默認url:後端

 

http://127.0.0.1:8000/video-0-0-0.html,其中第一個數字表明視頻方向,默認0表明所有方向;第二個數字表明視頻分類,默認0表明所有分類;第三個數字表明視頻等級,默認0表明所有等級。安全

每個a標籤默認的url:app

  例如運維自動化:<a href="/video-1-0-0.html">運維自動化</a>,即視頻方向的對應數字爲1,視頻分類和視頻等級都爲0,這樣作的目的是爲了將此url和用戶當前url進行拼接,並跳轉到新的url。運維

  選擇運維自動化後的url:

  多選狀況下的url:

前端html:

加載自定義simple_tag:

{% load xx %}

  注:

  • xx:名爲xx的py文件,裏面包含自定義函數,方便html中進行調用
  • 在app中建立templatetags文件夾,將xx.py文件放在templatetags文件夾下

關於自定義simple_tag的更多信息,詳見下文。

css:

<style>
        a{
            display: inline-block;
            padding: 8px;
        }
        .active{
            background-color: coral;
            color: white;
        }
        .item{
            display: inline-block;
            width: 300px;
            height: 400px;
        }
        .item img{
            border: 0;
            width: 300px;
            height: 280px;
            overflow: hidden;
        }
    </style>
css代碼

設置css目的,當用戶選擇視頻方向、視頻分類、視頻等級時,加深對應a標籤。

選擇區域html:

    <h3>選擇:</h3>
    <div>
        {% action_all current_url 1 %} :
        {% for item in direction_list %}

             {% action current_url item 1 %}
        {% endfor %}
    </div>
    <div>
        {% action_all current_url 2 %} :
        {% for item in class_list %}

            {% action current_url item 2 %}
        {% endfor %}
    </div>
    <div>
        {% action_all current_url 3 %} :
        {% for item in level_list %}
            {% action current_url item 3 %}
        {% endfor %}
    </div>
    <hr />

  該區域所有基於自定義simple_tag實現,詳見下文。

視頻顯示區域html:

  <h3>視頻:</h3>
    <hr />

    {% for item in video_list %}
        <a class="item" href="{{ item.href }}">
            <img src="/{{ item.img }}">
            <p>{{ item.title }}</p>
            <p>{{ item.summary }}</p>
        </a>
    {% endfor %}

  循環顯示符合條件的所有視頻。

自定義simple_tag:

所有標籤的生成:

咱們但願,當用戶選擇所有標籤時,url對應位置爲0,即當用戶三個選擇都是所有時,url爲:/video-0-0-0.html

以視頻方向爲例介紹:

對應位置html:

{% action_all current_url 1 %} :

  從上述html可看出,action_all爲對應的函數,它接收兩個參數:當前url(current_url)、當前位置(視頻方向、視頻分類、視頻等級)。其中current_url是後臺傳入html的,詳見下文後臺views函數介紹。

action_all函數:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template  
from django.utils.safestring import mark_safe

@register.simple_tag  #註冊simple_tag
def action_all(current_url,index):   #接收當前url和對應的位置參數
    """
    獲取當前url,video-1-1-2.html
    :param current_url:
    :param item:
    :return:
    """
    url_part_list = current_url.split('-')   #根據「-」進行分割
    if index == 3:  #若是是視頻等級
        if url_part_list[index] == "0.html":  #若是選擇的是所有
            temp = "<a href='%s' class='active'>所有</a>"   #添加 「active」屬性
        else:
            temp = "<a href='%s'>所有</a>"

        url_part_list[index] = "0.html"
    else:
        if url_part_list[index] == "0":  
            temp = "<a href='%s' class='active'>所有</a>"
        else:
            temp = "<a href='%s'>所有</a>"

        url_part_list[index] = "0"


    href = '-'.join(url_part_list)  #處理後的列表再拼接成url字符串

    temp = temp % (href,)  #生成對應的a標籤
    return mark_safe(temp)  #返回原生html

 其它a標籤:

  以視頻方向爲例介紹:

 對應位置html:

{% action current_url item 1 %}

從上述html可看出:action函數接收三個參數 當前url、當前標籤對象、當前位置。

  action函數:

@register.simple_tag
def action(current_url, item,index):
    # videos-0-0-1.html
    # item: id name
    # video-   2   -0-0.html
    url_part_list = current_url.split('-')

    if index == 3:
        if str(item['id']) == url_part_list[3].split('.')[0]:  #若是當前標籤被選中
             temp = "<a href='%s' class='active'>%s</a>"
        else:
            temp = "<a href='%s'>%s</a>"

        url_part_list[index] = str(item['id']) + '.html' #拼接對應位置的部分url
    else:
        if str(item['id']) == url_part_list[index]:
            temp = "<a href='%s' class='active'>%s</a>"
        else:
            temp = "<a href='%s'>%s</a>"

        url_part_list[index] = str(item['id'])

    ur_str = '-'.join(url_part_list)  #拼接總體url
    temp = temp %(ur_str, item['name']) #生成對應的a標籤
    return mark_safe(temp)  #返回安全的html

  至此,全部選擇標籤生成完畢,可以根據用戶選擇動態生成url。

視頻顯示區域的先後端處理:

前端html:

  {% for item in video_list %}
        <a class="item" href="{{ item.href }}">
            <img src="/{{ item.img }}">
            <p>{{ item.title }}</p>
            <p>{{ item.summary }}</p>
        </a>
    {% endfor %}

  循環顯示全部符合條件的視頻。

後端views函數:

def video(request,*args,**kwargs):
    print(kwargs)
    print(request.path)
    request_path = request.path  #當前請求的路徑

    q = {}  #從數據庫獲取視頻時的filter條件字典
    q['status'] = 1 #狀態爲審覈經過的

    class_id = int(kwargs.get('classification_id')) #獲取url中的視頻分類id

    direction_list = models.Direction.objects.all().values('id','name') #從數據庫中獲取全部的視頻方向(包括視頻方向的id和name)

    if kwargs.get('direction_id') == '0': 
        # 方向選擇所有
        print('方向等於0')
        class_list = models.Classification.objects.all().values('id', 'name') #方向id=0,即獲取全部的視頻分類(包括視頻分類的id和name)
        if kwargs.get('classification_id') == '0': #若是視頻分類id也爲0,即所有分類
            pass
        else:
            # 若是視頻分類不是所有,過濾條件爲視頻分類id in [url中的視頻分類id]
            q['classification_id__in'] = [class_id,] 

    else:
        print('方向不爲0')
        # 方向選擇某一個方向,
        # 若是分類是0
        if kwargs.get('classification_id') == '0':
            print('分類爲0')
            obj = models.Direction.objects.get(id=int(kwargs.get('direction_id')))  #獲取已選擇的視頻方向
            class_list = obj.classification.all().values('id', 'name')  #獲取該方向的全部視頻分類
            id_list = list(map(lambda x: x['id'], class_list)) #獲取全部視頻分類對應的視頻分類id

            q['classification_id__in'] = id_list #過濾條件爲視頻分類id in [該方向下的全部視頻分類id]
        else:
#方向不爲0,分類也不爲0
            obj = models.Direction.objects.get(id=int(kwargs.get('direction_id')))
            class_list = obj.classification.all().values('id', 'name')
            id_list = list(map(lambda x:x['id'], class_list))
            q['classification_id__in'] = [class_id,] #過濾條件爲視頻分類id in [已經選擇的視頻分類id]
            print('分類不爲0')
            # 當前分類若是在獲取的全部分類中,則方向下的全部相關分類顯示
            # 當前分類若是不在獲取的全部分類中,
            if int(kwargs.get('classification_id')) in id_list:
                pass
            else:
                print('再也不,獲取指定方向下的全部分類:選中的回到所有')
                url_part_list = request_path.split('-')
                url_part_list[2] = '0'
                request_path = '-'.join(url_part_list)

    level_id = int(kwargs.get('level_id')) #視頻等級id
    if level_id == 0:
        pass
    else:
        q['level'] = level_id #過濾條件增長視頻等級

    # models.Video.objects.filter(status=1)
    video_list = models.Video.objects.filter(**q).values('title','summary', 'img', 'href')


    # level_list = models.Video.level_choice

    ret = map(lambda x:{"id": x[0], 'name': x[1]}, models.Video.level_choice)#把視頻等級轉化爲單個標籤是字典格式,總體是列表格式
    level_list = list(ret)
    return render(request, 'video.html', {'direction_list': direction_list,
                                          'class_list': class_list,
                                          'level_list': level_list,
                                          'current_url': request_path,
                                          "video_list": video_list})

  總結:以上就是混合搜索的先後端全過程,歡迎讀者來與樓主進行交流。若是本文對您有參考價值,歡迎幫博主點下文章下方的推薦,謝謝!

相關文章
相關標籤/搜索