文章列表
 
您正在查看 "搜索技术" 分类下的文章

2007-08-31 0:50
何谓搜索相关性?

简单来说,就是你的搜索结果和你的关键词之间相匹配的程度。比如使用“汽车”作为关键词结果搜出的网站80%都是与汽车相关的,那么这个引擎的相关性做的就相当的好了;但是如果结果搜索出来一堆介绍飞机的网站,那么它的相关性可就差到极点了。

这么解释一下,相关性在理解上面似乎也不难,但是真正做到搜索技术上面问题就大了,使用什么的算法才能使一个搜索引擎的相关性做到最好呢?

早 期的搜索引擎使用的一个算法理论是关键词频次分析,就是说,一个关键词在一个网页上面出现的次数越多,那这个网页与这个关键词的相关性就越大。但是这个理 论对于互联网环境来说并不太适用,因为在互联网上面,每个使用者都可以随意发表自己的观点,发布自己的信息,同一个关键词出现次数相同的两个网页很有可能 讲的是完全不同的两件事情。比如同样使用了10次“汽车”这个关键词,网页A讲述的是汽车相关的东西,而网页B讲述的却是旅游相关的东西!这样的话对于按 照这种理论进行的搜索结果也就很难保证其友好的相关性了。在这种理论不再适合于欣欣向荣的互联网时代的情况下,第二代的搜索引擎理论出现了。

第二代的搜索引擎理论被称为超链分析 或者英文叫Page Rank这个理论是怎么回事儿呢?

超链分析的原理是借鉴论文引用的理论提出的,对于一篇论文来说,引用它的论文越多,那这篇论文的权威性也就越大。对于互联网来说,链接就相当于引用:引用网站A的网站越多,则网站A的重要性就越大,那它在搜索结果中的排序就应该越靠前。

这只是一个简单的原理解释。
Page Rank的理论公式为:
网页A级别 = (1-系数)+系数x(网页1级别/网页1的链出个数 + 网页2级别/网页2的链出个数 + 网页3级别/网页3的链出个数 + …… + 网页n级别/网页n的链出个数)
其中:
系数是一个大于0,小于1的数。一般设置为0.85(我也不知道为什么)。网页1……n表示所有有指向网页A的链接的网页。

由这个公式可以看出:
链接到网页A的网页数越多,则A的级别越高;这个就没什么好解释的了
连接到网页A的网页的级别越高,则A的级别越高;这个就好比宋大嘴说你是个活雷锋估计别人都怎么相信,但要是胡core说你是活雷锋的话,那结果怎么样你就想象去吧
链 接到网页A的网页上面的链出个数(也就是这个网页上面的链接个数)也多,则网页A的级别越低;这个就好比你一天对你bf(or gf)说了一万句话,其中只有一句是I love you,估计你bf(or gf)根本就不当一会儿;但是要是你一天就对你bf(or gf)说了一句话,而那句话是I love you,那你说你的那位得怎么感动去吧?(或者压根儿早就因为你一天都不理她而气得跟你分手了,你也就没机会说这句话了:)不过搜索引擎是没有感情的,这 点就不再考虑之列了)
这样搜索引擎会对每个网页计算出它的级别值,按照级别的高低而在搜索结果中对这些网页进行排序。
这个差不多就是理论上的搜索引擎相关性的计算方法了。
一个搜索引擎的相关性做的越好,那么这个搜索引擎自然也就越好。

另外还有一个相关算法需要在这里关怀到,那就是HillTop算法。
HillTop算法是Google的一个工程师Bharat在2001年提出的专利。Google也曾基于这个算法对其排序法则做出过一次很大的调整。
HillTop 算法的指导思想和Page Rank的是一致的,都是通过网页被链接的数量和质量来确定搜索结果的排序权重。但HillTop认为只计算来自具有相同主题的相关文档链接对于搜索者的 价值会更大:即主题相关网页之间的链接对于权重计算的贡献比主题不相关的链接价值要更高。如果网站是介绍“服装”的,有10个链接都是从“服装”相关的网 站链接过来,那这10个链接比另外10个从“电器”相关网站链接过来的贡献要大得多。Bharat称这种对主题有影响的文档为“专家”文档,从这些专家文 档页面到目标文档的链接决定了被链接网页“权重得分”的主要部分。
 
2007-08-25 22:47

1)Apache的首页

    http://lucene.apache.org/java/docs/index.html

    里面包含的file format应该是开始了解lucene的必读内容,Query Syntax描述输入关键词得语法。当然,结合程序代码一起看会效果更好。

2)lucene 倒排索引的原理
    http://www.zhanglihai.com/blog/c_296.html

    这篇文章浅显易懂,介绍里倒排索引的原理。

3)开放源代码的全文检索引擎Lucene

    http://liyu2000.nease.net/article/Lucene/bachelor-paper.htm#_Toc43005312

    是一篇人大本科生的毕业论文,虽然写的不是很完善,许多细节没有提到,但是对于整个lucene系统和源代码的构架描述得还不错,适合从整体上了解lucene。

4)Lucene lecture at Pisa

    http://lucene.sourceforge.net/talks/pisa/

    lucene作者cutting得一次讲座内容,里面包含了lucene得许多算法简介,必读。

5)基于Java的全文索引引擎Lucene简介

    http://www.chedong.com/tech/lucene.html

    车东的文章,lucene得一些特征介绍,和数据库的比较,推荐读一下。

6)Advanced Text Indexing with Lucene

    http://www.onjava.com/pub/a/onjava/2003/03/05/lucene.html

    关于lucene优化策略的研究文章,描述了lucene中一些参数的调整对性能的影响。写的很不错,若想优化lucene的性能,推荐读。

7)C++版本的lucene

    http://clucene.sourceforge.net/

    当然,想深入了解lucene,读源码是最好的途径。
 
2007-08-07 17:50
在好久以前就想学一下Lucene搜索引擎工具,但一直没安排好时间,网上关于它的介绍也不多。网上有好多人在为它的推广不停地努力,我所知道的比 较出名的如车东,在搜索引擎方面有很深的研究。我现在只是一个初学者,所能做的只能是站在他们的肩膀上,去学他们的技术,记录他们的只言片语。

  以下就是我记录了他们关于Lucene的资料,我总结如下:(在文章最后我会标明出处!)

Lucene的概述:

  Lucene(发音为 ['lusen] )是一个非常优秀的开源的全文搜索引擎,我们可以在它的上面开发出各种全文搜索的应用来。Lucene在国外有很高的知名度,现在已经是Apache的顶级项目,在国内,Lucene的应用也越来越多。

Lucene的算法原理:

  Lucene是一个高性能的java全文检索工具包,它使用的是倒排文件索引结构。该结构及相应的生成算法如下:

 0)设有两篇文章1和2
文章1的内容为:Tom lives in Guangzhou,I live in Guangzhou too.
文章2的内容为:He once lived in Shanghai.

 1)全文分析:由于lucene是基于关键词索引和查询的,首先我们要取得这两篇文章的关键词,通常我们需要如下处理措施
a.我们现在有的是文章内容,即一个字符串,我们先要找出字符串中的所有单词,即分词。英文单词由于用空格分隔,比较好处理。中文单词间是连在一起的需要特殊的分词处理。
b.文章中的”in”, “once” “too”等词没有什么实际意义,中文中的“的”“是”等字通常也无具体含义,这些不代表概念的词可以过滤掉
c.用户通常希望查“He”时能把含“he”,“HE”的文章也找出来,所以所有单词需要统一大小写。
d.用户通常希望查“live”时能把含“lives”,“lived”的文章也找出来,所以需要把“lives”,“lived”还原成“live”
e.文章中的标点符号通常不表示某种概念,也可以过滤掉
在lucene中以上措施由Analyzer类完成

 经过上面处理后
文章1的所有关键词为:[tom] [live] [guangzhou] [i] [live] [guangzhou]
文章2的所有关键词为:[he] [live] [shanghai]

 2) 倒排索引:有了关键词后,我们就可以建立倒排索引了。上面的对应关系是:“文章号”对“文章中所有关键词”。倒排索引把这个关系倒过来,变成:“关键词”对“拥有该关键词的所有文章号”。文章1,2经过倒排后变成
关键词 文章号
guangzhou 1
he 2
i 1
live 1,2
shanghai 2
tom 1

   通常仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现次数和出现的位置,通常有两种位置:a)字符位置,即记录该词是文章中第几 个字符(优点是关键词亮显时定位快);b)关键词位置,即记录该词是文章中第几个关键词(优点是节约索引空间、词组(phase)查询快),lucene 中记录的就是这种位置。

加上“出现频率”和“出现位置”信息后,我们的索引结构变为:

关键词 文章号 [出现频率] 出现位置
guangzhou 1 [2] 3,6
he 2 [1] 1
i 1 [1] 4
live 1 [2] 2,5
2 [1] 2
shanghai 2 [1] 3
tom 1 [1] 1

   以live 这行为例我们说明一下该结构:live在文章1中出现了2次,文章2中出现了一次,它的出现位置为“2,5,2”这表示什么呢?我们需要结合文章号和出现 频率来分析,文章1中出现了2次,那么“2,5”就表示live在文章1中出现的两个位置,文章2中出现了一次,剩下的“2”就表示live是文章2中第 2个关键字。
以上就是lucene索引结构中最核心的部分。我们注意到关键字是按字符顺序排列的(lucene没有使用B树结构),因此lucene可以用二元搜索算法快速定位关键词。
实现时 lucene将上面三列分别作为词典文件(Term Dictionary)、频率文件(frequencies)、位置文件 (positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。

  Lucene中使用了field的概念,用于表达信息所在位置(如标题中,文章中,url中),在建索引中,该field信息也记录在词典文件中,每个关键词都有一个field信息(因为每个关键字一定属于一个或多个field)。
为了减小索引文件的大小,Lucene对索引还使用了压缩技术。 首先,对词典文件中的关键词进行了压缩,关键词压缩为<前缀长度,后缀>,例如:当前词为“阿拉伯语”,上一个词为“阿拉伯”,那么“阿拉伯 语”压缩为<3,语>。其次大量用到的是对数字的压缩,数字只保存与上一个值的差值(这样可以减小数字的长度,进而减少保存该数字需要的字节 数)。例如当前文章号是16389(不压缩要用3个字节保存),上一文章号是16382,压缩后保存7(只用一个字节)。 注意是“上一个词”。由于词典是按顺序排列的,这种压缩方法的效果会非常显著。

  下面我们可以通过对该索引的查询来解释一下为什么要建立索引。
假设要查询单词 “live”,lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。
而用普通的顺序匹配算法,不建索引,而是对所有文章的内容进行字符串匹配,这个过程将会相当缓慢,当文章数目很大时,时间往往是无法忍受的。

全文检索框架的实现机制:

   Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表==>记录==>字段,所以很多传统的应用的文件、数据库等都可以 比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。

比较一下Lucene和数据库:

Lucene 数据库

索引数据源:doc(field1,field2...) doc(field1,field2...)

           \ indexer /
       _____________
         | Lucene Index |
             --------------
            / searcher \

结果输出:Hits(doc(field1,field2) doc(field1...))

索引数据源:record(field1,field2...) record(field1..)  

            \ SQL: insert/
           _____________
            |   DB Index   |
               -------------
            / SQL: select \

结果输出:results(record(field1,field2..) record(field1...))

Document:一个需要进行索引的“单元,一个Document由多个字段组成

Record:记录,包含多个字段

Field:字段

Field:字段

Hits:查询结果集,由匹配的Document组成

RecordSet:查询结果集,由多个Record组成

全文检索 ≠ like "%keyword%"

   由于数据库索引不是为全文索引设计的,因此,使用like "%keyword%"时,数据库索引是不起作用的,在使用like查询时,搜索过程又变成类似于一页页翻书的遍历过程了,所以对于含有模糊查询的数据库 服务来说,LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配:like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。

  通常比较厚的书籍后面常常附关键词索引表(比如:北京:12, 34页,上海:3,77页……),它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样,想像一下通过书后面的索 引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高,另外一个原因是它是排好序的。对于检索系统来说核心是一个排序问题。

   所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制,将数据源(比如多篇文章)排序顺序存储的同时,有另外一个排好序的关键词 列表,用于存储关键词==>文章映射关系,利用这样的映射关系索引:[关键词==>出现关键词的文章编号,出现次数(甚至包括位置:起始偏移 量,结束偏移量),出现频率],检索过程就是把模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程。从而大大提高了多关键词查询的效率,所以,全文 检索问题归结到最后是一个排序问题。

  由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题,这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制,并提供了扩展接口,以方便针对不同应用的定制。

  可以通过一下表格对比一下数据库的模糊查询:

Lucene全文索引引擎 数据库
索引 将数据源中的数据都通过全文索引一一建立反向索引 对于LIKE查询来说,数据传统的索引是根本用不上的。数据需要逐个便利记录进行GREP式的模糊匹配,比有索引的搜索速度要有多个数量级的下降。
匹配效果 通过词元(term)进行匹配,通过语言分析接口的实现,可以实现对中文等非英语的支持。 使用:like "%net%" 会把netherlands也匹配出来,
多个关键词的模糊匹配:使用like "%com%net%":就不能匹配词序颠倒的xxx.net..xxx.com
匹配度 有匹配度算法,将匹配程度(相似度)比较高的结果排在前面。 没有匹配程度的控制:比如有记录中net出现5词和出现1次的,结果是一样的
结果输出 通过特别的算法,将最匹配度最高的头100条结果输出,结果集是缓冲式的小批量读取的。 返回所有的结果集,在匹配条目非常多的时候(比如上万条)需要大量的内存存放这些临时结果集。
可定制性 通过不同的语言分析接口实现,可以方便的定制出符合应用需要的索引规则(包括对中文的支持) 没有接口或接口复杂,无法定制
结论 高负载的模糊查询应用,需要负责的模糊查询的规则,索引的资料量比较大 使用率低,模糊匹配规则简单或者需要模糊查询的资料量少

全文检索和数据库应用最大的不同在于:让最相关的头100条结果满足98%以上用户的需求。
Lucene的创新之处:

   大部分的搜索(数据库)引擎都是用B树结构来维护索引,索引的更新会导致大量的IO操作,Lucene在实现中,对此稍微有所改进:不是维护一个索引文 件,而是在扩展索引的时候不断创建新的索引文件,然后定期的把这些新的小索引文件合并到原先的大索引中(针对不同的更新策略,批次的大小可以调整),这样 在不影响检索的效率的前提下,提高了索引的效率。

Lucene和其他一些全文检索系统/应用的比较:

Lucene 其他开源全文检索系统
增量索引和批量索引 可以进行增量的索引(Append),可以对于大量数据进行批量索引,并且接口设计用于优化批量索引和小批量的增量索引。 很多系统只支持批量的索引,有时数据源有一点增加也需要重建索引。
数据源 Lucene没有定义具体的数据源,而是一个文档的结构,因此可以非常灵活的适应各种应用(只要前端有合适的转换器把数据源转换成相应结构)。 很多系统只针对网页,缺乏其他格式文档的灵活性。
索引内容抓取 Lucene的文档是由多个字段组成的,甚至可以控制那些字段需要进行索引,那些字段不需要索引,近一步索引的字段也分为需要分词和不需要分词的类型:
   需要进行分词的索引,比如:标题,文章内容字段
   不需要进行分词的索引,比如:作者/日期字段
缺乏通用性,往往将文档整个索引了
语言分析 通过语言分析器的不同扩展实现:
可以过滤掉不需要的词:an the of 等,
西文语法分析:将jumps jumped jumper都归结成jump进行索引/检索
非英文支持:对亚洲语言,阿拉伯语言的索引支持
缺乏通用接口实现
查询分析 通过查询分析接口的实现,可以定制自己的查询语法规则:
比如: 多个关键词之间的 + - and or关系等
功能较强大
并发访问 能够支持多用户的使用 功能较强大

关于亚洲语言的的切分词问题(Word Segment)
对于中文来说,全文索引首先还要解决一个语言分析的问题,对于英文来说,语句中单词之间是天然通过空格分开的,但亚洲语言的中日韩文语句中的字是一个字挨一个,所有,首先要把语句中按“词”进行索引的话,这个词如何切分出来就是一个很大的问题。
首先,肯定不能用单个字符作(si-gram)为索引单元,否则查“上海”时,不能让含有“海上”也匹配。
但一句话:“北京天安门”,计算机如何按照中文的语言习惯进行切分呢?
“北京 天安门” 还是“北 京 天安门”?让计算机能够按照语言习惯进行切分,往往需要机器有一个比较丰富的词库才能够比较准确的识别出语句中的单词。
另外一个解决的办法是采用自动切分算法:将单词按照2元语法(bigram)方式切分出来,比如:
"北京天安门" ==> "北京 京天 天安 安门"。
这样,在查询的时候,无论是查询"北京" 还是查询"天安门",将查询词组按同样的规则进行切分:"北京","天安安门",多个关键词之间按与"and"的关系组合,同样能够正确地映射到相应的索引中。这种方式对于其他亚洲语言:韩文,日文都是通用的。
基于自动切分的最大优点是没有词表维护成本,实现简单,缺点是索引效率低,但对于中小型应用来说,基于2元语法的切分还是够用的。基于2元切分后的索引一般大小和源文件差不多,而对于英文,索引文件一般只有原文件的30%-40%不同。
自动切分 词表切分
实现 实现非常简单 实现复杂
查询 增加了查询分析的复杂程度 适于实现比较复杂的查询语法规则
存储效率 索引冗余大,索引几乎和原文一样大 索引效率高,为原文大小的30%左右
维护成本 无词表维护成本 词表维护成本非常高:中日韩等语言需要分别维护。
还需要包括词频统计等内容
适用领域 嵌入式系统:运行环境资源有限
分布式系统:无词表同步问题
多语言环境:无词表维护成本
对查询和存储效率要求高的专业搜索引擎

目前比较大的搜索引擎的语言分析算法一般是基于以上2个机制的结合。关于中文的语言分析算法,大家可以在Google查关键词"wordsegment search"能找到更多相关的资料。

Lucene的结构框架:
注意:Lucene中的一些比较复杂的词法分析是用JavaCC生成的(JavaCC:JavaCompilerCompiler,纯Java的词法分析生成器),所以如果从源代码编译或需要修改其中的QueryParser、定制自己的词法分析器,还需要从https://javacc.dev.java.net/下载javacc。
lucene的组成结构:对于外部应用来说索引模块(index)和检索模块(search)是主要的外部应用入口。
org.apache.Lucene.search/ 搜索入口
org.apache.Lucene.index/ 索引入口
org.apache.Lucene.analysis/ 语言分析器
org.apache.Lucene.queryParser/ 查询分析器
org.apache.Lucene.document/ 存储结构
org.apache.Lucene.store/ 底层IO/存储结构
org.apache.Lucene.util/ 一些公用的数据结构

从Lucene学到更多:
Luene的确是一个面对对象设计的典范。

  1. 所有的问题都通过一个额外抽象层来方便以后的扩展和重用:你可以通过重新实现来达到自己的目的,而对其他模块而不需要;
  2. 简单的应用入口Searcher, Indexer,并调用底层一系列组件协同的完成搜索任务;
  3. 所 有的对象的任务都非常专一:比如搜索过程:QueryParser分析将查询语句转换成一系列的精确查询的组合(Query),通过底层的索引读取结构 IndexReader进行索引的读取,并用相应的打分器给搜索结果进行打分/排序等。所有的功能模块原子化程度非常高,因此可以通过重新实现而不需要修 改其他模块。
  4. 除了灵活的应用接口设计,Lucene还提供了一些适合大多数应用的语言分析器实现(SimpleAnalyser,StandardAnalyser),这也是新用户能够很快上手的重要原因之一。


这些优点都是非常值得在以后的开发中学习借鉴的。作为一个通用工具包,Lunece的确给予了需要将全文检索功能嵌入到应用中的开发者很多的便利。
此外,通过对Lucene的学习和使用,我也更深刻地理解了为什么很多数据库优化设计中要求,比如:

  1. 尽可能对字段进行索引来提高查询速度,但过多的索引会对数据库表的更新操作变慢,而对结果过多的排序条件,实际上往往也是性能的杀手之一。
  2. 很多商业数据库对大批量的数据插入操作会提供一些优化参数,这个作用和索引器的merge_factor的作用是类似的。
  3. 20%/80%原则:查的结果多并不等于质量好,尤其对于返回结果集很大,如何优化这头几十条结果的质量往往才是最重要的。
  4. 尽可能让应用从数据库中获得比较小的结果集,因为即使对于大型数据库,对结果集的随机访问也是一个非常消耗资源的操作。

摘引:车东BLOGhttp://www.chedong.com/tech/lucene.html

 
2007-08-07 16:52

ICTCLAS for java研究,sinboy的BLOG:http://blog.csdn.net/sinboy/category/207165.aspx

ICTCLAS for C#研究,吕震宇的BLOG:http://www.cnblogs.com/zhenyulu/category/85598.html

DanceFire的专栏:http://blog.csdn.net/DanceFire/category/294373.aspx

ICTCLAS的主页:http://www.i3s.ac.cn/index.htm

ICTCLA4J开源项目:http://code.google.com/p/ictclas4j/

http://groups.google.com/group/ictclas

北大中文论坛:http://www.pkucn.com/forumdisplay.php?fid=29

语料库语言学论坛:http://forum.corpus4u.org/index.php

语言技术论坛:http://bbs.langtech.org.cn

全文索引和检索平台:http://www.firtex.org

 
2007-08-07 1:05
ntroduction to Nutch:
Nutch is an open source Java implementation of a search engine(Lucene). It provides all of the tools you need to run your own search engine.

所需软件:
1.Nutch
下载网址:http://mirror.vmmatrix.net/apache/lucene/nutch/
2.Cygwin
下载网址:http://www-inst.eecs.berkeley.edu/~instcd/iso/
另需Daemon虚拟光驱安装Cygwin ISO
注:Cygwin安装时需选(3)Install from Local Directory
Select Packages中选ALL Install
3.Tomcat
下载网址:http://tomcat.apache.org

安装步骤:

首先配置JAVA运行环境,然后配置nutch,设置NUTCH_JAVA_HOME( 路径和JAVA_HOME相同)

1.将nutch-0.7.1.tar.gz解压到目标地址,如D:\nutch-07.1(在Cygwin里cd入压缩包目录,使用tar -zxvf nutch-0.7.1.tar.gz)

2.测试Nutch,在Cygwin下键入“cd d:/nutch-0.7.1/bin”在bin目录下运行nutch命令,看到一大堆命令简介就说明正常

3.运行爬虫(crawl命令),在nutch-0.7.1目录下新建一个urls.txt文件(对于nutch 0.8 需要在根目录下新建一个文件夹再放入urls并且
此文件不要后缀)。这时爬虫程序才可以发现urls文件,当然这个文件名也是可以随便修改的。只要在运行爬虫的命令中指明正确的文件
名。在urls中添加一个网址作为爬行的根目录,例如:http://www.sina.com.cn/ 注意:这里一定要加上http://以及后面的"/" 缺一不可,很多朋友crawl后发现索引目录非常小,一会就索引完了(我一开始也是这样,找了半天才偶然发现此症结),原因就是爬虫找不到完整的网址路径,造成索引失败
接着在nutch-0.7.1/conf/crawl-urlfilter.txt中设置爬行规则,找到此行 +^http://([a-z0-9]*\.)*MY.DOMAIN.NAME/,把
其中的MY.DOMAIN.NAME改成待爬行网址,例如:sina.com.cn配制完后,在Cygwin中cd到nutch-0.7.1目录,运行命令bin/nutch
crawl urls.txt -dir crawl.test -depth 3 -threads 4 >& crawl.log其中urls.txt 就是存放爬行网址的文件;crawl.test是我们指定的索引文件存放目录,爬虫在执行的时候,一些临时文件将被存放在此目录;-depth 3 指定爬行深度(搜索网址层数,其深度与爬行所需时间成正比);-threads 4 指定启动4个线程进行爬行(机子够快的话建议多开几个线程,这样会大大加快爬行速度);>& crawl.log 指定生成运行日志,其中记录了爬行时每一步操作,对于查错很有帮助。
接下来是等待,其时间与depth层数成正比,与threads数成反比,当然还与机器速度有关。本人试验用-depth 3 -threads 4爬行
www.sina.com.cn需15分钟以上;结束后可以看到nutch-0.7.1下多了crawl.test目录,里面有3个文件夹,当看到index目录,就说明
索引已经建立,爬行成功,此时可以利用Lucene索引工具箱-Luke(下载网址:http://www.getopt.org/luke/ )打开nutch-0.7.1\crawl.test\index 来查看索引数据库。

然后配置Tomcat:

1.将nutch-0.7.1目录下的nutch-0.7.1.war拷贝到Tomcat的Webapps目录下。启动Tomcat,其自动把nutch -0.7.1.war解压生成nutch-0.7.1目录。接着把Tomcat\Webapps目录下的ROOT目录改名(如ROOT.BAK),把 Webapps目下nutch-0.7.1文件夹改名成 ROOT(即设置为Tomcat主页)

2.打开Tomcat\Webapps\ROOT\WEB-INF\classes目录下修改nutch-site.xml。在<nutch-conf></nutch-conf>中间添加

<property>
<name>searcher.dir</name>
<value>D:\nutch-0.7.1\crawl.test</value>
</property>

其中关键是在<value></value>中写上刚才爬虫建立的索引路径(本例为crawl.test)

3.为了让Nutch正确处理中文,需要修改Tomcat/conf下的server.xml配置文件,

以下红色部分是新加入的

<Connector port="8080"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true"
URIEncoding="UTF-8" useBodyEncodingForURI="true" />

4.重启Tomcat。在浏览器里输入http://127.0.0.1:8080 应该就能看到Nutch的主页了,在搜索条里键入“新闻”可以看到好几百项搜索结果,另外包括 搜索到的网址,网页快照,评分,anchors等信息。
 
2007-08-07 1:03
最重要部分
public synchronized native boolean init(int j, int i);
public synchronized native String paragraphProcess(String sParagraph);
public synchronized native boolean fileProcess(String source,String target);
import com.xjt.nlp.word.*;

public class ICTCLASTest {
   public static void main (String [] args) {
     ICTCLAS instance = ICTCLAS.getInstance();
     instance.init(j, i);
     String sentence = "我们是神仙";
     System.out.println(instance.paragraphProcess(sentence));
   }
}

j=nOutputFormat,i=nOperateType,j和i的取值范围都是{0,1,2},

i:0,j:0 --我们 是 神仙
i:1,j:0 --我们/r 是/v 神仙/n
i:2,j:0 --我们/r 是/v 神仙/n

i:0,j:1 --我们\是\神仙\
i:1,j:1 --我们\[r]是\[v]神仙\[n]
i:2,j:1 --我们\[r]是\[v]神仙\[n]

i:0,j:2 --<src>我们</src><src>是</src><src>神仙</src>
i:1,j:2 --<any type="r"><src>我们</src></any><any type="v"><src>是</src></any><any type="n"><src>神仙</src></any>测试有错误发生
i:2,j:2 --<any type="r"><src>我们</src></any><any type="v"><src>是</src></any><any type="n"><src>神仙</src></any>测试有错误发生

搜索离不开切分词 也就是说 google baidu 也离不开上面这样的代码,不管他们技术有多先进
 
2007-08-07 1:02
附录主要列举了一些常用格式文件的文件抽取工具,以供在使用Lucene时使用

1. PDF格式
PDF格式的文档相当常见,但是如果要想要为其中的内容建立索引,就需要专门的工具来对其中的文本进行抽取。最常见的一种PDF文本抽取工具就是PDFBOX了,它的下载网址为:

http://sourceforge.net/projects/pdfbox/

当然,PDFBOX对于中文的支持很差,如果想抽取中文的PDF文件中的文本,可以使用一个称之为xpdf的工具。以下是它的下载网址:

http://www.foolabs.com/xpdf/download.html

2. 微软格式(WORD,EXCEL)
如要要对微软的WORD和EXCEL格式进行本文抽取,通常的方式有两种:

1. 使用COM-Java

我 们知道,微软的WORD和Excel程序是以一种COM组件形式存在。如果能够在Java中调用其COM组件就能使用它的相当方法来获取其中的文本信息。 目前网上有许多提供这种通过Java访问COM组件的工具。不过它们中的很多都是收费的,以下是一个称之为Jacob的工具的下载地址:http://danadler.com/jacob/

2. 使用Apache的POI

POI是Apache的开源工具,它可以直接访问微软的WORD和Excel文件格式,它的下载地址为:

http://www.apache.org/dyn/closer.cgi/jakarta/poi/
 
2007-08-07 1:01
首先建立公共资源
package testlucene;

public class Constants {

   public final static String INDEX_FILE_PATH = "c:\\test"; //待搜索的文件
   public final static String INDEX_STORE_PATH = "c:\\index"; //索引的位置
}

建立索引
package testlucene;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Date;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;

public class LuceneIndex {
   public static void main(String[] args) throws Exception {
     // 声明一个对象
     LuceneIndex indexer = new LuceneIndex();
     // 建立索引
     Date start = new Date();
     indexer.writeToIndex();
     Date end = new Date();
    
     System.out.println("建立索引用时" + (end.getTime() - start.getTime()) + "毫秒");

     indexer.close();
   }

   public LuceneIndex() {
     try {
       writer = new IndexWriter(Constants.INDEX_STORE_PATH,
           new StandardAnalyzer(), true);
     } catch (Exception e) {
       e.printStackTrace();
     }
   }

   // 索引器
   private IndexWriter writer = null;

   // 将要建立索引的文件构造成一个Document对象,并添加一个域"content"
   private Document getDocument(File f) throws Exception {
     Document doc = new Document();

     FileInputStream is = new FileInputStream(f);
     Reader reader = new BufferedReader(new InputStreamReader(is));
     doc.add(Field.Text("contents", reader));

     doc.add(Field.Keyword("path", f.getAbsolutePath()));
     return doc;
   }

   public void writeToIndex() throws Exception {
     File folder = new File(Constants.INDEX_FILE_PATH);
     if (folder.isDirectory()) {
       String[] files = folder.list();
       for (int i = 0; i < files.length; i++) {
         File file = new File(folder, files[i]);
         Document doc = getDocument(file);
         System.out.println("正在建立索引 : " + file + "");
         writer.addDocument(doc);
       }
     }
   }

   public void close() throws Exception {
     writer.close();
   }

执行检索
package testlucene;

import java.util.Date;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;

public class LuceneSearch {
//   public static void main(String[] args) throws Exception {
//     LuceneSearch test = new LuceneSearch();
//     // 检索“人民”这个关键字
//     Hits h = null;
//     h = test.search("中华");
//     test.printResult(h);
//     h = test.search("人民");
//     test.printResult(h);
//     h = test.search("共和国");
//     test.printResult(h);
//   }
  
   public static void main(String[] args) throws Exception {
     LuceneSearch test = new LuceneSearch();
     Hits h = null;
     h = test.search("华人");
     test.printResult(h);
     h = test.search("共和");
     test.printResult(h);
     h = test.search("人");
     test.printResult(h);
   }

   public LuceneSearch() {
     try {
       searcher = new IndexSearcher(IndexReader
           .open(Constants.INDEX_STORE_PATH));
     } catch (Exception e) {
       e.printStackTrace();
     }
   }

   // 声明一个IndexSearcher对象
   private IndexSearcher searcher = null;

   // 声明一个Query对象
   private Query query = null;

   public final Hits search(String keyword) {
     System.out.println("正在检索关键字 : " + keyword);
     try {
       // 将关键字包装成Query对象
       query = QueryParser.parse(keyword, "contents",
           new StandardAnalyzer());

       Date start = new Date();
       Hits hits = searcher.search(query);
       Date end = new Date();
       System.out.println("检索完成,用时" + (end.getTime() - start.getTime()) + "毫秒");
       return hits;
     } catch (Exception e) {
       e.printStackTrace();
       return null;
     }
   }

   public void printResult(Hits h) {
     if (h.length() == 0) {
       System.out.println("对不起,没有找到您要的结果。");
     }
     else
     {
       for (int i = 0; i < h.length(); i++) {
         try {
           Document doc = h.doc(i);
           System.out.print("这是第" + i + "个检索到的结果,文件名为:");
           System.out.println(doc.get("path"));
         } catch (Exception e) {
           e.printStackTrace();
         }
       }
     }
     System.out.println("--------------------------");
   }

}


PS:以上在 Lucene 1.4.3 eclipse 3.2测试通过 Lucene 2.0.0 暂不行


Lucene JAR 包 下载 http://apache.justdn.org/lucene/java/
 
2007-08-07 0:59
本文主要介绍了Lucene的起源、发展、现状,以及Luence的初步应用,可以作为了解和学习Lucene的入门资料。

1.起源与发展

Lucene是一个高性能、纯Java的全文检索引擎,而且免费、开源。Lucene几乎适合于任何需要全文检索的应用,尤其是跨平台的应用。

Lucene的作者Doug Cutting是一个资深的全文检索专家,刚开始,Doug Cutting将Lucene发表在自己的主页上,2000年3月将其转移到sourceforge,于2001年10捐献给Apache,作为Jakarta的一个子工程。

2.使用现状

经过多年的发展,Lucene在全文检索领域已经有了很多的成功案例,并积累了良好的声誉。

基于Lucene的全文检索产品(Lucene本身只是一个组件,而非一个完整的应用)和应用Lucene的项目在世界各地已经非常之多,比较知名的有:

l Eclipse:主流Java开发工具,其帮助文档采用Lucene作为检索引擎

l Jive:知名论坛系统,其检索功能基于Lucene

l Ifinder:出自德国的网站检索系统,基于Lucene(http://ifinder.intrafind.org/)

l MIT DSpace Federation:一个文档管理系统(http://www.dspace.org/)

国内外采用Lucene作为网站全文检索引擎的也很多,比较知名的有:

l http://www.blogchina.com/weblucene/

l http://www.ioffer.com/

l http://search.soufun.com/

l http://www.taminn.com/

(更多案例,请参见http://wiki.apache.org/jakarta-lucene/PoweredBy)

在所有这些案例中,开源应用占了很大一部分,但更多的还是商化业产品和网站。毫不夸张的说,Lucene的出现,极大的推动了全文检索技术在各个行业或领域中的深层次应用。

3.初步应用

前面提到,Lucene本身只是一个组件,而非一个完整的应用,所以若想让Lucene跑起来,还得在Lucene基础上进行必要的二次开发。

实例地址
http://www.zdnet.com.cn/developer/tech/story/0,3800067013,39290365-2,00.htm

4.附加信息
Lucene官方
http://lucene.apache.org/

IBM.com Lucene资料
http://www-128.ibm.com/developerworks/cn/java/j-lo-lucene1/

Lucene 索引机制架构


基于Lucene/XML的站内全文检索解决方案
http://www.chedong.com/tech/weblucene.html
 
2007-08-06 23:19
1. 概述

本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。

在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示"。

2. 编码基本知识

最早的编码是iso8859-1,和ascii编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。

2.1. iso8859-1

属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母a的编码为0x61=97。

很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧 使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为 例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。

2.2. GB2312/GBK

这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。

2.3. unicode

这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1 编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前面增加了一个0字节,比如字母a为"00 61"。

需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。

2.4. UTF

考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以 unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码 是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。

注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK 无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网 页中包含了很多的英文字符。

3. java对字符的处理

在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。

3.1. getBytes(charset)

这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按 unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。

3.2. new String(charset)

这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。 参考上述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。

因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。

3.3. setCharacterEncoding()

该函数用来设置http请求或者相应的编码。

对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用 iso8859-1编码,需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前,不能执行任何 getParameter()。java doc上说明:This method must be called prior to reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候, java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。 而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无 效。

对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。

3. java对字符的处理

在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。

3.1. getBytes(charset)

这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按 unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。

3.2. new String(charset)

这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。 参考上述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。

因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。

3.3. setCharacterEncoding()

该函数用来设置http请求或者相应的编码。

对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用 iso8859-1编码,需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前,不能执行任何 getParameter()。java doc上说明:This method must be called prior to reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候, java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。 而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无 效。

对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。
3.4. 处理过程

下面分析两个有代表性的例子,说明java对编码有关问题的处理方法。

3.4.1. 表单输入

User input *(gbk:d6d0 cec4) browser *(gbk:d6d0 cec4) web server iso8859-1(00d6 00d 000ce 00c4) class,需要在class中进行处理:getbytes("iso8859-1")为d6 d0 ce c4,new String("gbk")为d6d0 cec4,内存中以unicode编码则为4e2d 6587。

用户输入的编码方式和页面指定的编码有关,也和用户的操作系统有关,所以是不确定的,上例以gbk为例。

从browser到web server,可以在表单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。上述仍旧以gbk编码为例。

Web server接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进行处理。但如果预先设 置了编码(通过request. setCharacterEncoding ()),则能够直接获取到正确的结果。

在页面中指定编码是个好习惯,否则可能失去控制,无法指定正确的编码。

3.4.2. 文件编译

假设文件是gbk编码保存的,而编译有两种编码选择:gbk或者iso8859-1,前者是中文windows的默认编码,后者是linux的默认编码,当然也可以在编译时指定编码。

Jsp *(gbk:d6d0 cec4) java file *(gbk:d6d0 cec4) compiler read uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) compiler write utf(gbk: e4b8ad e69687; iso8859-1: *) compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) class。所以用gbk编码保存,而用iso8859-1编译的结果是不正确的。

class unicode(4e2d 6587) system.out / jsp.out gbk(d6d0 cec4) os console / browser。

文件可以以多种编码方式保存,中文windows下,默认为ansi/gbk。

编译器读取文件时,需要得到文件的编码,如果未指定,则使用系统默认编码。一般class文件,是以系统默认编码保存的,所以编译不会出问题,但对于 jsp文件,如果在中文windows下编辑保存,而部署在英文linux下运行/编译,则会出现问题。所以需要在jsp文件中用 pageEncoding指定编码。

Java编译的时候会转换成统一的unicode编码处理,最后保存的时候再转换为utf编码。

当系统输出字符的时候,会按指定编码输出,对于中文windows下,System.out将使用gbk编码,而对于response(浏览器),则 使用jsp文件头指定的contentType,或者可以直接为response指定编码。同时,会告诉browser网页的编码。如果未指定,则会使用 iso8859-1编码。对于中文,应该为browser指定输出字符串的编码。

browser显示网页的时候,首先使用response中指定的编码(jsp文件头指定的contentType最终也反映在response上),如果未指定,则会使用网页中meta项指定中的contentType。

3.5. 几处设置

对于web应用程序,和编码有关的设置或者函数如下。

3.5.1. jsp编译

指定文件的存储编码,很明显,该设置应该置于文件的开头。例如:。另外,对于一般class文件,可以在编译的时候指定编码。

3.5.2. jsp输出

指定文件输出到browser是使用的编码,该设置也应该置于文件的开头。例如:。该设置和response.setCharacterEncoding("GBK")等效。

3.5.3. meta设置

指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用jsp的设置,而且也无法执行response.setCharacterEncoding()。例如:

如果同时采用了jsp输出和meta设置两种编码指定方式,则jsp指定的优先。因为jsp指定的直接体现在response中。

需要注意的是,apache有一个设置可以给无编码指定的网页指定编码,该指定等同于jsp的编码指定方式,所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。

3.5.4. form设置

当浏览器提交表单的时候,可以指定相应的编码。例如:

。一般不必不使用该设置,浏览器会直接使用网页的编码。
4. 系统软件

下面讨论几个相关的系统软件。

4.1. mysql数据库

很明显,要支持多语言,应该将数据库的编码设置成utf或者unicode,而utf更适合与存储。但是,如果中文数据中包含的英文字母很少,其实unicode更为适合。

数据库的编码可以通过mysql的配置文件设置,例如default-character-set=utf8。还可以在数据库链接URL中设置,例 如: useUnicode=true&characterEncoding=UTF-8。注意这两者应该保持一致,在新的sql版本里,在数据库链接 URL里可以不进行设置,但也不能是错误的设置。

4.2. apache

appache和编码有关的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,该功能会将所有静态页面的编码设置为UTF-8,最好关闭该功能。

另外,apache还有单独的模块来处理网页响应头,其中也可能对编码进行设置。

4.3. linux默认编码

这里所说的linux默认编码,是指运行时的环境变量。两个重要的环境变量是LC_ALL和LANG,默认编码会影响到java URLEncode的行为,下面有描述。

建议都设置为"zh_CN.UTF-8"。

4.4. 其它

为了支持中文文件名,linux在加载磁盘时应该指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。

另外,如前所述,使用GET方法提交的信息不支持request.setCharacterEncoding(),但可以通过tomcat的配置文件 指定字符集,在tomcat的server.xml文件中,形如:。这种方法将统一设置所有请求,而不能针对具体页面进行设置,也不一定和browser 使用的编码相同,所以有时候并不是所期望的。

5. URL地址

URL地址中含有中文字符是很麻烦的,前面描述过使用GET方法提交表单的情况,使用GET方法时,参数就是包含在URL中。

5.1. URL编码

对于URL中的一些特殊字符,浏览器会自动进行编码。这些字符除了"/?&"等外,还包括unicode字符,比如汉子。这时的编码比较特殊。

IE有一个选项"总是使用UTF-8发送URL",当该选项有效时,IE将会对特殊字符进行UTF-8编码,同时进行URL编码。如果改选项无效,则 使用默认编码"GBK",并且不进行URL编码。但是,对于URL后面的参数,则总是不进行编码,相当于UTF-8选项无效。比如"中文.html?a= 中文",当UTF-8选项有效时,将发送链接"%e4%b8%ad%e6%96%87.html?a=x4ex2dx65x87";而UTF-8选项无效 时,将发送链接"x4ex2dx65x87.html?a=x4ex2dx65x87"。注意后者前面的"中文"两个字只有4个字节,而前者却有18个字 节,这主要时URL编码的原因。

当web server(tomcat)接收到该链接时,将会进行URL解码,即去掉"%",同时按照ISO8859-1编码(上面已经描述,可以使用 URLEncoding来设置成其它编码)识别。上述例子的结果分别是"ue4ub8uadue6u96u87.html?a= u4eu2du65u87"和"u4eu2du65u87.html?a=u4eu2du65u87",注意前者前面的"中文"两个字恢复成了6个字符。 这里用"u",表示是unicode。

所以,由于客户端设置的不同,相同的链接,在服务器上得到了不同结果。这个问题不少人都遇到,却没有很好的解决办法。所以有的网站会建议用户尝试关闭UTF-8选项。不过,下面会描述一个更好的处理办法。

5.2. rewrite

熟悉的人都知道,apache有一个功能强大的rewrite模块,这里不描述其功能。需要说明的是该模块会自动将URL解码(去除%),即完成上述 web server(tomcat)的部分功能。有相关文档介绍说可以使用[NE]参数来关闭该功能,但我试验并未成功,可能是因为版本(我使用的是 apache 2.0.54)问题。另外,当参数中含有"?& "等符号的时候,该功能将导致系统得不到正常结果。

rewrite本身似乎完全是采用字节处理的方式,而不考虑字符串的编码,所以不会带来编码问题。

5.3. URLEncode.encode()

这是Java本身提供对的URL编码函数,完成的工作和上述UTF-8选项有效时浏览器所做的工作相似。值得说明的是,java已经不赞成不指定编码来使用该方法(deprecated)。应该在使用的时候增加编码指定。

当不指定编码的时候,该方法使用系统默认编码,这会导致软件运行结果得不确定。比如对于"中文",当系统默认编码为"gb2312"时,结果是"% 4e%2d%65%87",而默认编码为"UTF-8",结果却是"%e4%b8%ad%e6%96%87",后续程序将难以处理。另外,这儿说的系统默 认编码是由运行tomcat时的环境变量LC_ALL和LANG等决定的,曾经出现过tomcat重启后就出现乱码的问题,最后才郁闷的发现是因为修改修 改了这两个环境变量。

建议统一指定为"UTF-8"编码,可能需要修改相应的程序。

5.4. 一个解决方案

上面说起过,因为浏览器设置的不同,对于同一个链接,web server收到的是不同内容,而软件系统有无法知道这中间的区别,所以这一协议目前还存在缺陷。

针对具体问题,不应该侥幸认为所有客户的IE设置都是UTF-8有效的,也不应该粗暴的建议用户修改IE设置,要知道,用户不可能去记住每一个web server的设置。所以,接下来的解决办法就只能是让自己的程序多一点智能:根据内容来分析编码是否UTF-8。

比较幸运的是UTF-8编码相当有规律,所以可以通过分析传输过来的链接内容,来判断是否是正确的UTF-8字符,如果是,则以UTF-8处理之,如 果不是,则使用客户默认编码(比如"GBK"),下面是一个判断是否UTF-8的例子,如果你了解相应规律,就容易理解。
public static boolean isValidUtf8(byte[] b,int aMaxCount){

int lLen=b.length,lCharCount=0;

for(int i=0;i

byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;)

if(lByte>=0) continue;//>=0 is normal ascii

if(lByte<(byte)0xc0    lByte>(byte)0xfd) return false;

int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4

:lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1;

if(i+lCount>lLen) return false;

for(int j=0;j=(byte)0xc0) return false;

}

return true;

}

相应地,一个使用上述方法的例子如下:

public static String getUrlParam(String aStr,String aDefaultCharset)

throws UnsupportedEncodingException{

if(aStr==null) return null;

byte[] lBytes=aStr.getBytes("ISO-8859-1");

return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset);

}

不过,该方法也存在缺陷,如下两方面:

没有包括对用户默认编码的识别,这可以根据请求信息的语言来判断,但不一定正确,因为我们有时候也会输入一些韩文,或者其他文字。

可能会错误判断UTF-8字符,一个例子是"学习"两个字,其GBK编码是" xd1xa7xcfxb0",如果使用上述isValidUtf8方法判断,将返回true。可以考虑使用更严格的判断方法,不过估计效果不大。

有一个例子可以证明google也遇到了上述问题,而且也采用了和上述相似的处理方法,比如,如果在地址栏中输入"http: //www.google.com/search?hl=zh-CNnewwindow=1&q=学习",google将无法正确识别,而其他汉 字一般能够正常识别。

最后,应该补充说明一下,如果不使用rewrite规则,或者通过表单提交数据,其实并不一定会遇到上述问题,因为这时可以在提交数据时指定希望的编码。另外,中文文件名确实会带来问题,应该谨慎使用。

6. 其它

下面描述一些和编码有关的其他问题。

6.1. SecureCRT

除了浏览器和控制台与编码有关外,一些客户端也很有关系。比如在使用SecureCRT连接linux时,应该让SecureCRT的显示编码(不同 的session,可以有不同的编码设置)和linux的编码环境变量保持一致。否则看到的一些帮助信息,就可能是乱码

另外,mysql有自己的编码设置,也应该保持和SecureCRT的显示编码一致。否则通过SecureCRT执行sql语句的时候,可能无法处理中文字符,查询结果也会出现乱码。

对于Utf-8文件,很多编辑器(比如记事本)会在文件开头增加三个不可见的标志字节,如果作为mysql的输入文件,则必须要去掉这三个字符。(用 linux的vi保存可以去掉这三个字符)。一个有趣的现象是,在中文windows下,创建一个新txt文件,用记事本打开,输入"连通"两个字,保 存,再打开,你会发现两个字没了,只留下一个小黑点。

6.2. 过滤器

如果需要统一设置编码,则通过filter进行设置是个不错的选择。在filter class中,可以统一为需要的请求或者回应设置编码。参加上述setCharacterEncoding()。这个类apache已经给出了可以直接使 用的例子SetCharacterEncodingFilter。

6.3. POST和GET

很明显,以POST提交信息时,URL有更好的可读性,而且可以方便的使用setCharacterEncoding()来处理字符集问题。但GET方法形成的URL能够更容易表达网页的实际内容,也能够用于收藏。

从统一的角度考虑问题,建议采用GET方法,这要求在程序中获得参数是进行特殊处理,而无法使用setCharacterEncoding()的便 利,如果不考虑rewrite,就不存在IE的UTF-8问题,可以考虑通过设置URIEncoding来方便获取URL中的参数。

6.4. 简繁体编码转换

GBK同时包含简体和繁体编码,也就是说同一个字,由于编码不同,在GBK编码下属于两个字。有时候,为了正确取得完整的结果,应该将繁体和简体进行 统一。可以考虑将UTF、GBK中的所有繁体字,转换为相应的简体字,BIG5编码的数据,也应该转化成相应的简体字。当然,仍旧以UTF编码存储。

例如,对于"语言 語言",用UTF表示为"xE8xAFxADxE8xA8x80 xE8xAAx9ExE8xA8x80",进行简繁体编码转换后应该是两个相同的 "xE8xAFxADxE8xA8x80>"。
 
   
 
 
文章存档
 
     
 
最新文章评论
  

你好,我的毕业设计也是这方面的,第一次接触这些,很想向你学习学习啊,能加一下QQ
 

请问内个sudo chown ender:ender php是神马意思呢?
 

不错! 朋友,有时间可以看看免费开源的、跨平台的、易操作的kangle反向代理服务器软
 

你好 我想请教个问题 memcached-client.php 中 run_command 的用法 因为一定要用memc
 

不错,很准备、全面,收藏QQ了。
   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu