我们的网站通常都要集成搜索服务。通常情况下,我们都使用自己的搜索后端,例如使用Django,对于Python,我们主要有两种选择,一种是Whoosh,它是纯Python写成的搜索后端;另一种则是著名的Lucene的Python扩展,PyLucene,要提醒使用PyLucene,需要安装JVM。以后的文章我会介绍他们。

不过,今天的主角显然不是它们。因为有时候,我们并不需要这么麻烦,有时我们只需要集成一个Google搜索在其中就可以了。那么,Google custom search就派上了用场。在这里,我们也有三种方案:

  1. 用iframe版本的cse,使用这种方式,甚至不需要写什么代码。

  2. 使用Google ajax API,使用ajax方式获取搜索结果,然后用js将结果呈现在页面上。

  3. 使用Custom search API,在后端使用Python的http编程,远程获取json方式的结果,并把数据渲染到模板中。

在一切开始之前,必须先创建一个新的自定义搜索。在cse页面上,点击“Create a Custom Search Engine”,如果已经新建过了,点击下面的“manage your existing search engines”。

Django开发中使用Google custom search API_第1张图片


创建或进入管理界面以后,在“Control panel”中可以得到Search engine unique ID,常常用cx标记。在Control panel中有众多设置,比如你的网站不是所有页面都想被索引,你可以在“Sites”设置匹配规则。或者,你可以把网站的sitemap添加到“Indexing”中,更多请参考cse页面中说明。

使用iframe版本的cse是相当简单的,直接拷贝cse提供的html代码拷贝到自己页面即可,这里不多赘述。对于第二种方式,请参考这篇文章,讲的很详细。本文主要讲解第三种方式。

要使用Custom search API获取json方式的结果,首先需在Google APIs console中开启(需×××),来获取API的key。要注意的是,使用这种方式,每天只能免费搜索100次。不过对于个人网站,100次基本上够用了。

假设现在我们已经获取了API的key和cx。那我们就正式开始。

由于我们使用json处理数据,我们需要导入json库,Python中有内置的json模块,不过在2.6版本前称为simplejson,为了保证在Python各版本中不会发生导入错误,我们这么写:

def import_simplejson():
    try:
        import simplejson as json
    except ImportError:
        try:
            import json  # Python 2.6+
        except ImportError:
            try:
                from django.utils import simplejson as json  # Google App Engine
            except ImportError:
                raise ImportError, "Can't load a json library"
 
    return json


这样我们在使用json库之前,只需调用import_simplejson函数即可。

考虑我们以后可能考虑换成其他的搜索后端,另一方面,也是保证依赖倒置的设计原则,我们首先定义SearchBase类:

class SearchBase(object):
    def __init__(self, q):
        self._q = q
 
    def __call__():
        raise NotImplementedError # 使用callable的方式。


我们要获取数据,需要提供这几个参数,以GET方式调用:key,cx,q,start。当然参数不止这么多,其他参数。key和cx已经讲过,q是搜索关键字,必须要url encode。start是搜索起始。贴出代码:

import urllib2, urllib
class Record(object):
    '''表示单条搜索结果'''
    def __init__(self, adict):
        assert adict, dict
        setattr(self, 'title', adict['title'])
        setattr(self, 'htmlSnippet', adict['htmlSnippet'])
        setattr(self, 'link', adict['link'])
 
class GoogleSearch(SearchBase):
    key = ''
    cx = ''
     
    url = 'https://www.googleapis.com/customsearch/v1' # 调用api的地址
     
    def __init__(self, q, page=1):
        # 初始化,q为搜索关键字,默认从第一页开始
        SearchModel.__init__(self, q)
        self._page = page
     
    def _get_data(self):
        start = (self._page - 1) * 10 + 1 # 计算起始位置
         
        data = {'q': self._q.encode("utf-8")}
        q_str = urllib.urlencode(data) # 使用urllib中的urlencode方法
         
        abs_url = "https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&%s&start=%d" % \
        (self.key, self.cx, q_str, start)
         
        data = urllib2.urlopen(abs_url) # 使用urllib2的urlopen方法更简单
         
        resultContent = data.read() # 获取结果
 
        return resultContent
     
    def _get_json(self):
        if getattr(self, '_json', None) is None:
            json = import_simplejson()
            self._json = json.loads(self._get_data()) # 加载json数据
        return self._json
 
    def _get_count(self): 
        # 获取搜索结果数量
        _json = self._get_json()
        return int(_json['queries']['request'][0]['totalResults'])
     
    def _get_result_list(self):
        _json = self._get_json()
        return _json['items']
     
    def __call__(self):
        records = []
        try:
            results =  self._get_result_list()
            for r in results:
                record = Record(r)
                records.append(record)
        except KeyError:
            pass
            # print KeyError.message
        return self._get_count(), records


代码很简单。这样,我们在搜索相应的view中,只需获取搜索关键字,渲染给模板即可。

search = GoogleSearch(query, page)
count, results = search()