search

 

%%% 
%%% Description: My final paper.
%%% File:        final.tex
%%% Author:      Li Yi <pank7yardbird@gmail.com>, <liyi@netera.cn>
%%% Date & Time: 2006年 05月 23日 星期二 20:44:58 CST
%%% 
/documentclass[12pt, a4paper, titlepage]{article}
/usepackage{CJK, indentfirst, times, graphicx, cite, tabularx,
  CJKnumb, calc, fancyhdr, verbatim, amssymb}

/setlength{/textwidth}{450pt}
/setlength{/textheight}{670pt}
/setlength{/voffset}{-1cm}
/setlength{/hoffset}{-1cm}
/setlength{/parskip}{0.3ex}

/begin{document}

/begin{CJK*}{GB}{song}
  /pagestyle{fancy}

  /CJKindent
  /CJKtilde

  /title{搜索引擎索引的研究} /author{姓名:李一//学号:4102412//学校:
    中国地质大学(北京)//院系:信息工程学院//专业:计算机科学与技
    术//联系方式:/mbox{pank7yardbird@gmail.com}}

  /maketitle

  /clearpage
  /addcontentsline{toc}{section}{摘~~要}
  /begin{center}
    /section*{摘~~要}
    /label{sec:abs_zh}
  /end{center}

    /begin{CJK*}{GB}{kai}

      本文旨在研究当前Web上中英文搜索引擎的检索子系统中的主要技术。笔者
      根据约三个月在“天网时代”公司实习和毕设工作的实践经历,着重研究
      了“天网”中英文搜索引擎检索子系统的体系结构及其实现。本论文还重
      点介绍了面向主题的搜索引擎中的检索技术,并结合一个已实际运行的面
      向主题的搜索引擎:“天网”搜索引擎数字资源搜索
      (http://www.tianwang.com/)的工程实践,提出了一种对面向主题的搜
      索引擎中的xml数据进行索引的基本方法及其技术实现路径。

      本文的大纲如下:

      /begin{itemize}
      /item 本文首先说明了什么是Web,何谓搜索引擎,搜索引擎和信息检索的
        关系,以及搜索引擎的分类。
      /item 接着描述了北大天网中英文搜索引擎的历史、各项技术特点和系统
        的模型。
      /item 然后本文重点剖析了天网搜索引擎的检索子系统的设计、结构和实
        现。着重研究了检索子系统中的倒排索引模型、创建算法、查询服务算
        法和查询结果排序中的技术。
      /item 之后本文介绍了一个面向主题的搜索引擎:天网数字资源搜索。详
        细讲解了其设计和开发。着重研究了其中的检索子系统,并提出一种
        “小索引”算法。该系统已经开始成功运行。
      /item 最后总结了本文的研究工作,提出了现有系统的不足和对未来的展
        望。
      /end{itemize}

      /textbf{关键词}:搜索引擎;检索;索引;倒排
    /end{CJK*}

  /newpage

  /clearpage
  /addcontentsline{toc}{section}{ABSTRACT}
  /begin{abstract}
    /label{sec:abs_en}

    In this paper, we studied the main techniques of retrieval
    subsystem of Chinese/English search engines. The author put much
    stress on the architecture and implementation of a famous
    Chinese/English search engine: ``Tianwang'', with an experience of
    a three-month's work at Netera company. After that, the author
    introduced the implementation of a running topic oriented search
    engine: ``Tianwang Digital Resources
    Search(http://www.tianwang.com/)'' and its retrieval subsystem.
    And a method and implementation of indexing xml documents of topic
    oriented search engines is proposed by the author.

    The outline of this paper includes:

    /begin{itemize}
    /item The first part is trying to illustrate the concepts of Web
      and search engine, the relationship of information retrieval and
      search engine, and the classification of search engines. 
    /item Then the paper introduced the history of a famous Chinese
      search engine: Tianwang Chinese/English search engine of PKU,
      the characteristics of its techniques, and the model of this
      system.
    /item After that, the author analyzed the retrieval subsystem of
      ``Tianwang'' about its design, architecture and implementation.
      This part put much stress on the model of inverted index, the
      algorithms of building index and service, and the techniques in
      relevance ranking of query results.
    /item A topic oriented search engine: Tianwang Digital Resource
      Search is presented, about its design and development in detail.
      The author especially focused on its retrieval subsystem, and
      proposed an ``indexie'' algorithm. This system is running for
      service on the Web now.
    /item The last part is a summary of the whole paper, and posed
      inadequate of the existing system. Also take a look into the
      future.
    /end{itemize}

    /textbf{Keywords}: search engine; retrieval; index; inversion.
  /end{abstract}

  /newpage

  /tableofcontents
  /listoffigures
  /listoftables

  /newpage

  /clearpage
  /addcontentsline{toc}{section}{前~~言}
  /section*{前~~言}
  /label{sec:firstofall}

  1994年左右,万维网(World Wide Web,简记为WWW或Web)出现。它的开放性
  (openness)和其上信息广泛的可访问性(accessibility)极大地鼓励了人们
  创作的积极性。Web的核心技术是超文本和超媒体。通过将文本、图形、图像、
  音频、视频等信息有机结合,给人们提供了丰富的信息表示空间。由于其界面
  友好、易学易用、内容丰富的特点,很快就被包括政府机关、科研机构、商业
  企业和个人用户所接受,成为人们日常信息交流中经常使用的工具。1995年4月
  开始,Web在网上的流量就超过其他所有服务跃居第一。之后Web的发展速度还
  在加快。

  而搜索引擎(search engine, 简写为SE),是指一种在Web上应用的软件系统,
  它以一定的策略在Web上搜集和发现信息,在对信息进行处理和组织后,为用户
  提供Web信息查询服务/cite{LXM}。搜索引擎的导航服务功能现在已成为互联网
  上最重要的服务之一。

  /section{搜索引擎的介绍}
  /label{sec:se_intro}

  /subsection{信息检索和搜索引擎}
  /label{ssec:ir_se}

  信息检索(information retrieval,简写为IR)是一门关注于对半结构化或非
  结构化数据(特别是自然语言的文本文档)提供检索功能的学科。信息检索的
  目的为:在某个特定的集合(文档集)中寻找“有关于”某个特定的主题或者
  满足某个特定的信息需求的文档/cite{EG}。

  可以说搜索引擎是在信息检索系统的基础上发展而来的,又和信息检索有很大
  的不同。IR系统中所处理的数据常被称为“文档(documents)”,具体来
  说IR系统要检索的数据来自于某个组织好的(并且相对来说稳定的)文档仓库
  (repository),常被称为“馆藏(collection或 archive)”/cite{LXM}。
  但是并不是说IR系统被限制在这个范围内。文档集可以是某个消息流,
  如:E-mail消息、传真和新闻等等。而对于一个SE系统来说,它要检索的对象
  是整个互联网。相比较而言,信息检索的研究对象多集中在一些小规模的半结
  构化的文档集上/cite{SL}。首先在数据量上,互联网要比信息检索文档集大得
  多。信息检索领域的主要评测组织Text Retrieval Conference (TREC)1996年
  所公布的测评标准,极大型文档集的标准
  是20G,而Google/cite{GOOGLE}在1998年就已经索引了147G共2千4百万篇网页。
  而现在正在进行的2006年中文Web检索评测使用的网页文档集大小
  为200G/cite{SEWM06}。其次在数据的格式上,万维网是一个完全无控制的大型
  异构文档集合/cite{SL}。且极具变化性。而信息检索的对象则经常是组织好的
  半结构化数据,比如:论文集、新闻集等。另外从数据的来源上看,互联网是
  从来没有对什么人可以上传什么数据加任何的限制/cite{SL}。

  虽然IR和SE系统间存在很大区别,但是它们的核心技术是一脉相承的,都是基
  于检索技术的。SE的检索系统是建立在IR技术之上的。广义的信息检索是研究
  信息的结构、分析、组织、存储和检索的学科。其中非结构化的文本信息检索
  是信息检索领域研究的重点,当前搜索引擎的检索系统就是这一类的信息检索
  系统/cite{PB}。本文要介绍的主要是SE系统中的检索子系统的技术。另
  外,Web上很大一部分数据是以文本形式出现的,所以自动分析Web上的文本数
  据具有很高的重要性。当然,这些技术也可以经过修改和细化来适应其它类型
  的数据,如:图像。音频和视频数据,但是本文将只介绍文本检索技术。

  /subsection{搜索引擎的简史}
  /label{ssec:se_hist}

  现代搜索引擎起源于Matthew Grey在1993年开发的World Wide Web Wanderer,
  它利用html网页之间的链接关系来探测Web的发展规模,但并不提供检索的功能。
  这种程序的工作方式就像在网上爬行,所以常形象的称为蜘蛛(spider)、爬
  虫(crawler)或者机器人(robot)。之后许多人在其基础上做了改
  进。1994年7月Michael Gauldin将一个蜘蛛程序和索引程序结合起来,创造了
  著名的Lycos/cite{LYCOS},这是第一个现代意义上的搜索引擎。之后Web信息
  开始了爆炸式的增长,搜索引擎也随之迅速发展。近年来全球最受欢迎的搜索
  引擎是Google/cite{GOOGLE},虽然起步较晚,但以其良好服务质量赢得了广大
  用户的亲睐。Google四次荣获Searchenginewatch读者选举出的“最杰出的搜索
  引擎”称号。其他著名主流搜索引擎还有:alltheweb/cite{ALW}、Ask
  Jeeves/cite{ASKJ}、HotBot/cite{HB}、Teoma/cite{TEOMA}、
  WiseNut/cite{WN}、Overture/cite{OVT}、Vivisimo/cite{VV},在中国比较著
  名的搜索引擎有百度/cite{BD}、天网/cite{TW}和中搜/cite{ZS}等。

  /subsection{搜索引擎的分类}
  /label{ssec:se_cate}

  按照搜集数据和提供服务的方式,搜索引擎可以被分为三类:

  /begin{itemize}
  /item 基于robot的搜索引擎。这种搜索引擎利用一个robot(或
    称spider、crwaler)的搜集程序自动搜集Web上的网页数据,并根据网页中
    的链接进一步搜集其他网页。再将搜集来的网页进行分析处理、建立索引,
    然后想Web用户提供检索服务。“搜索引擎”这个词的狭义上就是指这种基
    于robot的搜索引擎。
  /item 基于目录(directory,category)的搜索引擎。这种搜索引擎以人工的方
    式或者半自动的方式搜集信息,并事先将信息分门别类。检索时只在这些内
    容中搜索。目录一般都是依靠一群专职编辑来建立和维护的。Yahoo!就是这
    类搜索引擎最好的代表。
  /item Meta搜索引擎。也叫做元搜索引擎。其特点是本身并不搜集和处理信息,
    检索时将用户的查询请求发送给其他搜索引擎,再将这些搜索引擎的结果处
    理后返回给用户。比如上面提到的Vivisimo/cite{VV}就属于这一类。
  /end{itemize}

  按照提供服务所用的数据范围的不同搜索引擎还可以被分为一下两类:

  /begin{itemize}
  /item 通用型搜索引擎。这一类的搜索引擎致力于将Web上的数据“一网打尽”,
    讲究高覆盖率,可以应答各种检索。但检索结果往往很分散。
    如Google/cite{GOOGLE}、百度/cite{BD}/item 专用的、面向主题的搜索引擎。这一类搜索引擎有其独特的需求和应用
    特点,但处理的数据规模相对较小,用于应答本领域内的检索。例如本文下
    面将要介绍的天网数字资源搜索/cite{TWDIG}/end{itemize}

  从表/ref{tab:diff}可以看出这两种搜索引擎的不同点。

  /begin{table}
    /centering
    /caption{两种搜索引擎的比较}
    /begin{tabular}{@{} r | l @{}}
      /hline
      /textbf{通用搜索引擎} & /textbf{面向主题的搜索引擎} //
      /hline
      /hline
      数据量大 & 数据量小 //
      /hline
      更新慢 & 更新快 //
      /hline
      博而不精 & 精而不博 //
      /hline
      数据不完全 & 数据较万全 //
      /hline
      硬件需求高 & 硬件需求低 //
      /hline
    /end{tabular}
    /label{tab:diff}
  /end{table}

  /subsection{搜索引擎的未来}
  /label{ssec:se_future}

  搜索引擎的发展历史也有十几年了,现在搜索引擎的功能已经越来越强大,提
  供的服务也越来越多。但是搜索引擎的未来发展也面临着几个难题:

  /begin{itemize}
  /item 如何跟上Internet的发展速度。现在的Internet上信息量呈爆炸式增长,
    搜索引擎需要处理的信息也就越来越多。如何扩展搜索引擎的容量同时还要
    保持更新(up to date)速度成为搜索引擎未来重要的课题。
  /item 如何给用户提供更高质量的查询。搜索引擎处理的信息量越来越大,提
    供给用户的信息量也是迅速增长。但是用户的信息量需求却并没有与信息量
    同比增长,人们最关心的仍旧还是前几十个结果是否有用。检索的速度和精
    确率与召回率一样也成为搜索引擎未来的难题。
  /end{itemize}

  /section{天网中英文搜索引擎的剖析}
  /label{sec:tw_intro}

  /subsection{天网的历史和简介}
  /label{ssec:tw_hist_intro}

  天网/cite{TW}搜索引擎于1997年10月开始提供服务,是中国最早的搜索引擎。
  它由北京大学网络与分布式系统实验室开发并维护运行,搜集了中国范围内大
  量的网络信息资源,尤其是较全面地覆盖了中国教育科研网(CERNET)内的资
  源。天网目前的信息资源除已经超过3亿的网页外,还包括2000多万各种非网页
  类型的文件,是目前世界上最大的中文搜索引擎之一。本文所介绍的技术内容
  主要就是以天网为背景展开的。

  因为本文的所讨论的搜索引擎检索子系统的技术细节和之后面向主题的搜索引
  擎:天网数字资源搜索/cite{TWDIG}的设计和实现都是基于天网搜索引擎之上
  完成的,所以我们先介绍一下天网中英文搜索引擎及其检索子系统的结构。

  天网中英文搜索引擎是主要针对中国Internet上丰富的信息资源而开发的具有
  中文特色的搜索引擎。天网属于基于robot的的搜索引擎范畴,主要采用了基于
  服务器模式具有向导功能的搜索和提供文本摘要的方式。在实现中,天网使用
  了中文自动识别和中文编码自动转换技术、根据中文的语言特点和表达习惯对
  中文信息进行快速准确的检索技术等先进的中文信息处理和索引技术,从而大
  大地提高了中文信息的理解程度和发现、检索效率,同时也提高了汉语的查准
  率。

  目前,天网由若干个Gather(即robot)在主控的导向控制下,使用具有高度智
  能性和适应性的的信息发现算法搜集网页,提取关键词和摘要,形成原始数据
  库,然后在此基础上建立索引数据库。来自前端的的用户信息,传给检索服务
  器,经过查询优化后,产生结果返回给用户。天网搜索引擎的检索是基于词汇
  的,克服了中文分词的困难,同时具有中英文词汇自动学习的能力。它侧重于
  中文信息的发现,向全世界的中文用户提供准确、有效的网络中文信息。

  /subsection{天网的技术特征}
  /label{ssec:tw_tech_char}

  天网搜索引擎具有以下技术特征:

  /begin{itemize}
  /item 信息搜集符合Internet的相关协议和标准。
  /item 实用、高效的信息分析方法。
  /item 高度智能性和适应性的信息发现方法。
  /item 中文信息处理技术。
  /item 可扩展的分布式体系结构。
  /item 基于词汇的大型、高效的信息索引数据库和快速、准确的检索方法。
  /item 智能化、多功能的用户检索接口。
  /end{itemize}

  天网搜索引擎目前的访问量和搜集网页数已经达到了亿量级。系统已经大部分
  实现了分布式。“天网”由于才用了可扩展的分布式体系结构、查询缓存、索
  引数据库和检索数据库分开等先进、有效的技术,使得系统占用资源少、信息
  搜集速度快、用户查询相应时间快、召回率和准确率较高,已经达到实用化程
  度。

  /subsection{系统理论模型/protect/cite{LM}}
  /label{sec:tw_model}

  令集合$/mathbb{D}=/{w_{1}, w_{2}, /ldots, w_{n}/}$代表系统的词典,集
  合$/mathbb{P}=/{p_{1}, p_{2}, /ldots, p_{n}/}$代表当前保存的文档集合。
  则系统的索引可以表示为集合:

  /begin{equation}
    /label{eq:idx_def}
    /mathbb{R} = /{/langle w, p /rangle | r(w, p) > 0 /wedge w /in /mathbb{D} /wedge p /in /mathbb{P}/}
  /end{equation}

  其中$r$是一个函数,$r(w, p)$代表词汇$w$和网页$p$的相关度。

  一个搜索引擎可以定义为一个三元组:

  /begin{equation}
    /label{eq:se_def}
    /mathbf{S} = /{/mathbb{D}, /mathbb{P}, /mathbb{R}/}
  /end{equation}

  /subsection{天网总体结构}
  /label{tw_arch}

  这个系统主要由搜集子系统、索引子系统、查询子系统和日志分析子系
  统/footnote{有时候为了方便起见,将建立索引和提供服务放在一起,称为检
    索子系统。}等几个部分组成。

  系统结构如图/ref{fig:twarch}。更为详细的系统结构可以参看文
  献/cite{LXM}/begin{figure}
    /centering
    /includegraphics[width=12cm, height=5cm]{images/twarch}
    /caption{天网系统结构}
    /label{fig:twarch}
  /end{figure}

  /begin{itemize}
  /item 搜集子系统。包括主控、搜集器(spiders)和原始数据库。主控按照
    启发式算法优先选择重要的url并分派给各个蜘蛛,完成站点过滤,实现
    robot协议和域名解析并缓存等功能。搜集器按照http协议负责从Web上抓取
    网页,通常同时起动上百个搜集器一起工作,同时还要分析网页内容,调用
    切词软件提取索引项、摘要和url链接,记录网页元信息,并存入原始数据
    库。

  /item 索引子系统。索引器将原始数据库中的内容重新组织,建立索引数据库,
    以提高检索效率。

  /item 查询子系统。用户接口在截取用户的查询请求后,将它们转发给查询器,
    查询器根据查询词和索引数据库内容,找到匹配的网页后,进行相关度排序,
    最后通过用户接口返回给用户。

  /item 日志分析子系统。用户接口程序将用户的行为信息(包括用户查询词、
    用户点击的url和用户翻页情况等)记录到日志数据库。日志分析器跟踪用
    户行为,以提高搜索引擎的服务质量,如从日志中学习新词来动态更新词典
    中的内容。
  /end{itemize}

  天网搜索引擎的工作流程如图/ref{fig:twproc}/begin{figure}
    /centering
    /includegraphics[width=15cm, height=8cm]{images/twproc}
    /caption{天网搜索引擎的工作流程}
    /label{fig:twproc}
  /end{figure}

  /section{天网检索子系统的研究}
  /label{sec:retrieval_subsystem_research}

  /subsection{引言}
  /label{ssec:rs_firstofall}

  检索子系统研究的内容主要是如何对网页文档进行索引,为用户提供高性能的
  检索服务。在搜索引擎中,这一系统和信息检索领域的相关技术是一脉相承的,
  但同时也根据Web自身的特点发展了一些新技术。和传统的信息检索系统相比较,
  大规模搜索引擎的检索子系统面临着许多新的挑战。商业搜索引擎已经获得了
  巨大的成功,但是其技术属于商业机密,在激烈竞争的环境下是不会公开的,
  所以大规模通用搜索引擎的技术讨论也不多。文
  献/cite{SL}对Google在Stanford的原型系统做了一个较为全面的技术介绍,其
  中检索系统的设计和实现是其重点讲述的内容。文献/cite{YHF}是天网搜索系
  统的相关论文,文献/cite{PB}详细介绍了天网搜索引擎检索子系统的设计、实
  现和特色。文献/cite{GBH}主要讲述了面向主题的搜索引擎中的技术,并结合
  天网的实际系统介绍了面向主题的搜索引擎的搜集系统中的技术。

  /subsection{系统设计与结构}
  /label{ssec:rs_desigh_arch}

  搜索引擎检索子系统的设计宗旨无外乎提高检索效率和检索质量这两点。一个
  搜索引擎要实际提供服务,首先必须具有相当高的检索效率,没有一个用户会
  容忍1秒以上的响应时间。如何提高检索质量,更是一个棘手的问题。信息检索
  领域的相关工作相比已经相当成熟/cite{EG, PPP, RB, AS},而从检索质量上
  看,搜索引擎由于面临效率的压力,往往不得不在效率和质量间折衷,而不一
  定采用最好的检索模型。同时,统计表明,用户提供给搜索引擎的信息普遍较
  少,这也给搜索引擎的检索质量增加了难度。但是传统的信息检索只从文本内
  容上计算文档和查询的相关度,而Web环境下,除了大量的文本数据,还有大量
  其他信息可以为相关度计算提供辅助支持,比如网页内的html标签、url、链接
  关系、anchor text和网站目录数据等等。有效地利用这些信息是搜索引擎提高
  检索质量的一个重要途径。

  天网搜索引擎的检索系统的设计原则有两个:

  /begin{itemize}
  /item 追求系统效率和可扩展性。

  /item 力图通过一个集成的框架结构将各种有利于改善检索质量的技术集成到
    一起。这种框架结构体现在三个方面:
    /begin{enumerate}
    /item 文档表示。对一个文档可以有多种表示方式,包括索引项、半结构化
      的元信息和全局的网页属性。
    /item 用户信息需求的类别识别,以求对不同类别的信息需求提供最好的检
      索方案。
    /item 不同检索排序方式得到的结果的融合。
    /end{enumerate}
  /end{itemize}

  /begin{figure}
    /centering
    /includegraphics[width=12cm, height=6cm]{images/rsframe}
    /caption{检索子系统集成框架}
    /label{fig:rsframe}
  /end{figure}

  天网搜索引擎检索子系统的集成框架如图/ref{fig:rsframe}。在
  图/ref{fig:rsframe}中,外围方框表示检索子系统,在服务点(SE service
  point)处接受用户查询请求(User InfoNeed)。用户请求经过检索代理
  (Retrieval Agent)分类,进行检索策略的选择,调用索引服务提供的相应检
  索机制来完成检索。搜索引擎最基本的检索机制是基于关键词的布尔查询
  (Boolean OP)。通常用户输入的查询为自然语言方式,并不是一个布尔表达
  式,搜索引擎一般默认用户的输入查询词之间为与(AND)关系。为了提高检索
  效果,搜索引擎一般也扩展支持短语(Phrase)和邻近(Proximity)两种运算,
  并把用户默认的输入用Proximity运算来执行。Google成功的使用了链接分析技
  术/cite{SL, GOOGLE},为每个网页赋予一个全局的权值(PageRank)来表示网
  页的重要程度。网页的这种全局属性的检查在
  图/ref{fig:rsframe}中由Global Properties模块执行,除了PageRank,还可
  以包括根据权威的网站目录数据、用户反馈或人工编辑等方式得到的网站权
  值。Meta是元数据查询的执行模块,可以包括时间、文档格式、站点名称、分
  类类别等各种网页元数据,针对网页数据的信息提取技术可以融合到这一模块
  中。在检索系统框架中,网页文档的分类类别起着重要的作用。利用Meta模块
  返回的网页类别信息,Retrieval Agent可以进行类别聚合,把相同类别的网页
  集中显示给用户,这样一种方式可以更好的组织检索结果,提高检索效
  果。Semantic Constrains模块是语义的约束检查模块,这是建立在对网页文本
  中特定语义关系识别的自然语言处理技术之上,是实现回答自然语言问题的必
  要技术。

  天网检索子系统的具体实现也是是基于IR技术的。

  首先是检索模型和排序算法的选择。检索系统的相关性排序由多种因素综合决
  定。这其中,最基础的排序建立在信息检索的布尔模型和向量空间模型基础上。
  在Boolean OP模块中,首先执行布尔查询,得到的结果作为候选文档集合,然
  后按空间向量模型的相似度算法计算各个文档与查询的相似度,结果作为排序
  的基础。最后由 Retrieval Agent综合其它模块返回的信息,再进一步排序。
  排序采用一种分级排序算法,分为三个级别:查询词的邻近关系运算结果;查
  询词出现的位置,包括 Title、Anchor Text;相似度权值与其它的权值,如全
  局属性的PageRank权值。各种权值的通过线性方式组合起来。

  其次是索引的实现技术。索引的技术主要有三种:倒排索引、后缀数组和签名
  文件。对于大规模文档数据,倒排文件是一种高效率的索引组织方式,能够很
  好的支持多种检索模型,提供高性能的检索。研究领域对倒排文件的组织和检
  索效率做了大量的研究工作/cite{FHJJ, VA}。倒排文件的组织还需要在检索效
  率和更新效率上进行折衷。一般为了提高更新效率,倒排索引的索引项数据用
  链表方式分块存放有利,但会降低检索效率;反之,索引项数据连续存放有利
  于检索,而不利于更新。

  整个检索子系统采用分布式系统结构。当SE搜集的数据趋于大规模的时候,海
  量网页数据的索引是无法集中式完成的,此时分布式是解决数据规模和系统可
  扩展性的基本办法。文献/cite{AH}研究了倒排索引的物理组织对分布式查询的
  性能影响,认为最好的数据分布方式是“host index organization”,也就是
  每一个文档的全部索引项应该都分布在同一台处理机上。这种数据组织方式不
  仅可以最大限度降低节点间通讯开销,而且由于索引节点之间相互独立工作,
  整个查询系统有很好的容错性。天网检索子系统的系统构架如
  图/ref{fig:rsarch}/begin{figure}
    /centering
    /includegraphics[width=13cm, height=4cm]{images/rsarch}
    /caption{分布式检索子系统构架}
    /label{fig:rsarch}
  /end{figure}

  /subsection{系统的实现}
  /label{ssec:rs_implementation}

  系统的程序出于效率和稳定性的考虑,主要用C/C++语言编写,用gcc、g++编译
  器编译,用make工具管理代码,配合shell和perl等脚本程序
  和crontab在UNIX/Linux环境下运行和维护。

  /subsubsection{索引的创建}
  /label{sssec:index_creation}

  /noindent
  /emph{/textbf{主要数据结构}}

  首先我们介绍一下本系统中主要的数据结构。表/ref{tab:rs_data_structs}是
  系统中数据结构的列表和简单描述。

  /begin{table}
    /centering
    /caption{天网搜索检索子系统中重要的数据结构}
    /begin{tabular}{@{} r | l @{}}
      /hline
      /textbf{名称} & /textbf{简单描述} //
      /hline /hline
      /textbf{CDatabase} & 所有操作BDB数据库文件的基类。//
      /hline
      /textbf{CRecDatabase} & 用来操作小规模数据文件的基类。//
      /hline
      /textbf{CWebDataDB} & 原始网页库的类,由CDatabase继承而来。CFeeder结构中包含此类。//
      /hline
      /textbf{CWebTextDB} & 网页摘要库。切词后将保存网页的主要内容,以便检索时能找到//
      & 查询结果的摘要。//
      /hline
      /textbf{CFeeder} & 用来读取原始网页库中的数据,原始网页库是BDB形式的文件。//
      /hline
      /textbf{CRepository} & 用来操作和保存切词结果。//
      /hline
      /textbf{CDocument} & 用来记录每篇网页的全局信息,主要是pagerank等排序和网页权重//
      & 信息。//
      /hline
      /textbf{CUrlInfo} & 记录每个url的信息,在建立字典文件的时候需要根据此结构找到//
      & 切词结果文件的对应位置。//
      /hline
      /textbf{CTaskDB} & 将原始网页集分配成若干子任务时使用。//
      /hline
      /textbf{CTask} & 用来分配和执行子任务。如切词、建词典、倒排等。//
      /hline
      /textbf{CTaskConf} & 读取任务配置文件。//
      /hline
      /textbf{CDictionary} & 内存中的词典文件结构,在内存中是以散列的形式存在的。//
      /hline
      /textbf{CInvFile} & 建立词典、Chunk文件和倒排文件的时候需要用到的类。//
      /hline
      /textbf{CChunk} & 用来操作和保存Chunk文件,Chunk文件的内容是指向倒排文件的//
      & 指针。它会存在于内存中,用于查询。//
      /hline
      /textbf{CParser} & 用来对网页数据切词,利用语言所的切词程序。//
      /hline
      /textbf{CPattern} & 在保存切词结果时需要用到,能够得到每个词汇的具体信息,如//
      & 权重等。//
      /hline
      /textbf{CIndex} & 用来操作倒排索引。//
      /hline
      /textbf{CQuery} & 用来执行查询操作。//
      /hline
      /textbf{Srawhash} & 内存中散列表的一种数据结构。//
      /hline
      /textbf{Sfinehash} & 内存中散列表的一种数据结构。//
      /hline
      /textbf{CPageRankDB} & 用来记录网页的PageRank值。//
      /hline
    /end{tabular}
    /label{tab:rs_data_structs}
  /end{table}

  /begin{description}
  /item[class CWebDataDB:public CDatabase]
    /begin{tiny}
/begin{verbatim}

{
    int m_reckey;
    int m_recdata[2];
public:
    virtual int init ();
    virtual int initread (void *pkey);  
    virtual int initwrite (void *pkey, void *pdata);
    SWebPageData *getdata ();
    int getdatasize ();
    int read (int docid);
    int write (int docid, int recsize, char *pbuf);
};
/end{verbatim}
    /end{tiny}
  /item[class CWebTextDB:public CRecDatabase]
    /begin{tiny}
/begin{verbatim}

{
    int m_reckey;
    int m_recdata[2];
public:
    virtual int init ();
    virtual int initread (void *pkey);  
    virtual int initwrite (void *pkey, void *pdata);
    SWebTextRec *getdata ();
    int getdatasize ();
    int read (int docid);
    int write (int docid, int recsize, char *pbuf);
};
/end{verbatim}
    /end{tiny}
  /item[class CFeeder]
    /begin{tiny}
/begin{verbatim}

{
    CWebDataDB *m_pdb[MAXFEEDERNUMBER];
    CAnchorRawDB *m_panchordb;
    AnchorDB *m_panchordb_in;
    AnchorDB *m_panchordb_self;
    char *m_sContentBuf;
    char *m_sContentPart;
    int m_deleteBuf;
    int m_curtaskid;
    int m_curdocid;
    char m_path[MAXPATHLEN];
    int unpack_data (int taskid, int readcontent);
    int check_db (int taskid);
public:
    CFeeder (char *pPath = NULL, char *pContentBuf = NULL);
    ~CFeeder ();
    UrlInfoRec rec;
    char ip[16];
    int init (int fileid = -1);
    int fetchpage (int taskid, int key, int readcontent = 1);
    int nextpage (int taskid = -1, int readcontent = 1); 
    char *getbuf ();
    char *getpart (const char *part);
    int getdocid ();
};
/end{verbatim}
    /end{tiny}
  /item[class CDictionary]
    /begin{tiny}
/begin{verbatim}

{
    Srawhash *prh_dict;
public:
    CDictionary (int size);
    ~CDictionary ();
    int init (const char *fname, int order = 0);
    int save (char *fname);
    int store (char *key, int doccnt);
    int storeex (char *key, int freq, int docid);
    int store (char *key);
    int seek (char *word);
    int getsize ();
    int storeex (char *key);
    char *seekex (int code);
};
/end{verbatim}
    /end{tiny}
  /item[class CInvFile]
    /begin{tiny}
/begin{verbatim}

{
    CDictionary *pdictionary;
    CChunk *pChunk;
    Sfinehash *pDict;
    int curdocid;
    unsigned long loccnt;
    WORDINFO2 *pWordInfo;
    static int bUseDocView;
public:
    CInvFile ();
    ~CInvFile ();
    int startdocid;
    unsigned long *pBuf;
    int init_builddict (int startdocid, CDictionary *pdict);
    int process_doc_dict (char *pbuf);
    int end_builddict (char *dictname);
    int init_invf (int startdocid, CChunk *pchunk, Sfinehash *pdict);   
    int process_doc (char *pbuf);
    int save_invf (const char *invfile);
#ifdef _USE_COMPRESS_INVF_
    CGzFile finvf;
#else
    CFile finvf;
#endif
    int init_merge (char *invfile, CChunk *pchunk);
    int read_item (char *buf);
    static inline void set_weight (unsigned long &weight, int val, int &vipwordcnt)
    static inline void clear_weight (unsigned long &weight);
    static inline int get_tf (char val);
    static inline unsigned long get_typeweight (char val);
    static inline int is_extra (char val);
    static inline int forward_lookup (char *s);
    ...
};
/end{verbatim}
    /end{tiny}
  /item[class CChunk]
    /begin{tiny}
/begin{verbatim}

{
public:
    CChunk ();
    ~CChunk ();
    int nodenum;
    int doccnt;
    int loccnt;
    WORDINFO *pwi;
    int init_chunk (int node_cnt);
    int process_chunk (Sfinehash *pf_dict, const char *dictfile);
    int load_chunk (const char *chunkfile);
    int save_chunk (const char *chunkfile);
    int convert_wordinfo (WORDINFO2 *&pwi2);    
    int getbufsize ();
    int get_max_bufsize (int &encodesize);
    FILE *fchunk;
    WORDINFO wi;
    int load_chunkEx (const char *chunkfile);
    int load_wordinfo (int w_code);
    ...
};
/end{verbatim}
    /end{tiny}
  /item[class CParser]
    /begin{tiny}
/begin{verbatim}

{
    int dictsize;
    CUnicode unicoder;
    CDictionary dict;
    int *pbufwgt;
    char *pbuf1;
    char *pbuf2;
    char *psrc;
    char title[MAXTITLELEN + 1];
    char charset[MAXCHARSETLEN + 1];
public:
    CParser (int dict_size = 0, int use_static_buffer = 1);
    ~CParser ();
    int64 loccnt;
    int64 wordcnt;
    int doccnt;
    int init_parser ();
    int init_parser(int docid, int64 iloccnt, int64 iwordcnt, const char *fndict);
    int end_parser (const char *fdict);
    void init_page (char *psrcbuf);
    int convert_charset (char *headcs);
    int delete_tags();
    int cut_words (int debug = 0);
    int end_page ();
    CDictionary *get_dict ();
    int remove_tags (char *psrouce = NULL, char delimiter = ' ');
    static inline void clear_weight (int &weight);
    ...
};
/end{verbatim}
    /end{tiny}
  /item[class CIndex]
    /begin{tiny}
/begin{verbatim}

{
    int INDEXKEY, DICTKEY;
    static const int m_align = 8;
public:
    struct SIndexItem
    {
        int doccnt;
        int loccnt;
        int weightscale;
        unsigned int offset;
        int len;
        int extralen;
        int locidxptr;  
    };
    int m_extramode;
    int m_nItems;
    int m_bShare;
    SIndexItem *m_pIndex;
    Sfinehash *m_pdict;
    CIndex ();
    ~CIndex ();
    FILE *m_fout, *m_fblkidx;
    char *m_pblock, *m_pbitmap;
    CByteCode m_coder;
    int m_wordnum;
    int load (char *fname = MAINIDX, char *fndict = MAINDICT);
    int loadEx (char *fname = MAINIDX);
    int unload (char *fname = MAINIDX, char *fndict = MAINDICT);
    int attach (char *fname = MAINIDX, char *fndict = MAINDICT);        
    int saveidx (char *fnidx = MAINIDX, char *fnlocidx=LOCLISTIDX);             
    int init_build (int nodenum, char *fname = MAININVF, char *fnblkidx = LOCLISTIDX);  
    int process_node (char *pbuf, WORDINFO &wiex);
    int dump_node (CIndex &srcindex, int srccode, int code, int idxonly = 0);
    int end_build (char *fnidx = MAINIDX, char *fnlocidx = LOCLISTIDX);         
    ...
};
/end{verbatim}
    /end{tiny}
  /item[class CQuery]
    /begin{tiny}
/begin{verbatim}

{
private:
    int PROXINTERVAL;
    int idlist[MAXQUERYWORDNUM + 1];
    int sort_query (int type);  
    int m_fastmode;
    inline int issingleword (int id);
    inline int isexactword (int id);
    inline int isproxiword (int id);
    inline int isignoreword (int id);
    inline char *getkeyword (char *p);
    char *plocblk;
    unsigned long *ploclist, *pproxlist;        
    unsigned long *pmainproxlist;
    int m_mainproxinit;
    int m_ntruephrasecnt;
    int m_ntruekeywordcnt;
    int m_mainproxcheck;
    int check_filters (char *querystring);
    int execute_class (CQueryResult & qr);
    int execute_and (CQueryResult &qr);
    int execute_phrase (CQueryResult &qr, int startpos, int endpos);
    inline int VALIDATE_FILTERS (int docid);
public:
    struct SKeyword
    {
        char *keyword;
        int code;
        int phraseid;
        int loc;
        int depth;
    };
    struct SPhrase
    {
        int type;
        int bsentence;
        int wordcnt;
    };
    int start_query (int logmode = 0);
    int close_query (int logmode = 0);
    int init_query (const char *querystring);
    void make_words ();
    int execute (Squeryresult *qd);
    inline int dogroup();
    ...
};
/end{verbatim}
    /end{tiny}
  /item[struct SRawhash]
    /begin{tiny}
/begin{verbatim}

{
  int list_num;
  unsigned int remainder[256];
  int cur_code;
  Srawhashlist *list;
  Srawnodeblock *nblock;
  Srawnodeblock *cur_nblock;
  Srawnode *cur_node;
  int node_avail;
  Scharblock *cblock;
  Scharblock *cur_cblock;
  char *cur_char;
  int char_avail;
  char **gWordPtrArray;
  int gWordPtrArraySize;
} Srawhash;
/end{verbatim}
    /end{tiny}
  /item[struct Sfinehash]
    /begin{tiny}
/begin{verbatim}

{
  int list_num;
  unsigned int remainder[256];
  char *pshm;
  Sfinehashlist *list;
  int node_num;
  Sfinenode *node;
  int cur_noff;
  int char_num;
  char *pchar;
  int cur_soff;;
} Sfinehash;
/end{verbatim}
    /end{tiny}
  /item[class CPageRankDB:public CDatabase]
    /begin{tiny}
/begin{verbatim}

{
public:
    virtual int init ();
    virtual int initread (void *pkey);  
    virtual int initwrite (void *pkey, void *pdata);
    float getrec (int mttype = 0);      
};
/end{verbatim}
    /end{tiny}
  /end{description}

  完整的实现见附录/ref{sssec:index_data_structs}/noindent
  /emph{/textbf{索引创建的算法}}

  /begin{enumerate}
  /item 分配子任务。将整个原始网页库先分成若干个子任务。对于原始网页数
    据库文件(BDB格式/footnote{Berkeley DB格式的数据库文件。}),建
    立task.db的文件。创建子任务的过程以及task.db文件文件的功能:

    /begin{enumerate}
    /item 每条记录对应于一篇文档。
    /item 删除冗余的记录。
    /item 为每条记录分配一个唯一的文档号。
    /item 每条记录对应于原始网页库文件记录的key值。
    /end{enumerate}

    此外根据记录数,为以后的功能文件url.dat(只设文档号,并按文档号从大到
    小排列)和moonshine.doc.idx分配空间。moonshine.doc.idx记录了文档的
    全局信息,其中记录了下面信息:

/begin{verbatim}
struct  SDocument {
  unsigned int weightscale:10;
  unsigned int siteno:22;
  float pagerank;
  unsigned int classid:22;
  unsigned int type:8;
  unsigned int deleteflag:1;
  unsigned int homepage:1;
  unsigned int modifytime;
};
/end{verbatim}

  /item 切词。从原始网页库中读出原始网页数据,对数据进行编码转换、去标
    签、切词,切词即按html语法规则分析网页标签结构、调用中文分词软件和
    英文词法分析器提取索引项。在此过程中将产生三个主要功能文件:

    /begin{description}
    /item[keylist.dat] 以记录形式保存每个网页的切词结果。分析过程中记录
      每个索引项的文档频率$/mbox{df}$和文档内频率$/mbox{tf}$,通过散列
      表转换为索引项编码。
    /item[url.dat] 空间在前面已经分配,此时将实际内容添加,其主要功能就
      是keylist.dat的索引文件。
    /item[WebText.db] 用BDB保存去除html标签的网页,用于以后
      做网页摘要库用。
    /end{description}

  /item 创建词典。根据切词得到的切词结果(keylist.dat文件)建立词典文件。
    由于在切词的过程中已经在内存里建立好了词汇的散列,这里只需要根据切
    词结果将词汇以一定的格式存到文件中即可。产生dict.dat文件。

    做字典时首先打开文件url.dat, 文件存储着每条url的描述信息:

/begin{verbatim}
typedef struct {
  char  url[MAXURLLEN];
  char  charset[16];
  int   wordcnt;
  int64 startloc; 
  int64 dataoff;
  int   datalen;
  int   pagesize;
  int   lastmodifytime;
} UrlInfoRec;
/end{verbatim}

    根据字段dataoff,便可知道前面对此url内容的切词结果在文
    件keylist.dat的位置,将结果读入内存中,然后以此把每个此加到用于统计
    词出现的频率的字典里,该字典的结构是hash表+链表。

    把所有词加到字典后,把它们保存到文件中,在保存的过程中,按顺序为每
    个词编号,便得到了字典文件dict.dat:

/begin{verbatim}
| h_num | w_freq | doc_feq | w_num | 
------------------------------------
    1          1        1     89511 柴灰
    2       6104      660      2514 0
    3       7395     1902       436 1
    4       5465     1603        84 2
...
/end{verbatim}

    此时的h/_num和w/_num以后是没有用的。之后会把所有任务的dict.dat加到
    一个文件中

/begin{verbatim}
| h_num | w_freq | doc_feq | w_num |
------------------------------------
    1        1        1        0 文化学
    2        3        3    55474 旗袍
    3        1        1        1 操纵者
    4        3        3    55475 教鞭
    5        1        1        2 汗毛
...
/end{verbatim}

  /item 创建倒排文件和Chunk文件。按统计得到的索引项
    的$/mbox{tf}$$/mbox{df}$属性,可以估计出对应倒排数据的长度,以此
    预申请整个文档集合倒排所需要的的内存空间。重新读取页面分析保存的分
    析结果临时文件,在内存中执行倒排,把结果保存到临时文件。生
    成key.invf和dict.dat.chunk文件。

    chunk文件事实上是倒排文件的指针文件,里面存这这样的记录:

/begin{verbatim}
typedef struct {
  unsigned long locfreq,docfreq;
  unsigned long ptr;
  //public interface
  inline int getitemsize ()
  {
    return docfreq * 12 + locfreq * 4;
  }
} WORDINFO;
/end{verbatim}

    记录的个数则是字典的词数。建倒排文件时,对于每条记录将切词的结果
    按dict.dat.chunk填入key.invf。

  /item 将每个子任务的词典文件、chunk文件、网页摘要库和倒排文件合并成大
    的词典文件、chunk文件、网页摘要库和倒排文件。生
    成dict.dat、dict.dat.chunk、//WebText.db、moonshine.invf。
  /end{enumerate}

  /noindent
  /emph{/textbf{倒排索引模型}}

  下面我们详细介绍一下检索子系统中的倒排索引模型。 在切词和创建词典之后,
  实际上是建立了一个以文档号为主键的正向索引。为了查询的需要,必须建立
  反向索引。这个过程如图/ref{fig:rs_inversion},为了加快速度,全过程在
  内存中进行。正向索引即图/ref{fig:rs_inversion}中左边部分,反向索引创
  建完成后如图/ref{fig:rs_inversion}中右边部分。这个由正向索引建立反向
  索引的过程就称为倒排(inversion)。图/ref{fig:rs_invf}是一个简单的倒
  排过程的实际例子。

  /begin{figure}
    /centering
    /includegraphics[width=10cm, height=4cm]{images/inversion}
    /caption{倒排的过程}
    /label{fig:rs_inversion}
  /end{figure}

  /begin{figure}
    /centering
    /includegraphics[width=10cm, height=7cm]{images/invf}
    /caption{倒排的简单例子}
    /label{fig:rs_invf}
  /end{figure}

  倒排索引结构如图/ref{fig:rs_inv_arch}。在实际系统中,倒排文件往往很大
  (达到1G左右),所以无法直接调入内存,所以通常在内存中存储以索引项的
  倒排表项的偏移位置组合的ISAM信息,这就是上面提到的chunk文件的作用。

  /begin{figure}
    /centering
    /includegraphics[width=12cm, height=6cm]{images/invarch}
    /caption{倒排索引结构}
    /label{fig:rs_inv_arch}
  /end{figure}

  可用图/ref{fig:rs_index_algo}表示整个索引创建的算法。

  /begin{figure}
    /centering
    /includegraphics[width=10cm, height=12cm]{images/idxalgo}
    /caption{索引创建算法}
    /label{fig:rs_index_algo}
  /end{figure}

  /subsubsection{信息查询服务}
  /label{sssec:search_system}

  查询服务是搜索引擎三段式工作流程的最后一个环节。查询服务包括接受用户
  输入的查询短语、检索、获得相应的匹配结果并返回给用户。经过之前的环节,
  我们已经得到了索引网页库和索引文件,现在要做的就是利用这些结果和用户
  沟通。

  现在我们看看查询服务系统的结构,如图/ref{fig:rs_retrieval_arch}/begin{figure}
    /centering
    /includegraphics[width=8cm, height=5cm]{images/retrievalarch}
    /caption{查询服务系统结构}
    /label{fig:rs_retrieval_arch}
  /end{figure}

  下面我们给出检索的理论模型/cite{LM},这部分与前面所讲的搜索引擎的理论
  模型相承,回顾一下公式(/ref{eq:idx_def})和公式(/ref{eq:se_def})。

  首先我们定义系统的元查询──单个词汇的查询:

  /begin{equation}
    /label{eq:meta_search}
    WP(w) = /Psi(w, /mathbf{S}) = /{p | /langle w, p /rangle /in /mathbb{R} /wedge p /in /mathbb{P}/}
  /end{equation}

  其中,$WP(w)$代表查询词$w$的相关文档集合,$/Psi$函数是系
  统$/mathbf{S}$的元查询函数。

  大部分用户在查询的时候输入的是词组或者一个完整的自然语句。查询系统首
  先要从用户的输入中提取相应的关键词(索引项)。我们定义关键词提取函
  数$g:W = /{w_{1}, w_{2}, /ldots, w_{n}/} = g(q, /mathbb{D})$,即通过
  函数$g$我们获得查询输入$q$的相关关键词集合。由公
  式(/ref{eq:meta_search}),关键词$w_{i}$的相关文档集合为$WP(w_{i})$,
  显然最终查询输入$q$的对应结果为:

  /begin{equation}
    /label{eq:retrieval_def}
    WP = f(WP(w_{1}), WP(w_{2}), /ldots, WP(w_{n})) = h(q, /mathbb{D}, /mathbb{P}, /mathbb{R}) = h(q, /mathbf{S})
  /end{equation}

  其中,函数$f$表示从$q$中提取的关键词的逻辑关系运算式,$f(q,
  /mathbf{S})$是系统查询的抽象表达式。由公式(/ref{eq:meta_search})和公
  式(/ref{eq:retrieval_def})可以看出,文档的关键词(索引项)在查询过程
  中起到了桥梁作用,索引项选择的好坏直接影响到查询结果的质量。

  查询的过程实际上是根据用户输入的查询词(短语),在倒排索引中查询,产
  生结果集的过程。查询的算法如下:

  /begin{enumerate}
  /item 初始化。将结果集和累加器归零。
  /item 分析查询词。将用户输入的查询词切分为索引项集合。
  /item 读取倒排项。对每个切分得到的索引项在倒排索引中读取相应数据。
  /item 执行布尔查询。将每个索引项得到的文档集执行布尔运算,一般是取交
    集,即AND运算。得到结果集。
  /item 排序根据每个文档集中的权值数据将结果集重新排序。
  /item 返回。将前k个结果返回给用户。
  /end{enumerate}

  查询的过程可用图/ref{fig:rs_retrieval_proc}表示。

  /begin{figure}
    /centering
    /includegraphics[width=12cm, height=6cm]{images/retrievalproc}
    /caption{查询的流程}
    /label{fig:rs_retrieval_proc}
  /end{figure}

  /subsubsection{查询结果相关度排序}
  /label{sssec:rs_ranking}

  当原始文档集很大的时候,用户的查询结构集也可能会非常大,用户通常没有
  耐心浏览到所有的结果,况且有些结果可能根本就“不太相关”。这个时候,
  最好的办法就是把和用户的查询词最相关的结果排在查询结果的前面,将查询
  结果按照相关度排序。

  针对文本文档,传统的信息检索中最经典、最具影响力的的相关度排序方法
  是Gerald Salton等提出的“向量空间模型”/cite{GM}。该模型将文档和用户
  的查询词都看作索引项的向量,以某种方法来判定查询向量和每一篇文档向量
  间的空间距离作为相关度的评价。这种方法将文本近似看作索引项的集合,完
  全忽略了语法和语义。但从具体实现的角度来看,这种理论能够很方便地在倒
  排索引的模型上实现。

  但是在传统的信息检索领域的方法却并不能适应Web的情况:

  /begin{itemize}
  /item 信息检索系统中的文档集通常有很高的质量,很多IR系统都是针对一个
    特定的领域的。而Web上网页的质量经常是参差不齐的,大量的网页没有组织
    性和结构性。Web又是一个无所不包括的信息海洋。另外Web上还有很多根本
    就没有意义的内容,还有很多镜象网页。
  /item 大部分搜索引擎的用户都是没有任何经验的,而IR系统的用户常常是具
    有一定相关技能和知识的,通常IR系统都提供一套相当复杂的检索语法来满
    足用户的需要。搜索引擎的用户通常只输入一个或者两个查询词来检索他们
    想要的网页,但是会得到大量的结果。很少有用户会去用搜索引擎提供的检
    索语法。
  /end{itemize}

  尽管网页的复杂性给检索系统带来了很多麻烦,但同时其中复杂性也给我们带
  来了很多新的机会和资源。当前研究的最多的两个方面就是网页间的链接关系
  和用户行为分析。

  /noindent
  /emph{/textbf{链接分析}}

  网页和普通文本的区别主要反应在两个方面:html的标签和网页间的超链接。

  /begin{itemize}
  /item html的标准里有丰富的标签,包括字体、字号、布局的变化和控制。因
    此标签往往能够给我们提供一些信息,比如:在$/langle$h$/rangle
    /langle$/h$/rangle$标签内的内容就会比网页中一般文本要重要。许多搜索
    引擎已经很好的利用了这些信息,在预处理阶段记录下这些信息,用于相关
    排序。
  /item 超链接反映了网页间的“联系”、“参考”、“引用”和“推荐”的关
    系。在这方面最著名研究成果就是Google的PageRank/cite{LP}技术和IBM公
    司的Clever小组的HITS技术。
  /end{itemize}

  天网搜索的检索子系统中使用了标签分析和PageRank技术。下面我们详细介绍
  一下Google的PageRank技术。

  PageRank是一种客观的评价Web中网页重要性的技术,它和向量空间等信息检索
  模型一起来决定查询结果的排序。PageRank技术是基于网络图(Web Graph)和
  网页间的链接关系的,它对Web中每一篇搜集到的网页都会计算出一个对应的排
  序值。

  Web上每一篇网页都有若干个超链接,这些超链接从一篇网页出发,指向某一篇
  网页。这样看来,可以将Web用一个图表示出来(
  图/ref{fig:rs_web_graph})。

  /begin{figure}
    /centering
    /includegraphics[width=8cm, height=6cm]{images/webgraph}
    /caption{Web Graph}
    /label{fig:rs_web_graph}
  /end{figure}

  如图/ref{fig:rs_web_graph},每个超链接都会给整个图贡献一个出度,同时
  贡献一个入度。直观上来看,Google的PageRank模型将一篇网页指向另一篇网
  页的超链接看作一篇网页对另一篇网页的投票。首先,一篇有很多投票的网页
  是重要的,其次,一篇拥有重要网页投票的网页也是重要的。每篇网页的投票
  都有不同的权重。而用一句话描述PageRank的思想就是/cite{LP}/begin{quotation}
    A page has high rank if the sum of the ranks of its backline is
    high.
  /end{quotation}

  这句话就包含了上面描述的PageRank的思想的两个方面。
  图/ref{fig:rs_pagerank}表示了PageRank工作原理。

  /begin{figure}
    /centering
    /includegraphics[width=8cm, height=6cm]{images/pagerank}
    /caption{PageRank的工作原理}
    /label{fig:rs_pagerank}
  /end{figure}

  下面我们对PageRank做一个定义:设$u$是一篇网页,$B_{u}$则是指向网
  页$u$的网页集合,$F_{u}$是网页$u$所指向的网页集合。设$N_{u} =
  |F_{u}|$为一篇网页上链接的出度。$c$是一个用来规范化的常数因子(使这
  个Web中所有网页的PageRank值为一个常数)。设$E(u)$为网页$u$一个对应的
  值。则网页$u$的PageRank值$R$定义如下:

  /begin{equation}
    /label{eq:pagerank}
    R(u) = c /sum_{v /in B_{u}}{/frac{R(v)}{N_{v}}} + c E(u)
  /end{equation}

  其中$||R||_{1} = 1$(如果将$R$看作是一个向量)。

  换一种方式表示:设$A$为一个方阵,行数和列数等于Web上的网页数,
  让$A_{u,v} = 1 / N_{u}$如果从网页$u$到网页$v$有一个链接,否
  则$A_{u,v} = 0$。我们将$R$$E$也看作和Web上的网页对应的向量,则可以
  用矩阵的形式表示这个公式:$R = c (AR + E)$。又因为$||R||_{1} = 1$,可
  以重写公式为:$R = c (A + E /times 1)R$。可见$R$实际上是矩阵$(A + E
  /times 1)$的主特征向量。

  为了解释这个公式,Google有一个直觉模型──随机冲浪者模型:假设有一个
  随机冲浪者在网上随机的选择一篇网页开始浏览,随机地选择网页上的超链接
  点击,从来不点击后退。则这个随机冲浪者浏览到某一篇网页的概率就对应了
  这篇网页的PageRank值。然而这其中会出现一个问题,如果Web中网页集中有一
  个子集,这个子集只有入度没有出度,那么这个随机冲浪者就会在这个子集里
  循环而出不来。解决的办法就是向量$E$,Web中每篇网页所对应的$E$值指的是
  这个随机冲浪者在这篇网页上刚到“厌倦”的概率,一旦他“厌倦”了,就再
  次随机选取一篇网页开始浏览。然而向量$E$的作用还不止如此,它还可以用来
  将PageRank值个性化,具体见文献/cite{LP}/subsection{小结}
  /label{ssec:rs_end}

  本章我们介绍了天网搜索引擎检索子系统的各项主要技术。笔者是从通用型搜
  索引擎的角度去介绍的,但这些技术也能用到面向主题的搜索引擎中去,这些
  技术是通用的。首先我们介绍了天网搜索引擎检索子系统的整体框架,然后介
  绍了天网检索子系统中重要数据结构,分析了索引创建的算法和倒排索引的模
  型,之后介绍了查询服务部分的模型和算法。本章最后介绍了天网检索子系统
  中的相关度排序(Google的PageRank)的算法。

  这些技术在现代大型搜索引擎中的应用是很广泛的,也是一个搜索引擎的核心
  技术。下面我们将展示一个实际的面向主题的搜索引擎技术的方方面面,我们
  将详细介绍如何在通用型搜索引擎检索子系统的基础上建立一个面向主题的检
  索子系统。

  /section{天网数字资源搜索/protect/cite{TWDIG}}
  /label{sec:tw_dig}

  /subsection{项目概述}
  /label{ssec:tw_dig_firstofall}

  天网数字资源搜索(简称DRSE)是一个面向主题的搜索引擎。 http数字资源,
  指存在于网页中的对用户有用的数字资源,比如某个网页中包含一个软件的名
  称,大小,软件语言,软件描述,下载地址等信息,这些信息就描述了这个软
  件本身以及在哪里可以下载,它就是我们说的一个http数字资源。总的来
  说,http数字资源包括对资源的描述和下载地址两部分内容。http数字资源可
  以包括软件,音乐,书籍,图片等等类型。

  和传统的ftp文件及maze文件不同的是,从网页上获得的数字资源通常带有一些
  与资源内容相关的描述信息,而且可能有多个下载地址,但不方便获得他们的
  大小,文件后缀,创建日期等文件信息。

  该系统已经与2006年5月1日开始成功地运行了一段时
  间(http://www.tianwang.com/),欢迎大家提出宝贵的意见。

  /subsubsection{用户需求分析}
  /label{sssec:tw_dig_reqana}

  网页上的这种数字资源对用户是有用的,但它们分散在众多的网站和网页上,
  当用户需要某种资源时,用户就需要访问不同的网站及网页以获得它们,这是
  很不方便的。原因是当用户想找到某个资源时,他通常不会知道很多可能含有
  该资源的网站和网页,即使知道,他也不太可能访问每个网站去寻找该资源,
  况且某些网站并不提供站内的检索服务。

  因此,用户需要我们把这些网页上的数字资源的信息归类集中起来,并且以某
  种方式让用户方便地找到并下载。

  常见的http数字资源包括软件,音乐,书籍,影视等等,凡是存在于网页中对
  用户有用并包含下载地址的信息实体都可以包含在其中。这里以软件和音乐为
  例来进行说明。

  包含软件数字资源的网页通常含有软件的名称,大小,软件语言,软件描述,
  下载地址等信息项,而包含音乐数字资源的网页通常含有音乐名,作者,专辑,
  下载地址等信息项。可以看出,每大类http数字资源会具有不同的属性项来描
  述它们。

  对于每一个大类数字资源,比如软件,又可以分为很多小的类别,这种分类是
  有意义的,因为用户可以在查找时限定更小的范围,提高效率和准确度。

  /subsubsection{用户使用的设计}
  /label{sssec:tw_dig_user_disign}

  DRSE的目标是:集中分散在众多网站和网页上的各类数字资源信息,并以某种
  方便高效的方式提供给用户直接下载(这种方便高效不仅体现在技术上,还要
  体现在对以怎样的方式把信息呈现给用户的设计上)。

  天网数字资源搜索的系统构架如图/ref{fig:tw_dig_arch}/begin{figure}
    /centering
    /includegraphics[width=12cm, height=6cm]{images/drarch}
    /caption{天网数字资源搜索系统构架}
    /label{fig:tw_dig_arch}
  /end{figure}

  DRSE提供的服务:

  /begin{enumerate}
  /item 普通检索功能。用户仅输入查询词,不选择资源类别等信息,返回所有
    类型的相关的数字资源结果。查询词可能存在于资源名中,也可能存在于对
    资源的描述中,但资源名包含查询词的结果应该输出在前面。针对一个资源
    有多个下载地址的情况,要求能够返回多个下载地址给用户。
  /item 分类检索功能。用户输入查询词,并选择数字资源的大类或小类,返回
    给用户该类型的相关的数字资源结果。查询词可能存在于资源名中,也可能
    存在于对资源的描述中,但资源名包含查询词的结果应该输出在前面。针对
    一个资源有多个下载地址的情况,要求能够返回多个下载地址给用户。
  /item 小类资源浏览。用户仅选择资源小类别,不输入关键词,返回给用户所
    有该小类资源的结果。这是为了满足用户浏览的需要,可能用户并不知道要
    找什么,他就想随便看看而已。
  /item 最新资源推荐。为每一个数字资源小类别产生一个最新资源推荐,这应
    该是最近抓回来的信息,可能还没在索引里。比如,我们可以二十分钟更新
    一次这些推荐网页,以达到动态的效果。
  /end{enumerate}

  /subsection{系统特色}
  /label{ssec:tw_dig_char}

  如前所述,DRSE是一个面向主题的搜索引擎(见表/ref{tab:diff}),除了
  第/ref{ssec:se_cate}节所描述的整体上的区别以外,在DRSE中,这种区别还
  具体体现在下面两个方面。

  网页数字资源和传统文件资源的差异:

  /begin{enumerate}
  /item 传统文件的属性通常是文件名,文件大小,创建日期,文件后缀,文
    件url等,可以看出,这些属性除文件名可能表现文件内容外,其他的属性与
    文件内容本身关系不大。网页数字资源的属性含有更多的描述资源文件内容
    的信息,而这些信息对用户查找数字资源是有意义的。
  /item 传统文件的属性项都是一样的。网页数字资源的属性根据不同的资源类
    别不同,比如软件和音乐就有不同的属性描述项。
  /item 传统文件的分类查询是基于后缀的,也就是说不是基于内容的,例如压
    缩,文档,目录等。网页数字资源的分类查询应该是基于内容的,而不是基
    于后缀。
  /end{enumerate}

  和通用型搜索引擎相比,天网数字资源搜索有自己的特点:

  /begin{enumerate}
  /item 每大类数字资源有自己的属性项来描述自己的特征。
  /item 每类数字资源可以分为不同的许多小类别,这种分类是基于内容的,目
    的是方便用户使用。
  /item 每个数字资源可能对应多个下载地址。(需要把多个下载地址提供给用
    户)
  /end{enumerate}

  /subsection{系统结构}
  /label{ssec:tw_dig_arch}

  天网DR搜索系统的详细设计如图/ref{fig:tw_dig_archdetail}/begin{figure}
    /centering
    /includegraphics[width=12cm, height=8cm]{images/drarchdetail}
    /caption{天网数字资源搜索详细设计}
    /label{fig:tw_dig_archdetail}
  /end{figure}

  /subsubsection{各模块设计与接口}
  /label{sssec:tw_dig_models_interfaces}

  /begin{enumerate}
  /item 抓取模块。 抓取模块的功能是从指定的网站取回包含数字资源信息的网
    页并存为天网格式。

    一般来说,一个数字资源对应于一个网页及一个网页url,但有些数字资源的
    信息可能不全在一个网页上,例如,有些软件网站的软件资源的下载地址就
    和其他信息不在一个网页上,这时需要在程序里取回下载地址,并加入到保
    存的网页中供提取模块使用。

    还有的情况是一个资源对应多个下载地址,这时不方便在提取模块匹配模版,
    方便的做法是在抓取程序里提出多个下载地址并写入到保存的网页中供提取
    模块使用。

    抓取模块需要完成对抓取数字资源分类的工作,具体的做法是对抓取的种子
    指定所属的小类别ID(这是资源分类标准定义好的),然后把该小类别
    的ID写到保存的网页中供提取模块使用。

    需要为每个抓取的网站定制抓取程序(在原有基础上适当修改)及抓取的种
    子。
  /item 提取模块。提取模块的功能是把抓去回来的天网格式的网页中的数字资
    源信息提取出来并存为天网格式的xml文件。

    因为不同大类资源的xml格式不同,所以需要为不同大类的数字资源定制不同
    的提取程序(仅需修改输出和标签即可),并用相应的提取程序处理相应的
    站点网页数据。

    需要完成对各大类资源xml格式的定义,依据是上面提到的xml数据规范。需
    为每个站点匹配相应的提取模版。
  /item 资源发布平台。
    功能:
    /begin{enumerate}
    /item 提供资源发布界面供用户发布资源。
    /item 将用户发布的资源导成可建索引的xml文件。
    /end{enumerate}
  /item 数据转换。这部分主要是将数据转换为天网格式。
  /item 索引模块。下面将详细介绍,见第/ref{ssec:tw_dig_rs_implmt}节。
  /item 查询模块。查询模块的功能是响应用户的查询请求,通过与索引部分的
    交互返回相应的查询结果并以一定的网页效果输出。

    输出页面主要包括查询结果页和资源详细信息页。根据前面提到的系统功能,
    检索模块需要处理的用户查询请求分为以下几种情况:

    cgi-bin/tw?word=flashget/&cd=03

    普通查询,返回匹配该查询词的所有类别的数字资源结果,输出页面是查询
    结果页;

    cgi-bin/tw?word=flashget/&cd=0301

    分大类查询,返回匹配该查询词并属于0301大类的数字资源的结果,输出页
    面是查询结果页;

    cgi-bin/tw?word=flashget/&cd=030101

    分小类查询,返回匹配该查询词并属于030101小类的数字资源的结果,输出
    页面是查询结果页;

    cgi-bin/tw?word=/&cd=030101

    浏览小类别资源信息,返回该小类别数字资源的部分结果,输出页面是查询
    结果页;

    cgi-bin/detail?url=/ldots/ldots

    资源详细信息页面,返回该url对应的数字资源的详细信息,输出叶面是资源
    详细信息页。

    需要为查询结果页和资源详细信息页定制网页模版。因为各大类的详细信息
    页是不同的,所以需要为每一大类资源定制输出资源详细信息网页模版。
  /item 页面设计。网站风格应简洁、朴素,尽量减少图片的使用。为了方便维
    护和保证各个页面风格的一致性,应该将风格写入一个css样式文件。
  /end{enumerate}

  /subsubsection{数据说明}
  /label{sssec:tw_dig_data}

  由于DRSE的一个重要的特点是资源的分类,所以针对这个需求,我们定义了源
  数据分类的存储格式。这种数据格式需要满足所有的功能,还要有一定的可扩
  展性。

  /begin{description}
  /item[/textbf{数字资源分类及类别ID}] 数字资源的分类方法是:先分成大类,
    例如软件和音乐等大类;然后在大类里分为若干小类,例如,软件又可分为
    上传下载,系统软件,游戏娱乐等小类别。对于每一个小类,我们分配一个
    类别ID,该类别ID就代表这个小类,并且通过它我们可以知道该小类所属的
    大类。可用图/ref{fig:tw_dig_category}来表示分类及ID分配方法:

    /begin{figure}
      /centering
      /includegraphics[width=12cm, height=10cm]{images/drcategory}
      /caption{数字资源分类方法}
      /label{fig:tw_dig_category}
    /end{figure}

    大类别和小类别都分配了ID,方便处理用户分大类和小类检索的要求。大小
    类别形成树形结构,小类别ID包含了大类别ID,因此可以通过小类别ID知道
    大类ID,对于一个数字资源,我们只用保存它的小类别ID即可。
  /item[/textbf{xml格式}] 从系统的总体架构图可以看出,无论数字资源是来
    自各类资源网站还是来自用户发布,最后都会转换成xml格式的数据,提供给
    索引部分建立索引。这里,来自两部分的xml数据应该具有相同的格式,索引
    部分并不区分它们的来源。

    不同大类的数字资源将对应不同的xml格式,这是因为不同大类的数字资源会
    具有很多截然不同的描述属性。我们仅为每一个大类定义xml格式,大类下的
    小类使用所属大类的xml格式。

    xml格式中的信息项可以按两种方法来区分:一种是把所有信息项划分为公有
    信息项和私有信息项,公有信息项是指所有大类的资源都具有的信息项,私
    有信息项是指各大类独有的信息项。例如,数字资源名,下载地址,来源是
    公有信息项,而软件平台,软件语言,专辑名,歌手名等是私有信息项。需
    要注意的是,在定义xml信息项的标签时,共有信息项应该采用一样的标签,
    而私有信息项可采用不同的标签;另一种是把信息项分为索引相关项和索引
    无关项,前者指建立全文索引需要的信息项,后者指建立索引不需要的信息。
    例如,数字资源名,软件描述等时索引相关项,而下载地址,来源等是索引
    无关项。为了方便建立索引,我们规定,所有的索引相关项都必须包含
    在$/langle$Content$/rangle /langle$/Content$/rangle$标签里,这样建
    立索引时只需取出标签$/langle$Content$/rangle
    /langle$/Content$/rangle$里的内容建立索引,而不需要关心里面具体有哪
    些信息项。

    下面是软件的xml文档格式:

/begin{verbatim}
<?xml version=''1.0'' encoding=''GB2312''?>

<Project>
    <ResInfo>
        <Size>
        3914KB
        </Size>
        <Download>
        http://www.software.com
        </Download>
        <Content>
            <Name>
            Flashget中文版
            </Name>
            <Language>
            简体中文
            </Language>
            <Platform>
            Win9x/Me/NT/2000/XP
            </Platform>
            <Detail>
            This is a good ………
            </Detail>
        </Content>
    </ResInfo>
    <CategoryID>
    030101
    </CategoryID>
    <Source>
    www.onlinedown.net
    </Source>
    <Copyright>
    Netera Inc.
    </Copyright>
</Project>
/end{verbatim}

    这里,公有信息项包
    括CategoryID,Size,Download,Name,Detail,Source,//Copyright,这
    些信息项在音乐,书籍的xml里也应该有相同的标签名;余下的就是私有信息
    项,可以根据不同的资源类别而定。$/langle$Content$/rangle~
    /langle$Content$/rangle$标签里的是索引相关项,外面的是索引无关项。
    在定义新类别的xml格式时,要严格遵守这种规范。
  /end{description}

  /subsection{检索子系统的实现}
  /label{ssec:tw_dig_rs_implmt}

  天网数字资源搜索引擎的检索子系统是建立在天网通用搜索引擎的检索子系统
  的基础上的。在分析文档、建立倒排索引等方面是类似的。不同点则是,数字
  资源搜索需要实现资源的分类。针对DR搜索的分类检索的需求,我们需要在短
  时间内设计出一个方案。

  DRSE和通用型搜索引擎的实现上最大的不同点之一就是:通用型搜索引擎索引
  的是原始网页,而DRSE索引的是经过了信息提取的xml文档。所以在索引的建立
  上我们要利用xml文档结构化的特点。具体来说,我们需要另外实现一个“小索
  引”。小索引将单独对xml文档中CategoryID一项建立索引,在查询的时候,将
  “大索引”的结果集和小索引的结果集合并得到最终的结果集。

  /subsubsection{数据结构设计}
  /label{sec:tw_dig_rs_data_structs}

  小索引的主要数据结构见表/ref{tab:tw_dig_data_structs}/begin{table}
    /centering
    /begin{tabular}{@{} r | l @{}}
      /hline
      /textbf{名称} & /textbf{简单描述} //
      /hline /hline
      /textbf{DrIndexie} & 小索引的基类。//
      /hline
      /textbf{CategoryID} & 用来存储资源类别,用做小索引中的数据存储。//
      /hline
      /textbf{XmlParser} & 用来分析记录信息的xml页面。//
      /hline
      /textbf{DrQueryMessage} & 分析用户输入的查询词中的类别等信息,以便查询。//
      /hline
      /textbf{intersect} & 用来将小索引中查询出的分类结果和大索引的结果集合并。//
      /hline
    /end{tabular}
    /caption{天网数字资源搜索检索子系统数据结构}
    /label{tab:tw_dig_data_structs}
  /end{table}

  /begin{description}
  /item[class DrIndexie]
    /begin{tiny}
/begin{verbatim}

{
private:
    typedef     vector<ValueType>               T_IdVector;
    typedef     pair<KeyType, T_IdVector>       T_Pair;
    typedef     map<KeyType, T_IdVector, _Compare>      T_Item;
    typedef     typename        T_Item::iterator        Iterator;
    typedef     typename        T_IdVector::iterator    ValueVectorIterator;

public:
    DrIndexie ();
    DrIndexie (const char *);
    virtual     ~DrIndexie ();
    void        saveIndex2File (const string &indexFile) throw(DrIndexieException);
    void        loadIndexFromFile (const string &indexFile) throw(DrIndexieException);
    void        insertPair (const KeyType &key, const ValueType &value);
    size_t      query (T_IdVector &idVector, const KeyType &key);
    size_t      query (T_IdVector &idVector, const vector<KeyType> &keyList);
    size_t      size ();
    void        printKey ();
private:
    T_Item      mIndex;
private:
    class Serialize : public unary_function<T_Pair, void>
        {
        public:
    Serialize (ostream &os) : mOs(os){}
    void        operator() (const T_Pair &element) const;
        private:
    ostream& mOs;
        };
};
/end{verbatim}
    /end{tiny}
  /item[class CategoryID]
    /begin{tiny}
/begin{verbatim}

{
    friend ostream      &operator<< (ostream &os, const CategoryID &categoryid);
    friend stringstream &operator>> (stringstream &ss, CategoryID &id);
public:
    CategoryID (string val) : id_value(val) {}
    CategoryID (char * val) : id_value(val) {}
    ~CategoryID ()
    const char  *c_str () const;
    size_t      size () const;
    string      get () const;
    void        set (string val);
private:
    CategoryID () 
    string      id_value;
};
/end{verbatim}
    /end{tiny}
  /item[class XmlParser]
    /begin{tiny}
/begin{verbatim}

{
public :
    XmlParser ();
    XmlParser (const string &xmlBuffer);
    virtual ~XmlParser ();
    string getValueOf (const string &tagName);
    void input (const string &xmlBuffer);
public :
    void trim (string &src);
private :
    string              mXmlData;
    RegularMatch        mMatcher;
};
/end{verbatim}
    /end{tiny}
  /item[class DrQueryMessage]
    /begin{tiny}
/begin{verbatim}

{
public :
    DrQueryMessage ();
    virtual ~DrQueryMessage();
    int receive (const void *msg, int len);
    T_ResourceCode *getResourceCode (T_ResourceCode *resourcecode);

    friend ostream &operator << (ostream &os, DrQueryMessage &message);
private :
    int assignKeyLocation();
private :
    string                   mMsgBody;
    map<string, char *>      mKeyLocation;
};
/end{verbatim}
    /end{tiny}
  /item[class intersect]
    /begin{tiny}
/begin{verbatim}

{
public :
    Intersect ();
    virtual ~Intersect ();
    static int intersect (vector<int> &result, const vector<int> &left, const vector<int> &right);
    static int intersect (Squeryresult &result, const Squeryresult &left, const vector<int> &right);
    static int intersect (Squeryresult &result, const vector<int> &right);
};
/end{verbatim}
    /end{tiny}
  /end{description}

  /subsubsection{算法设计和分析}
  /label{sssec:tw_dig_rs_algo}

  由于xml文档的特殊性,程序的实现利用了xml文档的标签。为了程序的可扩展
  性,程序采用标准C++的STL库编写,如果以后还有其它的小项需要专门索引,
  代码的开发量会大大减少。

  小索引程序的实现,实际上是一个小的倒排文件。由于情况简单了很多(没有
  切词等步骤),我们只需要将分类id号从xml文档中提取出来,然后建立一个简
  单的倒排索引。程序采用STL是为了利用其良好的可扩展性。如
  第/ref{sssec:index_creation}节所述,倒排文件的结构如
  图/ref{fig:rs_inv_arch},而小索引的结构与其相似,程序中DrIndexie类是
  小索引的基本类,其它小索引的结构都可以从它得到。

  /begin{tiny}
/begin{verbatim}
template <class KeyType, class ValueType, typename _Compare>
class DrIndexie
{
private:
    typedef     vector<ValueType>               T_IdVector;
    typedef     pair<KeyType, T_IdVector>       T_Pair;
    typedef     map<KeyType, T_IdVector, _Compare>      T_Item;
    ...
private:
    T_Item      mIndex;
    ...
};
/end{verbatim}
  /end{tiny}

  DrIndexie的结构就是一个倒排文件,如上面的代码所示,这个倒排文件中的数
  据项是用标准STL中map类实现的,map中以一个对偶作为其数据项,这个对偶存
  储了资源类别ID(KeyType)及其对应的文档号
  (vector$/langle$ValueType$/rangle$,一般来说文档号类型是int型的
  )。map类的内部采用红黑树组织数据,对于DRSE的40个左右的分类来说,查询
  的效率是相当高的,当然,完全可以使用一个线形的结构,效率上也不会有什
  么差别,但是使用map的可移植性和可扩展性较高。

  在查询时分别在大索引和小索引中查询,然后将两次得到的结果合并,代码如
  下:

  /begin{tiny}
/begin{verbatim}
int
DrCategoryIDQuery (vector<int> &results, char *querystring)
{
    CategoryIDIndexie   categoryidindexie;
    T_CategoryID        categoryid ("000000");
    DrQueryMessage      drmessage;
    int                 ret, drret;

    ret = drmessage.receive (querystring, 1024);

    if (ret != 0) {
        return -1;
    }

    if (ret == 0) {
        T_ResourceCode  *ret = NULL;
        T_ResourceCode  resourcecode;
        ret = drmessage.getResourceCode (&resourcecode);

        if (ret != NULL) {
            if (resourcecode.size () == 6) {
                drret = 1;
                categoryid.set (resourcecode);
                categoryidindexie.loadIndexFromFile (CATEGORYIDINDEXIE);
                categoryidindexie.query (results, categoryid);
            } else if (resourcecode.size () == 4) {
                drret = 2;
                stringstream            ss;
                string                  subid;
                vector<CategoryID>      categoryids;

                ss.flags (ss.flags () | ios_base::dec | ios_base::right);
                for (int i = 1; i < 10; ++i) {
                    ss.fill ('0');
                    ss.width (2);
                    ss << i;
                    ss >> subid;
                    categoryid.set (resourcecode + subid);
                    categoryids.push_back (categoryid);
                    ss.clear ();
                }
                categoryidindexie.loadIndexFromFile (CATEGORYIDINDEXIE);
                categoryidindexie.query (results, categoryids);
            } else {
                drret = 0;
            }
        } else {
            drret = 0;
        }
    }

    return drret;
}
...
int
do_mqueryEx (char *querystring, T_normal_result *result , int noword)
{
    Squeryresult        qr;
    CQuery              query;
    int                 t_ret;
    int                 r_num;
    int                 drret;
    vector<int>         DrQueryDocList;
    
    drret = DrCategoryIDQuery (DrQueryDocList, querystring);

    querystring += *(unsigned short *)querystring;
    t_ret = query.start_query(1);
    t_ret = query.init_query(querystring);
    t_ret = query.execute(&qr);

    if (drret != 0 && !noword) {
        Squeryresult    intersectQr;
        Intersect::intersect (intersectQr, qr, DrQueryDocList);
        r_num = do_mngroupQr (&intersectQr, result);
        DrFreeSqueryresult (intersectQr);
    } else if (drret != 0 && noword) {
        Squeryresult    intersectQr;

        Intersect::intersect (intersectQr, DrQueryDocList);
        r_num = do_mngroupQr2 (&intersectQr, result);
        DrFreeSqueryresult (intersectQr);
    } else {
        r_num = do_mngroupQr (&qr, result);
    }

    query.close_query(1);

    DrFreeSqueryresult(qr);
    return r_num;
}
/end{verbatim}
  /end{tiny}

  如第/ref{ssec:tw_dig_arch}节,资源的分类是一个树形结构,不光有小类,
  还有大类,而具体的每个资源只属于某个小类。所以在大类中查询时,实际上
  是根据语义判断,如果用户查询的是大类,则在某个大类中所有的小类里查
  询。

  查询系统的瓶颈在数据的IO。以上代码中每次查询都需要从小索引的倒排文件
  中读取数据,当数据量较小时,如小索引倒排文件大小在500K左右,操作系统
  的文件系统调用可以在一次磁盘IO中读取数据,但数据量变大时,系统就会出
  现瓶颈。小索引需要改进的地方,是在服务起动的时候就应该将倒排文件数据
  读入到内存中。

  以下代码是大类查询的算法。当搜集的数据多起来的时候每个小类可能对应有
  上十万个文档号,这里通过一个vector将小索引结果数据输出,虽然尽量使用
  了STL的库函数,但数据量仍然太大。这也是接下来需要解决的问题。

  /begin{tiny}
/begin{verbatim}
size_t      query (T_IdVector &idVector, const vector<KeyType> &keyList)
    {
        Iterator    it;
        typename T_IdVector::iterator   pos;
        for (typename vector<KeyType>::const_iterator key_ptr = keyList.begin ();
            key_ptr != keyList.end (); ++key_ptr) {
            it = mIndex.find (*(key_ptr));
            if (it == mIndex.end ()) {
                continue;
            }
            int size = idVector.size ();
            idVector.resize (size + it->second.size ());
            pos = idVector.begin ();
            pos += size;
            copy (it->second.begin (), it->second.end (), pos);
        }
        return idVector.size ();
    }
/end{verbatim}
  /end{tiny}

  小索引的详细数据结构和实现见附录第/ref{sssec:indexie_arch}节。小索引
  创建算法见附录第/ref{sssec:indexie_creation}节。

  /clearpage
  /addcontentsline{toc}{section}{结束语}
  /section*{结束语}
  /label{sec:end}

  /subsection*{全文总结}
  /label{ssec:end_paper}

  本文围绕现代搜索引擎中检索子系统的技术,主要研究了检索子系统的整体构
  架,索引模型和信息查询服务方面的技术。细致地研究了一个实际系统:天网
  中英文搜索引擎的检索子系统的实现,其中索引创建的算法、倒排索引的模型、
  查询服务的算法和相关度排序是本文的重点。同时,作者通过为期两个多月的
  工程实践,成功地搭建了一个面向主题的搜索引擎:天网数字资源搜索。其间,
  作者在天网通用搜索引擎检索子系统的基础上,增加并修改了索引创建和查询
  服务部分的算法和实现,使之能够适用于这个特定的系统并良好的运行。

  本文的主要贡献在于:

  /begin{enumerate}
  /item 详细的剖析了天网搜索引擎检索子系统的系统模型和算法实现。特别是
    对索引的模型、创建的算法、查询服务和相关度排序进行了详细的讲解。
  /item 成功地搭建了一个面向主题的搜索引擎,设计一种对xml文档的特定项进
    行建立倒排索引的方法,并实现了相应的查询功能。代码具有很高的可移植
    性和可扩展性。
  /end{enumerate}

  /subsection*{未来工作的展望}
  /label{ssec:forward}

  随着Web在人们的生活中扮演的角色越来越重要,人们也越来越依赖网络提供的
  服务功能。现代网络已经成为了人们查找信息的首选工具之一,而搜索引擎正
  是满足用户查找信息的功能的重要网络工具。随着Web上的信息量越来越多,并
  且还在持续增加,如何,让用户找到想要的信息,未来的通用型搜索引擎遇到
  了很多技术上的挑战。而面向主题的搜索引擎正是为此而生的,将信息量减少,
  从而提供更高质量的检索服务。天网数字资源搜索引擎具有面向主题的特点,
  并结合了目录型搜索引擎目录导航的特点,是未来搜索引擎的一大趋势。

  但天网数字资源搜索目前的工作中仍然有不足之处,主要体现在:

  /begin{enumerate}
  /item 原天网通用搜索引擎的检索子系统中还存在很多复杂的部分,且并不适
    用于一个面向主题的搜索引擎。
  /item 查询服务的程序设计和算法仍有可改进的余地。
  /end{enumerate}

  /clearpage
  /addcontentsline{toc}{section}{致谢}
  /section*{致谢}
  /label{sec:acknowledgements}

  首先要感谢我的导师/textbf{杜刚}副教授,毕设期间为我提供了大量的信息和
  宝贵的帮助。其次,要特别感谢北大网络实验室/textbf{李晓明}教授,无私地
  为我提供技术资料,并数次指导我的研究工作。第三,感谢网络实验
  室/textbf {韩华}老师和/textbf{闫宏飞}老师,慷慨地为我毕设提供研究项目
  和硬件设施,并对我的研究进展提出了很多重要的建议。另外,还要感谢天网
  时代公司的/textbf{张明辉}和网络实验室的/textbf{王东海}/textbf{毕圣
    杰}学长,在工程实践的过程中总是如兄长一样耐心地回答我的每一个疑问。

  最重要的是,我要感谢我的/textbf{父母和家人},任何时候都无微不至地关心
  着我,没有他们的关心和爱,我是无法完成本科四年的学习和工作的。面对他
  们殷切的期望,我不敢稍有懈怠。他们一直是我精神和物质上的最坚实的支柱,
  希望我没有让他们失望。

  总之,感谢你们──我的/textbf{老师们}/textbf{朋友们}/textbf{同学
    们}对我的大力支持。四年本科的学海生涯,地大(北京)给了我良好的教育
  和严格的训练。我将怀着感恩的心,结束毕业设计和答辩,结束这如白驹过隙
  般的大学本科生活,并开始新的征程。

  /clearpage
  /addcontentsline{toc}{section}{参考文献}
  /label{sec:bib}
  /begin{thebibliography}{99}
  /bibitem{AH} A. Tomasic and H. Garcia-Molina. Performance of
    inverted indices in shared-nothing distributed text document
    information retrieval systems. presented at Proceedings of the
    Second International Conference on Parallel and Distributed
    Information Systems. 1993.
  /bibitem{AJ} Alistair Moffat, Justin Zobel. Self-Indexing Inverted
    Files for Fast Text Retrieval. ACM Transactions on Information
    Systems, 14(4):349-379. 1994.
  /bibitem{AJHAS} Arvind Arasu, Junghoo Cho, Hector Garcia-Molina,
    Andreas Paepcke, Sriram Raghavan. Searching the Web. ACM
    Transactions on Internet Technology. 1(1): August 2001.
  /bibitem{ALW} alltheweb. http://www.alltheweb.com/.
  /bibitem{AS} Amit Singhal. Modern Information Retrieval: A Brief
    Overview. Bulletin of the IEEE Computer Society Technical
    Committee on Data Engineering. 2001.
  /bibitem{ASKJ} Ask Jeeves. http://www.askjeeves.com/.
  /bibitem{EG} Ed Greengrass. Information Retrieval: A Survey[R]. DOD
    Technical Report TR-R52-008-001. 2000. Available online at:
    http://www.csee.umbc.edu/cadip/readings/IR.report.120600.book.pdf
  /bibitem{FHJJ} F. Scholer, H. E. Williams, J. Yiannis, and J. Zobel.
    Compression of Inverted Indexes For Fast Query Evaluation.
    Presented at Proceedings of the Twenty-Fifth Annual International
    ACM SIGIR Conference on Research and Development in Information
    Retrieval, Aug 11-15 2002, Tampere, Finland, 2002.
  /bibitem{GM} G. Salton and Michael J. McGill. Introduction to Modern
    Information Retrieval. McGraw-Hill Book Company. 1983.
  /bibitem{GOOGLE} Google. http://www.google.com/.
  /bibitem{GTW} Google, Teoma and WiseNut. Information Retrieval
    Techniques in Commercial Systems. Master in Information
    Management. 2002.
  /bibitem{HB} HotBot. http://www.hotbot.com/.
  /bibitem{HJ} H. E. Williams, J. Zobel. Compressing integers for fast
    file access. Computer Journal, vol. 42, pp. 193-201, 1999.
  /bibitem{HJXL} H. F. Yan, J. Y. Wang, X. M. Li, and L. Guo.
    Architectural design and evaluation of an efficient Web-crawling
    system. presented at Proceedings of 15th International Parallel
    and Distributed Processing Symposium, San Francisco, California,
    USA, 2001a. (Also published in Journal of Systems and Software,
    vol. 60, pp. 185-193, Feb 15, 2002.).
  /bibitem{IAT} Ian Witten, Alistair Moffat, Timothy C. Bell: Managing
    Gigabytes. Academic Press. 1999.
  /bibitem{JSMZX} J. Wang, S. Shan, M. Lei, Z. Xie, and X. Li, Web
    search engine: characteristics of user behaviors and their
    implication. Science in China, Series F, vol. 44, pp. 351--365,
    2001. (王建勇、单松巍、雷鸣、谢正茂、李晓明。海量web 搜索引擎系统中
    用户行为的分布特征及其启示。《中国科学》E 辑。2001 年8 月,第31卷,
    第四期,372-384页。)
  /bibitem{LEE} Lee, J.H. Combining multiple evidence from different
    properties of weighting schemes. In Proceedings of the 18th Annual
    International ACM SIGIR Conference on Research and Development in
    Information Retrieval, pp. 180-188, 1995.
  /bibitem{LP} Larry Page. The PageRank Citation Ranking: Bringing
    Order to the Web. Stanford Digital Library Technologies Project.
    1998.
  /bibitem{LYCOS} Lycos. http://www.lycos.com/.
  /bibitem{MB} Maxim Martynov, Boris Novikov. An Indexing Algorithm
    for Text Retrieval. Proceedings of the International Workshop on
    Advances in Databases and Information Systems (ADBIS'96). Moscow,
    September 10-13, 1996. 1996.
  /bibitem{MCM} M. Catherine McCabe. Advancing Information Retrieval.
    A dissertation in partial fulfillment of the requirements for the
    degree of Doctor at George Mason University Fairfax, Virginia.
    2000.
  /bibitem{MJN} Michael J. Nelson. A Prefix Trie Index For Inverted
    Files. Information Processing /& Management, Vol. 33, No. 6, pp.
    739-744, 1997.
  /bibitem{OVT} Overture. http://www.overture.com/.
  /bibitem{PPP} Pierre Baldi, Paolo Frasconi, Padhraic Smyth. Modeling
    the Internet and the Web: Probabilistic Methods and Algorithms.
    USA: John Wiley and Sons. 2003.
  /bibitem{RB} Ricardo Baeza-Yates, Berthier Ribeiro-Neto. Modern
    Information Retrieval. ACM Press, 1999.
  /bibitem{SL} Sergey Brin, Lawrence Page. The Anatomy of a Large-Scale
    Hypertextual Web Search Engine. In Proceedings of the 7th
    International WWW Conference. 1998.
  /bibitem{TEOMA} Teoma. http://www.teoma.com/.
  /bibitem{TSE} TSE. Home page of a tiny search engine.
    http://net.pku.edu.cn/~webg/src/TSE/. 2004.
  /bibitem{VA} V. N. Anh, A. Moffat. Compressed Inverted Files with
    Reduced Decoding Overhead. In Proceedings of the 21st Annual
    International ACM SIGIR Conference on Research and Development in
    Information Retrieval (SIGIR-98), W. B. Croft, M. Alistair,
    C. J. v. Rijsbergen, W. Rose, and Z. Justin, Eds. New York City:
    ACM Press, pp. 290~297, 1998.
  /bibitem{VV} Vivisimo. http://www.vivisimo.com/.
  /bibitem{WN} WiseNut. http://www.wisenut.com/.
  /bibitem{BD} 百度. http://www.baidu.com/.
  /bibitem{CHY} 昝红英. 基于实体属性的中文网页检索研究. 北京大学,博士
    论文. 2004.
  /bibitem{GBH} 龚笔宏. 面向主题搜集系统中搜集策略的研究和测评. 北京大
    学, 学士论文. 2001.
  /bibitem{LM} 雷鸣, 刘建国, 王建勇, 陈葆珏. 一种基于词典的搜索引擎系统
    动态更新模型. 计算机研究与发展. 第37卷第10期. P1265~1270.
    2000年10月.
  /bibitem{LXM} 李晓明, 闫宏飞, 王继民, 搜索引擎-原理、技术与系
    统. 北京:科学出版社. 2005.
  /bibitem{PB} 彭波. 搜索引擎检索系统的效率优化与效果评估研究. 北京大学,
    博士论文. 2004.
  /bibitem{SEWM06} SEWM2006中文Web检索评测. http://www.cwirf.org/
  /bibitem{TW} 天网. http://e.pku.edu.cn/.
  /bibitem{TWDIG} 天网数字资源搜索. DRSE. http://www.tianwang.com/.
  /bibitem{TWHR} 天网职位搜索. HRSE. http://job.tianwang.com/.
  /bibitem{YHF} 闫宏飞. 可扩展Web信息搜集系统的设计、实现与应用初探. 北
    京大学,博士论文. 2002.
  /bibitem{ZS} 中搜. http://www.zhongsou.com/.
  /end{thebibliography}

  /clearpage

  /appendix

  /section{附录}
  /label{sec:appendix}

  /subsection{主要代码}
  /label{ssec:app_code}

  /subsubsection{索引部分数据结构}
  /label{sssec:index_data_structs}

  /begin{description}
  /item 
  /item[class CDocument]
    /begin{tiny}
/begin{verbatim}

{
    int DOCUMENTKEY;
    Srawhash *m_prhsite;
public:
    struct SDocument
    {
        unsigned int weightscale:10;
        unsigned int siteno:22;
        float pagerank;
        unsigned int classid:22;
        unsigned int type:8;
        unsigned int deleteflag:1;
        unsigned int homepage:1;
        unsigned int modifytime;
    };
    struct SZDocument
    {
        unsigned int weightscale:10;
        unsigned int siteno:22;
        float pagerank;
    };
    int m_bShare;
    int m_nItems;
    SDocument *m_pDocument;
    FILE *m_fmain;
    CDocument ();
    ~CDocument ();
    int load (char *fname = DOCUMENTDB);
    int loadEx (char *fname = DOCUMENTDB);
    int unload (char *fname = DOCUMENTDB);
    int attach (char *fname = DOCUMENTDB);
    int end_build (int savecnt = 0, char *fname = DOCUMENTDB);
    int init_build (int doccnt);
    int add_site (int docid, char *url);
    int add_site (int docid, int siteid);
    int loaddb (char *fmain = DOCUMENTDB);
    int savedb (char *fmain = DOCUMENTDB);
    int expand (char *fmain, int doccnt);
    int load_subdb (char *fmain, int startid, int subcnt);
    int save_subdb (char *fmain, int startid, int subcnt);
    inline int getmaxcnt ();
    inline int setpagerank (int id, float prval);
    inline int deletedoc (int id);
    inline int undeletedoc (int id);
    inline SDocument &operator[] (int id);
};
/end{verbatim}
    /end{tiny}
  /item[class CTaskConf]
    /begin{tiny}
/begin{verbatim}

{
public:
    int totaldoc;       
    int maxchild;
    int taskdoc;
    int level2seg;
    int convertcharset;
    int removetags;
    char initdir[128];
    int loadconf (char *fname);
    int writeconf (char * fname);
    int m_type;
    int m_nodecnt;
    char m_nodes[MAXNODESNUM][16];
    int m_task[MAXNODESNUM];
    int m_taskcnt;
    int m_totaldoc;
    int m_doccnt[MAXNODESNUM];
    char m_root[256];
    char m_adminaddr[64];
    int m_destid;
    char m_path[256];
    int init ();
    int save ();
    int gettaskcnt ();
    int getnodecnt ();
    int gettotaldoccnt();
    int gettaskdoccnt();
    char *getnodepath (int id, char *path);
    char *gettaskpath (int id, char *path);
};
/end{verbatim}
    /end{tiny}
  /item[class CPattern]
    /begin{tiny}
/begin{verbatim}

{
public:
    struct SNode
    {
        int code;
        int num;
        int pos;
    };
    CPattern ();
    ~CPattern ();
    int build_dict (char *fname, char *fndict);
    int load_dict (char *fndict, char *path = ".");
    int load_pattern (char *fnpattern, char *fndict);
    int load_image (char *fnimage);
    int save_pattern (char *fnimage);
    int sort_pattern ();
    int build_index (SNode &parent, int level);
    int do_build_index (char *fnimage);
    int unload_matcher (char *path = ".", char *fndict = PATTERNDICT ,char *fnimage = PATTERNINDEX);
    int init_matcher (char *path = ".", char *fndict = PATTERNDICT, char *fnimage =PATTERNINDEX);
    int match_token (char *ptoken);
    char *get_hitpattern (int &depth);
    char *get_lasthit (int &depth);
    int clear_match ();
private:
    int m_npattern;
    int m_ntoken;
    int m_maxtokencnt;
    int *m_patterns;
    Sfinehash *m_pdict;
    SNode *m_index;
    int m_nnode;
    struct STaskBoard
    {
        int tokencnt;
        int idx;
        char words[MAXPATTERNLEN];
    };
    STaskBoard m_board[MAXTASKCNT];
    unsigned int m_bitmask;
    STaskBoard m_hitpattern[MAXTASKCNT];
    int m_nhitcnt;
    int search_token (int pos, int code, int &stop, int &hit);
    inline int gettoken (int pos, int level);
    inline SNode *allocnode (int cnt);
    inline int getnodepos (SNode *pnode);
    inline int tb_clear ();     
    inline int tb_getnexttask (int curid);      
    inline int tb_getfreenode ();
    inline int tb_addtask (int pos);
    inline int tb_freetask (int pos);
    inline int ht_add (int pos);
    inline int ht_clear ();
    inline int ht_gethitcnt ();
};
/end{verbatim}
    /end{tiny}
  /item[class CTaskDB:public CRecDatabase]
    /begin{tiny}
/begin{verbatim}

{
    CDictionary m_sitedict;
public:
    struct STaskRec
    {
        int docid;
        int taskid:8;
        int key:24;
        int status;
        int siteid;
        int downtime;
        float pagerank;
        unsigned char md5[16];
    };
    STaskRec *m_pcurrec;
    CTaskDB (int maxsitenum = 0):m_sitedict(maxsitenum){}
    int init ();
    inline int getmaxcnt ();
    inline STaskRec &operator[] (int id);
    static int cmpmd5_time (const void *pp1, const void *pp2);
    static int cmpmd5 (const void *pp1, const void *pp2);
    static int cmpdocid (const void *pp1, const void *pp2);
    static int cmppagerank (const void *pp1, const void *pp2);
    int delete_duplication (STaskRec *prec, int &cnt);
    int add_data (STaskRec *prec, int cnt);
    int save_subtask (char *fname, int id, int subcnt);
    int load_subtask (char *fname, int id, aint subcnt);
    int save_task (char *fname);
    int sort ();
    int reset_data (int startid, int doccnt);
    int offset_data (int startid);
    int lookup (char *url);
    STaskRec *lookuprec (char *url);
    int init_lookup (char *fname);
    void format (int id, char *pdest);
    int addsite (char *url);
    int load_dict (char *fname = TASKDB_DICT);
    int save_dict (char *fname = TASKDB_DICT);
};
/end{verbatim}
    /end{tiny}
  /item[class CTask]
    /begin{tiny}
/begin{verbatim}

{
public:
    int taskid;
    int state;
    int pid;
    int statloc;
    struct rusage rs;
    char *argv[4];
    char command[128];
    char argvv[256];
    int createtask (int type, int id, char *path, int startdoc, int enddoc, char *confdir); 
    int runtask ();
    int saveusage (int &loc, struct rusage &rs1);
    int isidle ();
};
/end{verbatim}
    /end{tiny}
  /item[class CRepository]
    /begin{tiny}
/begin{verbatim}

{
    static const char magicword[];
    int nZbufLen;
    char *pZbuf;
    int pagelen;
    char *pbuf;
    int nbuflen;
    int bCompress;
    CLargeFile fmall;
public:
    CRepository (char *fname, int compress = 1);
    ~CRepository ();
    int init (int64 offset);
    char *geturl ();
    char *fetch (int64 offset, int len = 0);
    int store (const char *url, char *buf, int64 &offset, int &len);
};
/end{verbatim}
    /end{tiny}
  /item[class CUrlInfo]
    /begin{tiny}
/begin{verbatim}

{
public:
    CUrlInfo ();
    ~CUrlInfo ();
    CUrlInfo (char *fname, int startid = 0);
    int m_startdocid;
    int store (int id, UrlInfoRec &rec);
    int storeEx (UrlInfoRec &rec);
    int fetch (int docid, UrlInfoRec &rec);
    int getlastrec (UrlInfoRec &rec);
    int rewind ();
    int nextrec (UrlInfoRec &rec);
    int init (int docid, UrlInfoRec &rec);
    int str2rec (const char *str, UrlInfoRec &rec);
    int rec2str (UrlInfoRec &rec, char *str);
protected:
    static const char fnamedat[];
    static const char fnameidx[];
    FILE *fdata;
    char valuebuf[16], databuf[512];
};
/end{verbatim}
    /end{tiny}
  /item[class CDatabase]
    /begin{tiny}
/begin{verbatim}

{
    protected:
    Db *m_dbp;
    Dbc *m_dbcp;
    DB_ENV *m_dbenv;
    Dbt m_key,m_data;
    DBTYPE m_dbtype;
    int m_writeflag, m_openmode;
    public:
    CDatabase ();
    virtual ~CDatabase ();
    int close ();
    int open (char *fname, char *openmode = "r");
    int read (void *pkey);      
    int write (void *pkey, void *pdata);        
    int rewind ();
    int next ();
    char *geterrmsg (int error);
    void *getkey ();
    void *getdata ();
    int getdatasize ();
    int getreccnt ();
    virtual int init ();
    virtual int initread (void *pkey);
    virtual int initwrite (void *pkey, void *pdata);
};
/end{verbatim}
    /end{tiny}
  /item[class CRecDatabase]
    /begin{tiny}
/begin{verbatim}

{
public:
    CRecDatabase ();
    virtual ~CRecDatabase();
    virtual int open (char *fname, char *openmode = "r");
    virtual int read (void *pkey);
    virtual int write (void *pdata, int size);
    virtual int save ();
    virtual int close ();
    virtual int init() = 0;     
protected:
    FILE *m_fdata;
    char *m_buf;
    int m_cnt;
    int m_len;
};
/end{verbatim}
    /end{tiny}
  /item[class CParser]
    /begin{tiny}
/begin{verbatim}

{
    int dictsize;
    CUnicode unicoder;
    CDictionary dict;
    int *pbufwgt;
    char *pbuf1;
    char *pbuf2;
    char *psrc;
    char title[MAXTITLELEN + 1];
    char charset[MAXCHARSETLEN + 1];
public:
    CParser (int dict_size = 0, int use_static_buffer = 1);
    ~CParser ();
    int64 loccnt;
    int64 wordcnt;
    int doccnt;
    int init_parser ();
    int init_parser(int docid, int64 iloccnt, int64 iwordcnt, const char *fndict);
    int end_parser (const char *fdict);
    void init_page (char *psrcbuf);
    int convert_charset (char *headcs);
    int delete_tags();
    int cut_words (int debug = 0);
    int end_page ();
    char *get_keywords ();
    int *get_weights ();
    char *get_charset ();
    char *get_title ();
    char *get_buf ();
    char *get_removebuf ();
    char *get_text ();
    char *get_result_buf (int &len);
    int get_dictsize ();
    CDictionary *get_dict ();
    int remove_tags (char *psrouce = NULL, char delimiter = ' ');
    static inline void clear_weight (int &weight);
    static inline int test_weight (int &weight, int type);
    static inline void set_weight (int &weight, int closeflag, int type);
    static inline int isInTitle (int val);
    static inline int isKeySent (int val);
    static inline int isUnimportant (int val);
    static inline int getTypeWeight (int val);
    inline int append_deli (char *&dest, char deli);
};
/end{verbatim}
    /end{tiny}
  /item[class CIndex]
    /begin{tiny}
/begin{verbatim}

{
    int INDEXKEY, DICTKEY;
    static const int m_align = 8;
public:
    struct SIndexItem
    {
        int doccnt;
        int loccnt;
        int weightscale;
        unsigned int offset;
        int len;
        int extralen;
        int locidxptr;  
    };
    int m_extramode;
    int m_nItems;
    int m_bShare;
    SIndexItem *m_pIndex;
    Sfinehash *m_pdict;
    CIndex ();
    ~CIndex ();
    FILE *m_fout, *m_fblkidx;
    char *m_pblock, *m_pbitmap;
    CByteCode m_coder;
    int m_wordnum;
    int load (char *fname = MAINIDX, char *fndict = MAINDICT);
    int loadEx (char *fname = MAINIDX);
    int unload (char *fname = MAINIDX, char *fndict = MAINDICT);
    int attach (char *fname = MAINIDX, char *fndict = MAINDICT);        
    int saveidx (char *fnidx = MAINIDX, char *fnlocidx=LOCLISTIDX);             
    int init_build (int nodenum, char *fname = MAININVF, char *fnblkidx = LOCLISTIDX);  
    int process_node (char *pbuf, WORDINFO &wiex);
    int dump_node (CIndex &srcindex, int srccode, int code, int idxonly = 0);
    int end_build (char *fnidx = MAINIDX, char *fnlocidx = LOCLISTIDX);         
    int m_docbytes, m_locbytes;
    int m_hinvf;
    int64 m_maplen;
    char *m_pinvf;
    int init_query_io ();
    int get_query_io ();
    int get_query_doclen();
    int get_query_loclen();
    int get_max_doclen (int id);
    int get_max_loclen (int id);        
    int read_loclist (char *plocblk,int code,int blknum);
    int init_query (char *fname = MAININVF);
    int read_doclist (char *&pdoclist, int &doclistlen, int &weightlistlen, int code);
    int get_blknum (int code, int docid, volatile int &maxdocid);
    inline void setextramode (int val); 
    inline SIndexItem &operator[] (int id);
    inline unsigned long *get_pidx (int id);
    int print_blklist (int code);       
    int findkey (char *pkey);
};
/end{verbatim}
    /end{tiny}
  /item[class CQuery]
    /begin{tiny}
/begin{verbatim}

{
private:
    int PROXINTERVAL;
    int idlist[MAXQUERYWORDNUM + 1];
    int sort_query (int type);  
    int m_fastmode;
    inline int issingleword (int id);
    inline int isexactword (int id);
    inline int isproxiword (int id);
    inline int isignoreword (int id);
    inline char *getkeyword (char *p);
    char *plocblk;
    unsigned long *ploclist, *pproxlist;        
    unsigned long *pmainproxlist;
    int m_mainproxinit;
    int m_ntruephrasecnt;
    int m_ntruekeywordcnt;
    int m_mainproxcheck;
    int check_filters (char *querystring);
    int execute_class (CQueryResult & qr);
    int execute_and (CQueryResult &qr);
    int execute_phrase (CQueryResult &qr, int startpos, int endpos);
    inline int VALIDATE_FILTERS (int docid);
public:
    struct SKeyword
    {
        char *keyword;
        int code;
        int phraseid;
        int loc;
        int depth;
    };
    struct SPhrase
    {
        int type;
        int bsentence;
        int wordcnt;
    };
    char m_filterbuf[16];
    int m_classfilter;
    int m_classfilterlen;
    char m_szclassfilter[8];
    int m_sitefilter;
    int m_homepagefilter;
    int m_userfilter;
    AllyClassID m_catefilter;   
    char m_savedir[256];
    char m_szStopWords[MAXQUERYSTRINGLEN];      
    char m_szQueryWords[MAXQUERYSTRINGLEN];     
    char m_szQuery[MAXQUERYSTRINGLEN];  
    char m_szStandard[MAXQUERYSTRINGLEN];
    char m_szTmp[MAXQUERYSTRINGLEN];
    char m_szCombinedWords[MAXQUERYSTRINGLEN * 2];
    SKeyword m_keyword[MAXQUERYWORDNUM + 1];
    SPhrase m_phrase[MAXQUERYWORDNUM + 1];
    int m_nPhraseCnt;
    int m_nKeywordCnt;
    int m_bInvalid;
    int m_nException;
    int m_maxdocbytes, m_maxlocbytes;
    float m_execute_msec;
    int m_loclistlen;
    int start_query (int logmode = 0);
    int close_query (int logmode = 0);
    int init_query (const char *querystring);
    void make_words ();
    int execute (Squeryresult *qd);
    inline int dogroup();
    int execute_debug();
}
/end{verbatim}
    /end{tiny}
  /end{description}

  /subsubsection{小索引的数据结构和实现}
  /label{sssec:indexie_arch}

  /tiny
/begin{verbatim}
//
// Description:                 The common data structure of the indexies of DRSE.
// File:                        DrIndexieCommon.hh
// Date & Time:                 Wed Apr 12 21:37:37 CST 2006
// Author:                      Li Yi <pank7yardbird@gmail.com, liyi@netera.cn>
//

#ifndef DRINDEXIECOMMON_HH_LIYI_2006_04_12
#define DRINDEXIECOMMON_HH_LYYI_2006_04_12

#include        <iostream>
#include        <fstream>
#include        <string>
#include        <map>
#include        <set>
#include        <vector>
#include        <ext/algorithm>
#include        <utility>
#include        <iterator>
#include        <exception>
#include        <sys/errno.h>

using namespace std;

class DrIndexieException : public std::exception
{
public :
    explicit
    DrIndexieException (const string &msg) throw(): mMsg(msg) {}

    virtual ~DrIndexieException () throw () {}

    virtual const char *what () const throw () {return mMsg.c_str ();}

private :
    string      mMsg;
};
template <class KeyType, class ValueType, typename _Compare>
class DrIndexie
{
private:
    typedef     vector<ValueType>               T_IdVector;
    typedef     pair<KeyType, T_IdVector>       T_Pair; // 序列化要用到的类型.

    typedef     map<KeyType, T_IdVector, _Compare>      T_Item;
    typedef     typename        T_Item::iterator        Iterator;
    typedef     typename        T_IdVector::iterator    ValueVectorIterator;

public:
    //! Constructor.
    DrIndexie ();

    // Constructor.
    DrIndexie (const char *);

    //! Destructor.
    virtual     ~DrIndexie ();

    /**
     * @brief       将索引序列化到磁盘文件中去.
     * @param       indexFile            将要被写入的磁盘文件名字.
     * @exception   DrIndexieException   写文件出错抛出异常.
     */
    void        saveIndex2File (const string &indexFile) throw(DrIndexieException);

    /**
     * @brief       将文件中的索引载入内存.
     *
     * @param       indexFile            索引文件名字.
     * @exception   DrIndexieException   读文件出错抛出.
     *
     * note :  这样可以实现一个断点续索的功能,相当于把中间结果从
     *         文件中载入,未处理的文档可以接着建立索引.
     */
    void        loadIndexFromFile (const string &indexFile) throw(DrIndexieException);

    /**
     * @brief       将<key, value>插入到内存中的临时索引中.
     *
     * @param       key           主健.
     * @param       value         对应的索引值.
     */
    void        insertPair (const KeyType &key, const ValueType &value);

    /*
     * @brief   Query with only one key.
     *
     * @param   idVector        Where query results will be put.
     * @param   key             Query key.
     *
     * @retval                  The size of the results.
     */
    size_t      query (T_IdVector &idVector, const KeyType &key)
        {
            Iterator    it = mIndex.find (key);
            if (it == mIndex.end ()) 
                return 0;
            idVector.resize (it->second.size());
            copy (it->second.begin (), it->second.end (), idVector.begin ());

            return idVector.size ();
        }

    /*
     * @brief   Query with a group of keys.
     *
     * @param   idVector        Where query results will be put.
     * @param   keyList         Query key group.
     *
     * @retval                  The size of the results.
     */
    size_t      query (T_IdVector &idVector, const vector<KeyType> &keyList)
        {
            Iterator    it;

            typename T_IdVector::iterator   pos;
            for (typename vector<KeyType>::const_iterator key_ptr = keyList.begin ();
                 key_ptr != keyList.end (); ++key_ptr) {
                it = mIndex.find (*(key_ptr));
                if (it == mIndex.end ()) {
                    continue;
                }

                int size = idVector.size ();
                idVector.resize (size + it->second.size ());
                pos = idVector.begin ();
                pos += size;
                copy (it->second.begin (), it->second.end (), pos);
            }

            return idVector.size ();
        }

    size_t      size ()
        {
            return mIndex.size ();
        }

    void        printKey ()
        {
            for (Iterator it = mIndex.begin (); it != mIndex.end (); ++it)
                cout << it->first << endl;
            return;
        }
    
private:
    T_Item      mIndex;         // stl_map做的索引.

private:
    class Serialize : public unary_function<T_Pair, void>
        {
        public:
    Serialize (ostream &os) : mOs(os){}

    void        operator() (const T_Pair &element) const
        {
            // 为保险起见在此作出判断.
            if (element.second.size () == 0)
                return;

            // 首先将关键字的长度写进去.
            size_t size = element.first.size ();
            mOs.write ((char *)&size, sizeof (size_t));
                        
            // 将关键字的内容写进去.
            mOs.write (element.first.c_str (), element.first.size ());

            // 然后将符合这个关键字条件的职位数写入.
            size_t count = element.second.size ();
            mOs.write ((char *)&count, sizeof (size_t));

            // 然后将下一条纪录的偏移量写入.通过计算得到.
            size_t currentOffset = mOs.tellp ();
            size_t nextRecordOffset = currentOffset + sizeof (size_t) + count * sizeof (ValueType);
            mOs.write ((char *)&nextRecordOffset, sizeof (size_t));
            // 然后将具体的信息写入,一般情况下是文档号. 有时侯也可能有其他信息.
            const ValueType *firstPtr = &element.second[0];
            mOs.write ((char *)firstPtr, count * sizeof (ValueType));

            return;
        }

        private:
    ostream& mOs;
        };
};


template <class KeyType, class ValueType, typename _Compare>
DrIndexie<KeyType, ValueType, _Compare>::DrIndexie ()
{
}

template <class KeyType, class ValueType, typename _Compare>
DrIndexie<KeyType, ValueType, _Compare>::DrIndexie (const char *index_file)
{
    loadIndexFromFile (index_file);
    return;
}

template <class KeyType, class ValueType, typename _Compare>
DrIndexie<KeyType, ValueType, _Compare>::~DrIndexie ()
{
}

template <class KeyType, class ValueType, typename _Compare>
void DrIndexie<KeyType, ValueType, _Compare>::saveIndex2File (const string &indexFile) /
throw (DrIndexieException) 
{
    ofstream tIndexStream (indexFile.c_str (), ios_base::binary);

    if (!tIndexStream)
        throw DrIndexieException (string ("DrIndexie<KeyType, ValueType, _Compare>::saveIndex2File () open /
file ") + indexFile + " :" + strerror (errno));

    for_each (mIndex.begin (), mIndex.end (), Serialize (tIndexStream));

    tIndexStream.close ();

    return;     
}

template <class KeyType, class ValueType, typename _Compare>
void DrIndexie<KeyType, ValueType, _Compare>::loadIndexFromFile (const string &indexFile) /
throw (DrIndexieException)
{
    ifstream    tIndexStream (indexFile.c_str (), ios_base::binary);

    if (!tIndexStream)
        throw DrIndexieException (string ("DrIndexie<KeyType, ValueType, _Compare>::loadIndex2File () open /
file ") + indexFile + " :" + strerror (errno));
    // |------------|--------|---------|------------------|----------|----------|----------|
    // |key-byte-len|key-data|value-num|next-record-offset|value-data|value-data|value-data|.
    // |____________|________|_________|__________________|__________|__________|__________|
    int         keyByteLen = -1;
    string      keyData;
    int         valueNum = -1;
    int         nextRecordOffset = -1;

    string throwMsg = "DrIndexie<KeyType, ValueType, _Compare>::loadIndexFromFile () : index file format /
error";
    // read函数返回流本身,所以不能通过返回值来判断是否读出了正确的值
    while (tIndexStream.read ((char *)&keyByteLen, sizeof (int)))
    {
        if (keyByteLen < 0)
            throw DrIndexieException (throwMsg);
        keyData.resize (keyByteLen);
        if (!tIndexStream.read ((char *)&keyData[0], keyByteLen))
            goto error_format;
        if (!tIndexStream.read ((char *)&valueNum, sizeof (int))
            or valueNum < 0)
            goto error_format;
        if (!tIndexStream.read ((char *)&nextRecordOffset, sizeof (int))
            or nextRecordOffset < 0)
            goto error_format;
        vector<ValueType>       value (valueNum);
        if (!tIndexStream.read ((char *)&value[0], valueNum * sizeof (ValueType)))
            goto error_format;

        // 必须为KeyType定义KeyType(const char *)构造函数.
        mIndex.insert (make_pair (KeyType (keyData.c_str ()), value));

        tIndexStream.seekg (nextRecordOffset);
    }

    tIndexStream.close ();
    return;

    error_format :
        throw DrIndexieException (throwMsg);
    return;
}

template <class KeyType, class ValueType, typename _Compare>
void
DrIndexie<KeyType, ValueType, _Compare>::insertPair (const KeyType &key, const ValueType &value)
{
    Iterator    it = mIndex.find (key);
    if (it == mIndex.end ())
    {
        T_IdVector      tS;
        tS.push_back (value);
        mIndex.insert (make_pair (key, tS));
        return;
    }

    // 已经存在这个键值的纪录. 修改.
    it->second.push_back (value);

    return;
}

#endif

/end{verbatim}

  /subsubsection{小索引创建算法}
  /label{sssec:indexie_creation}

  /tiny
/begin{verbatim}
//
// Description:         For DRSE. Make indexie for CategoryID in the XML files.
// File:                DrIndexieCreator.cpp
// Author:              Li Yi <pank7yardbird@gmail.com, liyi@netera.cn>
// Date & Time:         Wed Apr 12 00:55:58 CST 2006
// 

#include        <sstream>
#include        "feeder.h"
#include        "taskdb.h"
#include        "webtextdb.h"
#include        "Drcommon.hh"
#include        "IDURL.hh"
#include        "XmlParser.hh"
#include        "CategoryIDIndexie.hh"

static void
showhelp (int argc, char **argv);

int
main (int argc, char **argv)
{
    if (argc != 2) {
        showhelp (argc, argv);
        return 1;
    }

    try {
        stringstream            part;
        string                  id;
        T_CategoryID            categoryid ("000000");
        int                     retval;
        CFeeder                 feeder;
        XmlParser               xmlparser;
        int                     xml_count = 0;
        CategoryIDIndexieCreator    categoryidindexie;

        xml_count = feeder.init (0);
        std::cout << "Found: " << xml_count << " xml documents in webdatadb." << std::endl;

        part.flags (part.flags () | ios_base::skipws | ios_base::dec);

        for (int count = 0; count < xml_count; ++count) {
            retval = feeder.fetchpage (0, count + 1);
            if (retval < 0) {
                std::cout << "fetch failed!" << std::endl;
                continue;
            } else {
                xmlparser.input (feeder.getbuf ());
                part << xmlparser.getValueOf ("categoryid");
                part >> categoryid;
                categoryidindexie.insertPair (categoryid, feeder.getdocid () - 1);

                if ((count % 1000) == 0) {
                    std::cout << "Processed " << count << " records" << "/r";
                }
                std::cout.flush();            
            }
        }
        categoryidindexie.saveIndex2File (CATEGORYIDINDEXIE);
    } catch (exception &E) {
        std::cout << E.what () << std::endl
                  << "Fatal! Exit!" << std::endl;
        return 2;
    }
        
    return 0;
}

static void
showhelp (int argc, char **argv)
{
    std::cout << "usage: " << argv[0] << " XMLdb" << std::endl
              << "       XMLdb:  The DB contains the raw XML file." << std::endl;

    return;
}

/end{verbatim}

/end{CJK*}
/end{document}

%%% Local Variables: 
%%% mode: latex
%%% TeX-master: t
%%% End: 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值