知识管理

搜索引擎 Howto

修订历史
修订 1.1 2003/11/7 jiangxin
完善 MHonArc 模板
修订 1.0 2003/06/08 jiangxin
新闻到网页的转换和搜索引擎。

摘要

没有全文检索,企业内部的知识管理系统就不能成为成功的系统。构建一个企业内部的 GOOGLE,将内部资源一网打尽。首先介绍使用 MHonArc 将企业的news、maillist、email转换为网页,再介绍搜索引擎的配置。

(编译自版本: 58d659b,最后更新时间: 2007-06-19)


目录

1. Web 存档
1.1. MHonArc 的安装配置
1.2. MHonArc 应用示例
1.2.1. 任务1: 将邮件存档转换为网页
1.2.2. 任务2: 新邮件直接转换为网页——别名
1.2.3. 任务3: 新邮件直接转换为网页——procmail
1.2.4. 任务4: 新闻转换为网页
1.3. 定制 MHonArc
2. 建立搜索引擎
2.1. 安装 ASPSeek
2.2. 数据库维护
2.2.1. 配置文件: aspseek.conf
2.2.2. 用 index 程序定期更新搜索引擎数据库
2.3. searchd 后台数据库检索
2.3.1. searchd.conf
2.3.2. 运行 searchd
2.4. s.cgi: 具有类似GOOGLE查询界面的cgi程序
2.5. ASPseek对中文的支持

1. Web 存档

在使用搜索引擎之前,最好能将资源尽可能的整合到 Web 页面,这样才能使搜索引擎实至名归。本节介绍将 News,Maillist,Email 进行 Web 存档的过程,谨供参考。

先介绍一下 MHonarc,一个强大的邮件归档工具。

1.1. MHonArc 的安装配置

下载 MHonArc:http://www.mhonarc.org...

MHonArc 是通过一组 Perl 脚本实现的将邮件(注意不是新闻),转换为网页,并为网页建立基于时间和线索的索引。 用户还可以通过模块定制转换后的网页格式。总之 MHonArc 是非常强大,并且是非常胜任这一工作的。

MHonArc 安装过程是非常简单的:

prompt> wget 'http://www.mhonarc.org/release/MHonArc/MHonArcX.X.X.tgz'
prompt> tar -zxvf MHonArcX.X.X.tgz
prompt> cd MHonArcX.X.X
prompt> perl install.me
# 添加用户 mhonarc
prompt> adduser mhonarc 1
1

之所以添加用户,是为了使用 procmail 处理邮件,将新闻转换为网页。

新闻通过 news2mail 发给用户 mhonarc,通过用户 mhonarc 主目录下的 .procmailrc 文件,交由 procmail,在传递给mhonarc 程序处理。

1.2. MHonArc 应用示例

1.2.1. 任务1: 将邮件存档转换为网页

mbox 格式是Unix上存储邮件的最常见的格式,一个 mobx 文件可能包含零个到多个邮件。MHonArc 支持将 mbox 格式的文件批量转换为网页。

prompt> mhonarc path/mbox 1
prompt> mhonarc -outdir /home/johnson/archive /home/johnson/mail/inbox 2
1

将 mbox 中的邮件批量转换为网页存储在当前目录下。将会生成如下文件:

  • maillist.html

    主要的索引文件,文件列表将按照时间排序。

  • threads.html

    索引文件,按照线索显示列表

  • msg*.html

    转换为网页的邮件。* 从 0 开始,顺次加一。

  • .mhonarc.db

    MHonArc 维护的数据库,便于今后的目录更新。

2

通过 -outdir 参数,指定转换后的网页的存储路径,确省是在当前路径。

可能大多数人和我一样使用 Outlook 处理邮件。SourceForge.net 上面有几个开放源码,可以将 Outlook 的文件夹格式转换为 mbox 格式,之后的过程就和前面一样了。

  • ol2mbox

    Outlook to unix mail converter. 可以将 Outlook 的 PST 格式转换为 mailbox 格式。

  • mbx2mbox

    Converts Outlook .mbx and .dbx files into standard RFC822 mail files.

1.2.2. 任务2: 新邮件直接转换为网页——别名

利用邮件别名,实现将新邮件直接用 MHonArc 转换为网页。这项功能对于将 Mail-List 内容转换为网页非常适合。

例如在别名文件 /etc/aliases 中加入如下条目:


maillist2html: "|/usr/local/bin/mhonarc -add -quiet -umask 022 -idxfname index.html -rcfile /home/mhonarc/.mhonarc.mrc -outdir '/home/mhonarc/public_html/maillist1' -title 'Maillist 1 Archive'"

  • 上面的示例中,参数: -idxfname index.html,的作用是将主索引的文件名设置为 index.html 而不是 maillist.html;

  • 参数: -rcfile /home/mhonarc/.mhonarc.mrc ,的作用是使用文件 /home/mhonarc/.mhonarc.mrc 作为确省的模板文件,用以定制转换的网页的界面;

  • 参数: -outdir '/home/mhonarc/public_html/maillist1',用来设置转换后的网页的储存路径;

  • 参数: -title 'Maillist 1 Archive'",用来设置索引页的标题。

1.2.3. 任务3: 新邮件直接转换为网页——procmail

在上面描述的任务中,通过使用邮件别名将邮件转换为网页固然可以完成任务,但是如果想对订阅的大量的邮件列表进行 html 存档,就需要建立大量的对应的 aliases 条目,显然笨拙了一些。

procmail 是一个用于过滤用户接收到的电子邮件,并能对其自动分类、处理的一个应用软件。可以用来分检来自不同邮件列表的邮件。

在用户 mhonarc 的主目录下创建一个文件: /home/mhonarc/.procmailrc ,来实现将多个邮件组的邮件分检。如下:

VERBOSE=no
MAILDIR=$HOME/mail
WWWROOT=$HOME/public_html
LOGFILE=$HOME/var/log.procmail

MHONARC='/usr/local/bin/mhonarc -add -quiet -umask 022 -idxfname index.html'

:0
* ^Subject: [\/[^\]]+ 1
{
        ##Directory needs to be created seperately, as Mhonarc is unable to
        ##create the required directory.

        :0c
        | /bin/mkdir -m 755 -p $WWWROOT/$MATCH 2

        :0 : $HOME/var/mhonarc.lock
        | $MHONARC -rcfile $HOME/.mhonarc.mrc -outdir $WWWROOT/$MATCH -title "Maillist [$MATCH] Archive" 3

}
1

匹配邮件标题,对于格式为:"[邮件列表名称] 邮件标题..." 的邮件标题的邮件进行处理,将“邮件标题列表”部分存储在变量 $MATCH 中。

例如邮件标题为: “[docbook] RE: how to ...” 的邮件,匹配条件继续处理,并且将变量 $MATCH 设置为 docbook。

2

创建目录 $WWWROOT/$MATCH,目录名利用了刚刚获取的变量 $MATCH,该目录作为 MHonArc 的输出目录,即完成了邮件的分检。

3

交由 MHonArc 处理。即将邮件转换为网页。

1.2.4. 任务4: 新闻转换为网页

新闻组固然会给沟通带来便利之处,但是不足之处:

  • 无法通过 URL 定位到具体的文章,只能笼统的说在某某版在几月几号有关于这个问题的一篇文章;

  • 当某一板块有过多文章时,客户端下载会给服务器带来过多的负荷;

  • 不能进行检索。没有检索就不能叫做成功的知识管理系统,建立新闻组到网页的存档为建立全文检索准备了条件。

如果能够将新闻转换为 WEB 页面,上述问题迎刃而解。首先配置 inn 的 newsfeed 文件,将新闻以邮件形式发送给用户(如上例的用户 mhonarc)。

newsfeed 文件中相应条目:


n2m!:!*:Tc,Ac,Wn*:/usr/lib/news/bin/news2mail
mail-archive@sample.com:worldhello.*,本站站务.*,!local.test:Tm:n2m!

对应的配置文件 news2mail.cf 的相关条目:

mail-archive@sample.com  mhonarc

重新启动 news,新来的新闻就可以出发 news2mail 将新闻以邮件形式发送给用户(mhonarc)。还要注意 news2mail (perl 脚本)是如何重组邮件标题的,后面使用 procmail 分检邮件需要依赖邮件标题的格式。

接下来就是设置用户(mhonarc)的 .procmailrc,以实现分检邮件。照搬前一个例子即可。

1.3. 定制 MHonArc

上面的步骤提及了 MHonArc 的模板配置文件 $HOME/.mhonarc.mrc。下面是我定制的一个模板。至于详细内容参考 MHonArc 的帮助。


<!--The MHonARC documentation on the web provides a good introduction to all the below -->

<SORT>		# List messages by date in main index
<REVERSE>	# Sort messages in the reverse order
<TSORT>		# Want thread index
<TREVERSE>	# Sort thread index in the reverse order
<MULTIPG>	# Split index page into multiple pages, each page contain $IDXSIZE$ articles
<IDXSIZE>
100
</IDXSIZE>


<LOCALDATEFMT>
%y/%m/%d %H:%M:%S
</LOCALDATEFMT>

<MSGLOCALDATEFMT>
%y/%m/%d %H:%M:%S
</MSGLOCALDATEFMT>

<GMTDateFmt>
%y/%m/%d %H:%M:%S GMT
</GMTDateFmt>

<TTITLE>
$IDXTITLE$(线索)
</TTITLE>


<CharsetConverters override>
plain;    mhonarc::htmlize
default;  mhonarc::htmlize 
</CharsetConverters>


<!--
 ***************************************************************
 Define a custom resource variable representing our link. 
 ***************************************************************
-->

<DefineVar>
HOME-LINK
[<a href="/">HOME</a>]
</DefineVar>

<IDXFNAME>
index.html
</IDXFNAME>

<IDXPREFIX>
index
</IDXPREFIX>

<TIDXFNAME>
threads.html
</TIDXFNAME>

<TIDXPREFIX>
threads
</TIDXPREFIX>

<TIDXLABEL>
<a href='threads.html'>依线索排序</a>
</TIDXLABEL>

<IDXLABEL>
<a href='index.html'>依日期排序</a>
</IDXLABEL>


<!--
 ***************************************************************
 Main Index Page
 ================
	IDXPGSSMARKUP
	IDXPGBEGIN
	    LISTBEGIN
		(AUTHORBEGIN |
		 DAYBEGIN |
		 SUBJECTBEGIN)?
		LITEMPLATE+
		(AUTHOREND |
		 DAYEND |
		 SUBJECTEND)?
	     LISTEND
	    DOC?
	IDXPGEND
 ***************************************************************
-->

<IdxPgBegin>
<!doctype html public "-//W3C//DTD HTML//EN">
<html>
<head>
  <meta http-equiv="Content-Language" content="zh-cn">
  <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
  <link rel="STYLESHEET" type="text/css" href="/inc/css/main.css"/>
  <title>$IDXTITLE$ - $PAGENUM$/$NUMOFPAGES$</title>
</head>
<body>
<center>
<script language="javascript" src="/inc/jscript/header.js" type="text/javascript"></script>
<table border=0 width=90%>
  <tr><td>
<table border=0 width=100%>
  <tr>
   <td class="idxtitle">
     $IDXTITLE$
   </td>
  </tr>
  <tr>
    <td class="homelink">
      $HOME-LINK$
    </td>
  </tr>
</table>
<br>
</IdxPgBegin>


<IdxPgEnd>
  </td></tr>
</table>
<script language="javascript" src="/inc/jscript/footer.js" type="text/javascript">
write_footer("Page $PAGENUM$/$NUMOFPAGES$");
</script>
</center>
</body>
</html>
</IdxPgEnd>


<ListBegin>
<hr>
<table border=0 width=100%>
<ul>
  <li>更新日期: $LOCALDATE$,Articles: $NUMOFIDXMSG$/$NUMOFMSG$, Page: $PAGENUM$/$NUMOFPAGES$</li>
  <li>$TIDXLABEL$</li>
  <li>$PGLINK(PREV)$ | [<a href="$PG(FIRST)$">First Page</a>] | [<a href="$PG(LAST)$">Last Page</a>] | $PGLINK(NEXT)$</li>
</ul>
</table>
<hr>
<table border=0 width=100%>
  <tr>
    <td ALIGN=LEFT VALIGN="middle" width=2%></td>
    <td ALIGN=LEFT VALIGN="middle" width=68%><strong><U>标题</U></strong></td>
    <td ALIGN=LEFT VALIGN="middle" width=10%>[<strong><U>作者</U></strong>]</td>
    <td ALIGN=LEFT VALIGN="middle" width=20%>[<strong><U>日期</U></strong>]</td>
  </tr>
  <!-- Blank Row left out intentionally -->
  <tr>
    <td ALIGN=LEFT VALIGN="middle"></td>
    <td ALIGN=LEFT VALIGN="middle"></td>
    <td ALIGN=LEFT VALIGN="middle"></td>
    <td ALIGN=LEFT VALIGN="middle"></td>
  </tr>
</ListBegin>


<LISTEND>
</table>
<hr>
<table border=0 width=100%>
<ul>
  <li>更新日期: $LOCALDATE$,Articles: $NUMOFIDXMSG$/$NUMOFMSG$, Page: $PAGENUM$/$NUMOFPAGES$</li>
  <li>$TIDXLABEL$</li>
  <li>$PGLINK(PREV)$ | [<a href="$PG(FIRST)$">First Page</a>] | [<a href="$PG(LAST)$">Last Page</a>] | $PGLINK(NEXT)$</li>
</ul>
</table>
</LISTEND>


<LiTemplate> #this defoines the LI's of every list on the main index page
<tr>
  <td ALIGN=LEFT VALIGN="top">
    <strong>#</strong></td>
  <td ALIGN=LEFT VALIGN="top">
    <font class="msgtitle">$SUBJECT:65$</font></td>
  <td ALIGN=LEFT VALIGN="top">
    [<font class="msgauthor">$FROMNAME:10$</font>]</td>
  <td ALIGN=LEFT VALIGN="top">
    [<font class="msgdate">$MSGLOCALDATE$</font>]</td>
</tr>
</LiTemplate>


<!--
 ***************************************************************
 Thread Index Page
 =================
	TIDXPGSSMARKUP
	TIDXPGBEGIN
	    THEAD
		((TTOPBEGIN
		    TSUBLISTBEG
			((TLITXT
			    [possible subthread listing]
			  TLIEND)|
			(TLINONE
			    [possible subthread listing]
			 TLINONEEND))+
		    (TSUBJECTBEG
			((TLITXT
			    [possible subthread listing]
			  TLIEND)|
			(TLINONE
			    [possible subthread listing]
			 TLINONEEND))+
		     TSUBJECTEND)?
		    TSUBLISTEND
		  TTOPEND)
		 |
		 TSINGLETXT)* [message w/o references or follow-ups]
	    TFOOT
	    DOC?
	TIDXPGEND
 ***************************************************************
-->

<TIdxPgBegin> #Every index page will have this at the begining
<!doctype html public "-//W3C//DTD HTML//EN">
<html>
<head>
  <meta http-equiv="Content-Language" content="zh-cn">
  <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
  <link rel="STYLESHEET" type="text/css" href="/inc/css/main.css"/>
  <title>$TIDXTITLE$ - $PAGENUM$/$NUMOFPAGES$</title>
</head>
<body>
<center>
<script language="javascript" src="/inc/jscript/header.js" type="text/javascript"></script>
<table border=0 width=90%>
  <tr><td>
<table border=0 width=100%>
  <tr>
   <td class="idxtitle">
     $TIDXTITLE$
   </td>
  </tr>
  <tr>
    <td class="homelink">
      $HOME-LINK$
    </td>
  </tr>
</table>
<br>
</TIdxPgBegin>


<TIdxPgEnd>
  </td></tr>
</table>
<script language="javascript" src="/inc/jscript/footer.js" type="text/javascript">
write_footer("Page $PAGENUM$/$NUMOFPAGES$");
</script>
</center>
</body>
</html>
</TIdxPgEnd>


<THead>
<hr>
<table border=0 width=100%>
<ul>
  <li>更新日期: $LOCALDATE$,Articles: $NUMOFIDXMSG$/$NUMOFMSG$, Page: $PAGENUM$/$NUMOFPAGES$</li>
  <li>$IDXLABEL$</li>
  <li>$PGLINK(TPREV)$ | [<a href="$PG(TFIRST)$">First Page</a>] | [<a href="$PG(TLAST)$">Last Page</a>] | $PGLINK(TNEXT)$</li>
</ul>
</table>
<hr>
<ul>
</THead>


<TFoot>
</ul>
<hr>
<table border=0 width=100%>
<ul>
  <li>更新日期: $LOCALDATE$,Articles: $NUMOFIDXMSG$/$NUMOFMSG$, Page: $PAGENUM$/$NUMOFPAGES$</li>
  <li>$IDXLABEL$</li>
  <li>$PGLINK(TPREV)$ | [<a href="$PG(TFIRST)$">First Page</a>] | [<a href="$PG(TLAST)$">Last Page</a>] | $PGLINK(TNEXT)$</li>
</ul>
</table>
</TFoot>


<TTopBegin>
<li>
  <font class="msgtitle">$SUBJECT:65$</font>,
  <font class="msgauthor">$FROMNAME:10$</font>,
  <font class="msgdate">$MSGLOCALDATE$</font>
</TTopBegin>


<TTopEnd>
</li>
</TTopEnd>


<TContBegin>
<li>
  <font class="msgtitle">$SUBJECT:65$</font>, <EM>(continued)</EM>,
  <font class="msgauthor">$FROMNAME:10$</font>,
  <font class="msgdate">$MSGLOCALDATE$</font>
</TContBegin>


<TContEnd>
</li>
</TContEnd>


<TSubListBeg>
<ul>
</TSubListBeg>


<TSubListEnd>
</ul>
</TSubListEnd>


<TLiTxt>
<li>
  <font class="msgtitle">$SUBJECT:65$</font>,
  <font class="msgauthor">$FROMNAME:10$</font>,
  <font class="msgdate">$MSGLOCALDATE$</font>
</TLiTxt>


<TLiEnd>
</li>
</TLiEnd>


<TLiNone>
<li><em>Message not available</em>
</TLiNone>


<TLiNoneEnd>
</li>
</TLiNoneEnd>


<TSubjectBeg>
<li>&lt;Possible follow-up(s)&gt;</li>
</TSubjectBeg>


<TSUBJECTEND>
</TSUBJECTEND>


<TSingleTxt>
<li>
  <font class="msgtitle">$SUBJECT:65$</font>,
  <font class="msgauthor">$FROMNAME:10$</font>,
  <font class="msgdate">$MSGLOCALDATE$</font>
</TSingleTxt>


<!--
 ***************************************************************
 Message Page
 ============
 The message page contains a single message converted to HTML and archive navigational links.
	MSGPGSSMARKUP
	MSGPGBEGIN
	    MSGHEAD
	    TOPLINKS
	    SUBJECTHEADER
	    Converted message header
	    HEADBODYSEP
	    Converted message body
	    MSGBODYEND
	    (FOLUPBEGIN
		FOLUPLITXT+
	     FOLUPEND)?
	    (REFSBEGIN
		REFSLITXT+
	     REFSEND)?
	    BOTLINKS
	    MSGFOOT
	MSGPGEND
 ***************************************************************
-->
<MsgPgBegin>
<!doctype html public "-//W3C//DTD HTML//EN">
<html>
<head>
<meta http-equiv="Content-Language" content="zh-cn">
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<link rel="STYLESHEET" type="text/css" href="/inc/css/main.css"/>
<title>$SUBJECTNA:20$</title>
</head>

<body>
  <center>
  <script language="javascript" src="/inc/jscript/header.js" type="text/javascript"></script>

  <table border=0 width=90%>
    <tr><td>
  <table border=0 width=100%>
    <tr>
      <td class="idxtitle">
        $SUBJECTNA$
      </td>
    </tr>
    <tr>
      <td class="homelink">
        $HOME-LINK$
      </td>
    </tr>
  </table>
  <br>
</MsgPgBegin>


<MsgPgEnd>
    </td></tr>
  </table>
  <script language="javascript" src="/inc/jscript/footer.js" type="text/javascript">
    write_footer("$LOCALDATE$");
  </script>
  </center>
</body>
</html>
</MsgPgEnd>


<MSGHEAD>
</MSGHEAD>


<MSGFOOT>
</MSGFOOT>


<TopLinks>
<hr>
<ul>
  <li>$BUTTON(PREV)$$BUTTON(NEXT)$$BUTTON(TPREV)$$BUTTON(TNEXT)</li>
  <li>$[<a href="$IDXFNAME$#$MSGNUM$">Date Index</a>][<a href="$TIDXFNAME$#$MSGNUM$">Thread Index</a>]</li>
</ul>
</TopLinks>


<SubjectHeader>
<h1>$SUBJECTNA$</h1>
</SubjectHeader>


<HEADBODYSEP>
<hr>
</HEADBODYSEP>


<MSGBODYEND>
<hr>
</MSGBODYEND>


<FOLUPBEGIN>
<ul><li><strong>跟帖</strong>:
<ul>
</FOLUPBEGIN>


<FOLUPLITXT>
<li><strong>$SUBJECT$</strong>
<ul><li><em>From:</em> $FROM$</li></ul></li>
</FOLUPLITXT>


<FOLUPEND>
</ul></li></ul>
</FOLUPEND>


<REFSBEGIN>
<ul><li><strong>参考</strong>:
<ul>
</REFSBEGIN>


<REFSLITXT>
<li><strong>$SUBJECT$</strong>
<ul><li><em>From:</em> $FROM$</li></ul></li>
</REFSLITXT>


<REFSEND>
</ul></li></ul>
</REFSEND>


<BotLinks>
<ul>
  $LINK(PREV)$$LINK(NEXT)$
  $LINK(TPREV)$$LINK(TNEXT)$
  <li>Index(es):
    <ul>
      <li><a href="$IDXFNAME$#$MSGNUM$">
      <strong>日期排序</strong></a></li>
      <li><a href="$TIDXFNAME$#$MSGNUM$">
      <strong>线索排序</strong></a></li>
    </ul>
  </li>
</ul>
</BotLinks>


与该模板对应的 css 文件


p {font-size:9pt}
h1 {font-size:18pt;line-height:130%;font-weight:"bold";align:"center";color:"red";}

td,li,select,input {font-size:9pt}
.sect1 {font-size:9pt;line-height:150%;color:#333333;background-color:#E1E1E1;font-weight:bolder;}
.datetime {font-size:9pt;color:"red"}
.star  {font-size:9pt;color:"red"}
.gray {font-size:9pt;color:"gray"}
.cvskw {font-size:9pt}
.footer           {font-size:9pt;font-weight:bolder}
.footer A:link    {font-size:9pt;font-weight:bolder}
.footer A:active  {font-size:9pt;font-weight:bolder}
.footer A:visited {font-size:9pt;font-weight:bolder}
.footer A:hover   {font-size:9pt;font-weight:bolder}
A:link {color: #000000;}
A:visited {color: #000000;}
A:active,A:hover {color : #000000}
HTML BODY { LINE-HEIGHT: 1.2; MARGIN: 0 0 2em 0 }
UL { list-style-image: url("/images/dot.gif") }

.em           {font-size:"9pt";color:"red";font-weight:"bolder";font-style:"normal";letter-spacing:"2px";}
.em A:link    {color: "red";letter-spacing:"4px";}
.em A:active  {color: "red";letter-spacing:"4px";}
.em A:visited {color: "red";letter-spacing:"4px";}
.em A:hover   {color: "red";letter-spacing:"4px";font-size:"12pt";}

.idxtitle {text-align:"center";text-valign:"middle";background-color:"#006666";font-size:"12pt";font-weight:"bold";color:"#FFFFFF";}
.homelink {text-align:"right";text-valign:"middle";font-size:"9pt";color:"#AB0000";}

hr		{border:dashed; color:blue; height:1px;}

.msgtitle	{font-size:"9pt";color:"black";font-weight:"bolder";font-style:"normal";}
.msgauthor	{font-size:"9pt";color:"black";font-weight:"normal";font-style:"italic";}
.msgdate	{font-size:"9pt";color:"gray"; font-weight:"normal";font-style:"normal";}


2. 建立搜索引擎

没有全文检索,企业内部的知识管理系统就不能成为成功的系统。构建一个类似 GOOGLE 的全文检索系统的是否要花费巨大呢?看看 Google 的搜索引擎软件目录吧。

我对其中的两个开放源码的搜索引擎进行了测试。htdig 是一个不错的搜索引擎软件,可惜不支持中文的检索;ASPseek 接着走入我的视线,这个软件对中文的支持非常之好,就连CGI的界面、CGI传递参数都和 GOOGLE 非常类似。

ASPseek 由三个部分组成:前端的 cgi 程序 s.cgi 提供查询界面和返回查询结果;后端的守护程序 searchd 接收cgi程序的查询请求,执行数据库查询,返回结果;后台数据库的维护则由程序 index 完成。

相关链接:

2.1. 安装 ASPSeek

ASPseek 安装过程比较简单,需要注意的是 ASPseek 的安装和运行需要先安装和配置 MySQL。下面是一般的安装过程(假设MySQL已经正确的安装在路径 /usr/local/mysql/ 下):

root> tar zxvf aspseek-1.2.10.tar.gz
root> cd aspseek-1.2.10
root> ./configure --with-mysql=/usr/local/mysql --prefix=/usr/local/aspseek 1
root> make && make install
root> /usr/local/aspseek/sbin/aspseek-mysql-postinstall 2
root> cp /usr/local/aspseek/bin/s.cgi <WWWROOT>/cgi-bin/ 3
root> useadd aspseek 4
root> su - aspseek
aspseek> /usr/local/aspseek/sbin/index  5
aspseek> /usr/local/aspseek/sbin/searchd -D  6
1

指出 mysql 的安装路径和设置 ASPseek 的安装路径

2

初始化 ASPseek 数据库设置。创建数据库 aspseek12,访问该数据库的用户名和口令保存在配置文件 /usr/local/aspseek/etc/db.conf

6

将cgi程序 s.cgi 程序拷贝到WEB服务器的cgi-bin目录中

4

为确保系统安全,创建用户 aspseek,并使用该用户的身份,进行数据索引的维护和启动搜索引擎服务程序

5

以 aspseek 用户的身份,进行数据索引的维护

6

以 aspseek 用户的身份,启动搜索引擎服务程序

2.2. 数据库维护

index 程序完成的功能包括网站爬虫、网页下载、解析、数据库维护。

2.2.1. 配置文件: aspseek.conf

aspseek.conf 是 index 程序的配置文件,告诉 index 程序为哪个网址建立索引,如果建立索引等等。

Include db.conf 1
UtfStorage yes 2
Include ucharset.conf  3
Period 1d 4
Server	URL 5
1

包含 db.conf 配置文件,设置连接 MySQL 数据库的用户名、口令等。例如:DBAddr mysql://aspseek12:PASSWOR-IS-HERE@localhost/aspseek12/

4

设定网页重建索引的间隔,作用域到下一个 Period 命令或者文件结尾。对作用域内的 Server 指定的网站有效,因此可以对不同的网站设置不同的更新频率

2

以 UTF-8 格式存储 MySQL 数据库中信息

3

配置字符集。若需要能够对中文进行检索,需要打开 CharsetTableU2 和 Dictionary2 的配置

5

最重要的设置。告诉 index 为哪些网站建立索引,可通过多个 Server 配置设置多个服务器。注意:如果在URL中包含路径并不能将索引限制在该目录下,仍然会对整个网站建立索引。如果想限制某些路径,使用 Disallow 配置,例如下面的配置将对网站的索引限制在 url: http://members.aol.com/midlandsda 下。

Server http://www.aol.com/ 
Allow ^http://members.aol.com/midlandsda 
Disallow ^http://www.aol.com/ 

2.2.2. 用 index 程序定期更新搜索引擎数据库

  • index -a

    为所有网页重新建立索引。不使用该参数,则参考配置中的 Period 参数,只对早于这个时间的网页重建索引。

  • index -S

    显示数据库的统计信息

  • 通过 crontab 设置定时启动 index 重建检索

    0 0 * * * su -c '/usr/local/aspseek/sbin/index' aspseek 
    
[注意]

注意要以 aspseek 用户身份执行。

2.3. searchd 后台数据库检索

searchd 是 ASPseek 的守护进程,用来接收 cgi 程序(s.cgi)的查询请求,执行查询动作(搜索由 index 程序创建的数据库),将结果反馈给 cgi 程序(s.cgi)。

2.3.1. searchd.conf

searchd 程序的配置文件。

# Port 12345 1
Include db.conf 2
UtfStorage yes 3
Include ucharset.conf  4
1

设置 searchd 侦听的端口。确省为 12345

2

包含 db.conf 配置文件,设置连接 MySQL 数据库的用户名、口令等。例如:DBAddr mysql://aspseek12:PASSWOR-IS-HERE@localhost/aspseek12/

3

以 UTF-8 格式存储 MySQL 数据库中信息

4

配置字符集。若需要能够对中文进行检索,需要打开 CharsetTableU2 和 Dictionary2 的配置

2.3.2. 运行 searchd

配置开机自动执行 searchd。


#!/bin/sh
# FileName: /etc/rc.d/rc3.d/S99aspseek
# Script to control aspseek
#

case "$1" in
'start')
        echo "starting aspseek server ...  "
        su aspseek -c "/usr/local/aspseek/sbin/searchd -D -R"
        echo done
        ;;
'stop')
        echo "stopping news server ...  "
        killall -9 searchd
        echo done
        ;;
*)
        echo "usage: news.sh {start | stop}"
        ;;
esac

2.4. s.cgi: 具有类似GOOGLE查询界面的cgi程序

搜索引擎的界面是由 CGI 提供的。ASPseek 的CGI程序叫做 s.cgi,当然可以改成其它名字。配置则非常简单,除了要将 Apache 配置为支持 cgi 外, 只需要将 s.cgi 从 /usr/local/aspseek/bin/ 目录下拷贝到 Apache 的 cgi-bin 目录下即可。 配置 Apache 可能非常简单,也许一句 "ScriptAlias /cgi-bin/ /PATH/cgi-bin/" 就足够了。

我感打赌,s.cgi 一下子就会抓住你的眼球,因为 s.cgi 程序提供的一流的查询界面,和伟大的 Google 太类似了,尤其是那一排字母 "e"。

ASPseek 查询界面

ASPseek 查询界面

s.cgi 还提供了定制界面的方案:只需要修改模板文件 /usr/local/aspseek/etc/s.htm 即可。下面是 s.htm 定制的示例(只显示重要的改动):

[注意]

注意:如果将 s.cgi 改名,相应的模板也需要改名。例如 cgi 改名为 search.cgi,则相应的模板文件为 search.htm。

<!--top-->
<HTML>
<HEAD>
  <META http-equiv="Content-Type" content="text/html; charset=gb2312">  1
  <LINK REL="STYLESHEET" TYPE="text/css" HREF="/inc/css/main.css"/> 2
 <title>ASPseek: $Q</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
  <SCRIPT language= "javascript" src="/inc/jscript/header.js" type=text/javascript></script>  3

<FORM METHOD=GET ACTION="$A">
<TABLE width="100%" align="center" valign="center">
<TR>
  <TD VALIGN=bottom><IMG SRC="/img/aspseek-big.png" WIDTH="168" HEIGHT="66" ALT="ASPseek" BORDER="0" ALIGN="left" HSPACE="0"></TD>
  <TD VALIGN=center ALIGN=center width="*">
   <INPUT TYPE="hidden" NAME="cs" VALUE="gb2312">  4
   搜索 
   <INPUT TYPE="text" NAME="q" SIZE=30 VALUE=""> 5
   <INPUT TYPE="submit" VALUE="查询知识库">
  </TD>
  <TD width="100">
  </TD>
</TR>
</TABLE>
</FORM>

... ...
... ...

<center>
<script language= "javascript" src="/inc/jscript/footer.js" type=text/javascript></script> 6
<script language= "javascript"> write_footer("Template: $Id$"); </script> 
</center>

<DIV ALIGN=right>
<A HREF="http://www.aspseek.org/"><IMG SRC="http://www.aspseek.com/i/pow_aseek.png" WIDTH=88 HEIGHT=31 BORDER=0 ALT="Free search engine software: ASPseek $AV"></A>
</BODY>
</HTML>
<!--/bottom-->

1

将cgi查询的输出界面(HTML)的语言编码设置为中文

2

引用外部的样式表文件,这样可以根据需要改变查询界面而尽量少的修改模板文件。一个简单的样式表示例:

p {font-size:9pt}
h1 {font-size:20pt;line-height:130%;font-weight:"bold";align:"center";}
td,li,select,input {font-size:9pt}
.sect1 {font-size:9pt;line-height:150%;color:#333333;background-color:#E1E1E1;font-weight:bolder;}
.datetime {font-size:9pt;color="red"}
.star  {font-size:9pt;color="red"}
.gray {font-size:9pt;color="gray"}
.cvskw {font-size:9pt}
.footer           {font-size:9pt;font-weight:bolder}
.footer A:link    {font-size:9pt;font-weight:bolder}
.footer A:active  {font-size:9pt;font-weight:bolder}
.footer A:visited {font-size:9pt;font-weight:bolder}
.footer A:hover   {font-size:9pt;font-weight:bolder}
A:link {color: #000000;}
A:visited {color: #000000;}
A:active,A:hover {color : #000000}
HTML BODY { LINE-HEIGHT: 1.2; MARGIN: 0 0 2em 0 }
UL { list-style-image: url("/images/dot.gif") }

.em           {font-size:"9pt";color="red";font-weight:"bolder";font-style:"normal";letter-spacing:"2px";}
.em A:link    {color: "red";letter-spacing:"4px";}
.em A:active  {color: "red";letter-spacing:"4px";}
.em A:visited {color: "red";letter-spacing:"4px";}
.em A:hover   {color: "red";letter-spacing:"4px";font-size:"12pt";}
3

引用外部 javascript,显示头部信息,避免过多对该模板的修改。一个 header.js 示例:

<!--
document.write("      <table width=750 border=0 cellspacing=0 cellpadding=0>");
document.write("        <tr valign=bottom>");
document.write("      	<td width='*'>");
document.write("      	  <table width='100%' height=50 border=0 cellspacing=0 cellpadding=0>");
document.write("            <tr>");
document.write("              <td align='center' valign='center'><h1><A HREF='/'>Johnson's Homepage</A></h1></td>");
document.write("            </tr>");
document.write("      	  </table>");
document.write("        </td></tr>");
document.write("      </table>");
//-->
4

通过设置cgi的cs字段,用以支持中文查询

5

q 是真正的查询字段。看看 GOOGLE 的查询网页,看看是不是非常类似?

6

引用外部 javascript,显示脚注信息,避免过多对该模板的修改。一个 footer.js 示例:

<!--
function write_footer($str)
{
	document.write("      <TABLE align='center' border='0' cellpadding='0' cellspacing='0' width='750'>");
	document.write("        <TR>");
	document.write("          <td align=center>");
	document.write("          <hr>");
	if ($str)
		document.write("<font class='cvskw'>" + $str + "</font><br>");
	document.write("          <font class='footer'>CopyLeft 2003, <a href='mailto:johnson.AT.worldhello.net'>Johnson</a></font><br>");
	document.write("          </td>");
	document.write("        </TR>");
	document.write("      </TABLE>");
}
//-->

2.5. ASPseek对中文的支持

中文、日文、韩文等多字节文字不同于英文等单字节语言,是多字节语言。我们都知道英文是依靠空格和标点符号将单词分割开,搜索引擎只要依据空格和标点就可以分割出词表。对一些检索意义不大的单词,则将这些词列在所谓 stopwords 词表(又称为 badwords中),如: "a, an, and, the, ...",避免因为对这些词建立检索而耗费过多的资源。这些 stopwords 保存在目录 /usr/local/aspseek/etc/stopwords/ 中。

而中文则不能造此办理,没有空格开区分词和词组,因此需要建立相应的词表。这些词表保存在目录 /usr/local/aspseek/etc/tables/ 中。

为能够对中文文档进行检索,需要检查是否在配置文件 aspseek.conf 和 searchd.conf 配置了 "CharsetTableU2" 和 "Dictionary2"选项。这两个选项是在配置文件 ucharset.conf 中定义的,用语句 "Include ucharset.conf" 包含入配置文件 aspseek.conf 和 searchd.conf。 ucharset.conf 中"CharsetTableU2" 和 "Dictionary2"的配置如下:


########################################################################
# Unicode charset definition for multibyte charsets
# (please comment all not needed charsets):
# CharsetTableU2 <charset> <lang> <table> [<lang_map>]
#
CharsetTableU2 big5 ch tables/big5.txt
CharsetTableU2 gb2312 ch tables/gb2312.txt

########################################################################
# Dictionary for tokenizing of text in Chinese, Japanese and Korean languages
# Dictionary2 <lang> <file> [<file encoding>]
# If file encoding is omitted, then file is assumed to be in unicode
# Must be set after encoding definition (CharsetTableU2)
# <lang> parameter must match <lang> in CharsetTableU2 directive.
Dictionary2 ch tables/chinese.txt big5