如何在Django中实现全文搜索?

时间:2010-03-17 10:09:45

标签: python django django-queryset full-text-search

我想在django博客应用程序中实现搜索功能。现状是我有一个用户提供的字符串列表,每个字符串缩小了查询集,只包括那些与字符串匹配的对象。

请参阅:

if request.method == "POST":
    form = SearchForm(request.POST)
    if form.is_valid():
        posts = Post.objects.all()
        for string in form.cleaned_data['query'].split():
            posts = posts.filter(
                    Q(title__icontains=string) | 
                    Q(text__icontains=string) |
                    Q(tags__name__exact=string)
                    )
        return archive_index(request, queryset=posts, date_field='date')

现在,如果我不想要连接由逻辑AND搜索但逻辑OR的每个单词怎么办?我该怎么办?有没有办法用Django自己的Queryset方法做​​到这一点,还是必须回退到原始SQL查询?

一般情况下,这样做全文搜索是一个合适的解决方案,还是建议使用像Solr,Whoosh或Xapian这样的搜索引擎。它们有什么好处?

6 个答案:

答案 0 :(得分:16)

我建议您采用搜索引擎。

我们使用Haystack search,django的模块化搜索应用程序支持许多搜索引擎(Solr,Xapian,Whoosh等...)

优点:

  • 更快
  • 即使不查询数据库也可以执行搜索查询。
  • 突出显示搜索字词
  • “更像这样”功能
  • 拼写建议
  • 更好的排名
  • 等...

缺点:

  • 搜索索引的大小可以快速增长
  • 最好的搜索引擎之一(Solr)作为Java servlet运行(Xapian没有)

我们对这个解决方案非常满意,并且很容易实现。

答案 1 :(得分:5)

实际上,您发布的的查询使用OR而不是AND - 您使用\来分隔Q个对象。 AND将是&

一般情况下,我强烈建议使用合适的搜索引擎。我们在Solr之上使用Haystack取得了很好的成功 - Haystack管理所有Solr配置,并公开了一个与Django自己的ORM非常类似的漂亮API。

答案 2 :(得分:4)

回答一般问题:绝对使用适当的应用程序。

根据您的查询,您始终会检查字段的全部内容(标题,文字,标签)。你没有从索引等中获益。

使用适当的全文搜索引擎(或任何你称之为),每次插入新记录时文本(单词)都被索引。因此,查询将会更快,特别是当您的数据库增长时。

答案 3 :(得分:4)

SOLR很容易设置并与Django集成。干草堆使它更简单。

答案 4 :(得分:2)

要在Python中进行全文搜索,请查看PyLucene。它允许非常复杂的查询。这里的主要问题是你必须找到一种方法来告诉你的搜索引擎哪些页面发生了变化并最终更新了索引。

或者,您可以使用Google Sitemaps告诉Google更快地为您的网站编制索引,然后在您的网站中嵌入自定义查询字段。这里的优点是你只需要告诉谷歌改变的页面,谷歌将完成所有艰苦的工作(索引,解析查询等)。最重要的是,大多数人习惯使用Google进行搜索,此外,它还会使您的网站在全球Google搜索中保持最新状态。

答案 5 :(得分:2)

我认为在应用程序级别上进行全文搜索更多的是您拥有的内容以及您希望它如何扩展。如果您运行一个使用率较低的小型网站,我认为花一些时间进行自定义全文搜索而不是安装应用程序来执行搜索可能更实惠。应用程序将在存储数据时创建更多依赖,维护和额外工作。通过自己进行搜索,您可以构建漂亮的自定义功能。例如,如果您的文本与一个标题完全匹配,则可以将用户定向到该页面而不是显示结果。另一种方法是允许标题:或作者:关键字的前缀。

以下是我用于从网络查询生成相关搜索结果的方法。

import shlex

class WeightedGroup:
    def __init__(self):  
        # using a dictionary will make the results not paginate
        # but it will be a lot faster when storing data          
        self.data = {}

    def list(self, max_len=0):
        # returns a sorted list of the items with heaviest weight first
        res = []
        while len(self.data) != 0:
            nominated_weight = 0                      
            for item, weight in self.data.iteritems():
                if weight > nominated_weight:
                    nominated = item
                    nominated_weight = weight
            self.data.pop(nominated)
            res.append(nominated)
            if len(res) == max_len:
                return res
        return res

    def append(self, weight, item):
        if item in self.data:
            self.data[item] += weight
        else:
            self.data[item] = weight


def search(searchtext):
    candidates = WeightedGroup()

    for arg in shlex.split(searchtext): # shlex understand quotes

        # Search TITLE
        # order by date so we get most recent posts
        query = Post.objects.filter_by(title__icontains=arg).order_by('-date')
        arg_hits = query.count() # count is cheap

        if arg_hits > 1000:
            continue # skip keywords which has too many hits

        # Each of these are expensive as it would transfer data
        #  from the db and build a python object, 
        for post in query[:50]: # so we limit it to 50 for example                
            # more hits a keyword has the lesser it's relevant
            candidates.append(100.0 / arg_hits, post.post_id)

        # TODO add searchs for other areas
        # Weight might also be adjusted with number of hits within the text
        #  or perhaps you can find other metrics to value an post higher,
        #  like number of views

    # candidates can contain a lot of stuff now, show most relevant only
    sorted_result = Post.objects.filter_by(post_id__in=candidates.list(20))