通常我们选用 Google CSE 自定义搜索引擎代替网站自身的搜索服务,可以减轻服务器的负载,但更重要的原因是 Google 搜索有强大的词语分割、智能匹配、拼写纠正功能,甚至能将 "bb" 与 "BlackBerry", "DM" 与 "桌面管理器" 进行通配,这些算法是我们自己做不到的。我以前在博客中采用 iframe 版的 CSE, 最近把它换成了定制性更强,基于 Google ajax API 的新版,将经验分享一下。本方案优点:
首先需要在 控制面板 - 外观新功能!中选择“搜索元素”模式 (Search element), 再选择一种布局和一种样式。实际上 ajax API 能做的事情非常多,比如就在搜索框下方即时展开结果列表。如果把结果列表悬浮绝对定位,再加上 Search as user Types, 就可以做 apple.com 右上角那种搜索样式了,一边输入一边匹配。
但是考虑到小博客的站内搜索被使用得并不多,我还是选择了两栏布局,将结果列表放在一个专门的页面 /search 中,这样的好处是不搜索的时候可以不加载多余的内容:API 库,JS, CSS 等等。
先不急着把获取的那一大堆代码往搜索框上放,上面说了,我们要的效果是不搜索的时候不加载。就改造一下模板原生的搜索框就可以了,让它提交用户输入的内容到 /search 这个页面,就这样。我的 header.php 中的搜索框是这样的:
<form action="/search" id="searchbox">
<input type="text" name="q" id="input_search" />
<input type="submit" value="搜索" />
</form>
一个 form 中装一个输入框和一个按钮,关键内容是 action="/search" 和 name="q", 表示将会跳转到 /search?q=搜索的内容.
建立一个页面 /search 来放入所有 CSE 代码。页面正文中放入 id="cse" 的 div, 搜索执行的时候脚本将会改写其中的内容。
<div id="cse">正在搜索...</div>
然后在页面任意位置,比如末尾,写入前面获得的两个 javascript 代码,一个是 ajax API 库 google.com/jsapi, 另一个是以 google.load 开头的一大串。还有些 CSS, 是前面获取代码的时候选择的一种样式。
但这时候还不会自动执行用户之前输入的搜索,要从 url 请求中把搜索词剥离出来,并执行。这里我用了 Kevin Yang 提供的方法,在 draw(’cse’) 一行后加入:
var match = location.search.match(/q=([^&]*)(&|$)/);
if(match && match[1]){
var search = decodeURIComponent(match[1]);
customSearchControl.execute(search);
}
现在这个搜索系统就可以正常工作了。下面是对脚本的进一步自定义,只逐条说明,不每次都写完整的代码,我会把完整的修改后的脚本放在本文末尾。如果今后代码发生变动,请自行查看我的 搜索结果页 源代码。
如果要用自己网站的样式,最好是完全不加载 Google 的 CSS, 不然覆盖样式就有得写了。在 google.load 中加入 "nocss" : true 即可。
google.load(’search’, ’1’, {language : ’zh-CN’ , "nocss" : true });
由 setNoResultsString 控制,在无结果时将字串写入到 "正在搜索" 处。中文语言下缺省值为“无结果”
customSearchControl.setNoResultsString(’什么也没找到,请重试’);
由 setResultSetSize 控制,可选参数为 FILTERED_CSE_RESULTSET 10条;LARGE_RESULTSET 8条;SMALL_RESULTSET 4条。
customSearchControl.setResultSetSize( google.search.Search.SMALL_RESULTSET);
由 setLinkTarget 控制,一般用到的就是 LINK_TARGET_BLANK 和 LINK_TARGET_SELF 两种。
customSearchControl.setLinkTarget( google.search.Search.LINK_TARGET_SELF);
由 setSearchCompleteCallback 控制,这是一个相当灵活的命令,我这里用它来将搜索结果标题中的“老肥博客 » 非唠不可”去掉,不然每条标题后面都有这样一句,比较难看。这里我另外加载了 jQuery 来用,当然这不是必需的,如果没有需要就不用了。
customSearchControl.setSearchCompleteCallback(null, function() {
$(’input.gsc-input’).select();
$(’a.gs-title’).unwrap().wrap(’<h3></h3>’).each(function() {
var title = $(this).html().replace(/\|.*/g, ’’);
$(this).html(title);
});
$(’b:contains("...")’).contents().unwrap();
});
以上在 setSearchCompleteCallback 中执行了三个步骤:
我的搜索页面中还有一些脚本和样式,比如将 CSE 的搜索框伪装成模板原生的输入框,这样可以避免多次搜索的时候重复加载页面;从搜索框中取得当前关键字,写入到右侧提示区;等等,比较特殊,这里就不细写了,反正 Firebug 什么的都一眼看穿。
另外,前面提到 Kevin 的文章中有个技巧很不错,只让单篇文章出现在搜索结果中,排除掉翻页、标签等页面,像我用 .html 作为单篇文章的链接结构就很好办,直接在 CSE 控制面板中设置“包含的网站”为 fis.io/*.html, 就排除了其它形式的链接结构。
微博也是博客,我在 fis.io/*.html 之外还将 twitter.com/fisio/* 编入了索引,在搜索某些内容的时候会看到我的推也在搜索结果中。
最后是我的 /search 页面中相关脚本完整版:
<script src="upload/2012/3/201203231357594114.gif" class="wp-smiley sm-sad"> 什么也没找到,请重试’);
customSearchControl.setSearchCompleteCallback(null,function() {
$(’input.gsc-input’).select();
var searchwords = $(’input.gsc-input’).val();
$(’.p > b’).text(searchwords);
$(’a.gs-title’).addClass(’new’).unwrap().wrap(’<h3></h3>’).each(function() {
var title = $(this).html().replace(/\|.*/g, ’’);
$(this).html(title);
});
$(’b:contains("...")’).contents().unwrap();
$(’.gsc-cursor-current-page’).removeClass(’gsc-cursor-page’);
});
customSearchControl.draw(’cse’);
var match = location.search.match(/q=([^&]*)(&|$)/);
if(match && match[1]){
var search = decodeURIComponent(match[1]);
customSearchControl.execute(search);
}
});
</script>