python应用程序Hypy实例教程

2009-09-17 21:26:59  来源:外文翻译 

WebjxCom提示:hypy 是一个可用于 python 应用程序的全文检索模块, 通过它你可以很方便的使用 python 代码来对你的文档(或者数据库内容)索引并进行全文检索. hypy 基于 Mikio Hirabayashi 编写的 C 库 Hyper Estraier.

重要的类和方法
—————————-

HDatabase
~~~~~~~~~~~
表示索引后的数据存储.

关键方法: db.open(filename,mode), db.close(), db.putDoc(document), db.search(condition)

HDocument
~~~~~~~~~~

标示一条被索引并存储的记录.

关键方法: doc.addText(text), doc.addHiddenText(text), doc.encode(encoding)

HDocument 的行为类似字典, key 保存记录的元数据(比如字段名)
比如 doc[u’@uri’] 返回文档(记录)的 @uri 属性. 它支持所有的字典方法.

HHit
~~~~~~
HDocument 的一个子类, 表示搜索结果中的一条记录.  它拥有 HDocument 的全部特性, 还多了一个关键方法: result.teaser(wordList)

HResults
~~~~~~~~~~

代表搜索结果, 它是 HHit 的集合. 关键方法: results.hitWords(), results.pluck(attr)

HResults 的行为类似字典, 这样你就可以迭代它.

HCondition
~~~~~~~~~~~

查询对象, 用来实施一次搜索. 带着搜索参数(如关键字, 返回的最大结果数, 或者其他元数据)创建一个查询实例.

关键方法: cond.addAttr(attributeExpression), cond.setOrder(orderExpression)

例子
——–

后面例子的代码依赖前面的例子. 如果你打算运行所有的例子, 你最好从上到下顺序运行. 如果你只是随便看看, 那就无所谓了, 挑你感兴趣了例子阅读就可以了.

打开一个索引库
~~~~~~~~~~~~~~~~

在使用索引库(或者称之为数据库)之前, 必须先(指定一个磁盘路径)创建它.

创建索引库的标志和 Python 内建函数 open() 一样:

‘r’ :flag
只读方式打开
‘w’ :flag
如果文件存在, 先删除或清空, 然后打开文件, 准备写入数据
‘a’ :flag
为写入而打开文件, 如果文件不存在, 自动创建之(最常用的方式)

例子:

from hypy import *   # 在生产环境不要这样做
# import * 是一个坏习惯, 我在这儿这么写只是图一时省事.

INDEX = ‘breakfast/’
db = HDatabase()
db.open(INDEX, ‘w’) # 创建新库, 旧库如果存在则删除之
db.close()
db.open(INDEX, ‘a’)

抓取内容
~~~~~~~~

Hypy 本身不是 web 爬虫, 不过因为它依赖 Hyper Estraier, 所以也就顺手拥有了爬虫的功能. 妙事一桩! 下面展示来如何使用 Hyper Estraier’s spider, 它的名字叫 estwaver. 关于 estwaver 的更多细节请问 google .

(Bash 语法) 例子:

$ cd ~/projects/
$ estwaver init hypysite
2009-02-21T23:18:45Z    INFO    the root directory created

estwaver init 做的事情和上面例子中的 db.open(INDEX,’w') 基本一样, 有一点不同, 就是它还创建了一个样板配置文件: hypysite/_conf.

编辑这个配置文件. 修改最顶部的 seeds 为你打算抓取的 url. 如果你打算限制一下抓取范围, 可以修改定义允许访问 url 的正则表达式.

# 原始文件
seed: 1.5|http://hyperestraier.sourceforge.net/uguide-en.html
seed: 1.0|http://hyperestraier.sourceforge.net/pguide-en.html
seed: 1.0|http://hyperestraier.sourceforge.net/nguide-en.html
seed: 0.0|http://qdbm.sourceforge.net/

# allowing regular expressions of URLs to be visited
allowrx: ^http://

我修改后的文件如下, 我增加了几条规则以避免对内建的 wiki 内容再次索引

# 我的修改
seed: 1.0|http://mysite.goonmill.org/

allowrx: ^http://(|[a-zA-Z0-9_]*\.)goonmill\.org
denyrx: ^http://wiki\.goonmill\.org/Help
denyrx: ^http://wiki\.goonmill\.org/.*Wiki
denyrx: ^http://wiki\.goonmill\.org/.*\?action=
denyrx: ^http://wiki\.goonmill\.org/SystemPages

# 保留剩下的 denyrx 不动.

现在可以用 estwaver crawl 命令来抓取内容了, 注意要告诉它索引建立在什么地方(./hypysite).

$ estwaver crawl hypysite
2009-02-21T23:44:43Z    INFO    DB-EVENT: status: name=hypysite/_index …
2009-02-21T23:44:43Z    INFO    crawling started (continue)
2009-02-21T23:44:43Z    INFO    fetching: 0: http://goonmill.org/
2009-02-21T23:44:44Z    INFO    seeding: 1.000: http://goonmill.org/
2009-02-21T23:44:45Z    INFO    [1]: fetching: 0: http://goonmill.org/
2009-02-21T23:44:45Z    INFO    [2]: fetching: 1: http://goonmill.org/cory

2009-02-21T23:47:02Z    INFO    DB-EVENT: closing: name=hypysite/_index …
2009-02-21T23:47:02Z    INFO    finished successfully

CRUD (Create/Read/Update/Delete) 创建/阅读/更新/删除
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

如果你的内容不是来自抓取, 或者需要手工添加内容, 我们提供了下面的例子来直接访问 documents.

简单例子:
^^^^^^^^^

doc = HDocument(uri=u’http://estraier.gov/example.txt’)
doc.addText(u”Hello there, this is my text.”)
db.putDoc(doc)

我仅仅添加了一个简单文档到索引库, 该文档的 uri 属性是 http://estraier.gov/example.txt. 注意! 无论是 uri 还是 text 都是 unicode 字符串. Hypy 总是要求 unicode 字符串

只有索引库刷新之后, 这篇文档才能被搜索到. 以下操作会刷新索引库:

* 调用索引库对象的 close() 方法关闭掉
* 调用索引库对象的 flush() 方法明确刷新索引
* 如果 autoflush 选项打开的话, 调用 putDoc() 方法也可以刷新索引库

打开 autoflush 选项的方式:

# db = HDatabase(autoflush=True)  ## 或者
db.autoflush = True
# 打开这个选项的操作本身并不刷新索引库, 所以接下来要手工执行一句
db.flush()

Tip

Autoflush 会增加磁盘 IO 负载, 为提高性能, 当你要索引大批文档时, 请务必关掉 autoflush 选项, 你应该每索引 n 个文件, 手动执行一次 flush(), 这个 n 的值要大到能够显著降低磁盘负载, 并且不会填满你的内存. 自己把握吧, 嘿嘿.

所有的文档都要有一个 @uri 属性, 以便初始化 HDocument() 对象. 不过, 你也可以添加任意的其他命名属性到文档中.

doc2 = HDocument(uri=u’http://estraier.gov/pricelist.txt’)
doc2.addText(u”"”Coffee: $2.00
Toast: $1.00
Eggs (2, any style): $3.00          Eggs (9, any style): $13.50
Eggs (3, any style): $4.50          Eggs (10, any style): $15.00
Eggs (4, any style): $6.00          Eggs (11, any style): $16.50
Eggs (5, any style): $7.50          Eggs (12, any style): $18.00
Eggs (6, any style): $9.00          Eggs (13, any style): $19.50
Eggs (7, any style): $10.50         Eggs (14, any style): $21.00
Eggs (8, any style): $12.00         Eggs (15, any style): $22.50

with apologies to the New Yorker for this joke”"”)
doc2[u’maxprice’] = u’22.50′
doc2[u’minprice’] = u’1.00′
db.putDoc(doc2)

有些属性是自动生成的:

print doc[u’@id’]
## prints 1
print doc[u’@uri’]
## prints http://estraier.gov/example.txt

你可以通过指定一个 id 从索引库中移除特定的文档, 也能通过其他内建的属性来移除任意一个文档

db.remove(id=1)
db.putDoc(doc) # 把它放回去, 以便在删除它一次
db.remove(doc)
db.putDoc(doc) # 再加进去
db.remove(uri=u’http://estraier.gov/example.txt’)
# 注意, 每次我们添加这个文档, 都会获得一个新的 id
print doc[u’@id’]
## prints 4

删除文档, 再把它放回去这种做法, 就是更新文档的标准做法. (哈哈, 原来这样!)

那么, 嗯, 我怎么知道索引库里一共有多少个文档? python 内建函数 len() 可以告诉你答案. 现在我们来确认一下 db 里还剩一个文档:

print len(db)
## prints 1

你可能猜到, 你能够通过 uri 属性获取一个 document, 使用类似字典的获取元素的语法:

print db[u’http://extraier.gov/pricelist.txt’]
## prints @digest=caacaefddcc1fd244de251723b0814be
##        @id=2
##        @uri=http://estraier.gov/pricelist.txt
##        …

现在打印出来的是 “draft” 格式, 这是这篇文档的内部表示, 也就是str(doc) 的结果. 我们不推荐用这样的手段获取文档. 你应该用 encode() 方法来得到自己想要的表示:

print doc2.encode(’utf-16′)
## prints ÿþC^@o^@f^@f^@e^@e^@:…

创建文档的复杂例子
~~~~~~~~~~~~~~~~~~~~

Hypy 没有提供一种直接的方式决定搜索结果的权重, 有一种变通的方式, 就是你在做索引的时候决定权重. 如果你使用 estwaver 抓取资料, 改变 seed 的值: 每行不同的 seed 值, 会导致不同的权重. 如果你手工添加文档, doc.addHiddenText(text) 可以帮你改变某个关键词的权重.

文档的权重主要是根据搜索关键字在该文档出现的次数计算得来的. 如果你想让某个关键字所占的权重特别大, 很容易, 简单的添加一段隐含文本:

doc5 = HDocument(u’http://estraier.gov/weighted.txt’)
doc5.addText(u”This is my boom-stick.”)
doc5.addHiddenText(u”eggs ” * 30)
db.putDoc(doc5)

当搜索 eggs 关键字时, 第三个文档的得分将超过 doc2 . 当你打印它的时候, 你看不到这些隐含的文档.

print doc5.encode(’utf-8′)
# prints This is my boom-stick.

搜索, 读取搜索结果
~~~~~~~~~~~~~~~~~~~~~

那么, 怎么进行搜索 ? 简单的很: 构建查询条件, 然后调用 db.search(condition). 搜索结果对象是一个类似列表的对象.

cond = HCondition(u’eggs’)
results = db.search(cond)
for doc in results:
print doc[u’@id’]
# prints 5 then 2

# 使用 pluck 方法能得到每个结果文档的某一属性:

print results.pluck(u’@id’)
# prints [u’5′, u’2′]

搜索关键字还支持通配符:

cond = HCondition(u’egg*’)
results = db.search(cond)
print len(results)
# prints 2
print results[0][u’@uri’]
# prints …/weighted.txt

还有些其他的模式. 默认搜索模式是 “simple”, 多个搜索关键字取其交集 ( 与 查询). 通过在查询条件中增加 matching 参数, 可以改变这一默认行为(变成 或 查询), 就是不指定 matching 参数, 也有别的办法改变默认行为, 在关键字中使用特殊语法. 建议你使用关键字语法, 这样能灵活的控制搜索结果.

doc6 = HDocument(uri=u’http://estraier.gov/spam.txt’)
doc6.addText(u’spam and eggs’)
db.putDoc(doc6)  # document @id is 6

# simple, 与 查询:
print db.search(HCondition(u’spam* eggs*’)).pluck(u’@id’)
# prints [u’6′]

# union, 或 查询
print db.search(HCondition(u’eggs spam’, matching=’union’)).pluck(u’@id’)
# prints [u’5′, u’2′, u’6′]

# unions with simple matching - you cannot use wildcard matches with
# matching=’union’ but you can do so with ‘|’ syntax
print db.search(HCondition(u’egg* | spam’)).pluck(u’@id’)
# prints [u’5′, u’2′, u’6′]

Hyper Estraier 用户指南里有完整的搜索语法.

最后, 你可以获取一个文档的抽象, 称为 “teaser”, 嗯, 就是使用一个叫 teaser() 的方法得到它. 这个方法目前支持两种速度输出格式, html 和 rst. 你必须以列表的形式提供要高亮的关键字.

words = [u’toast’]
results = db.search(HCondition(u’ ‘.join(words)))
hit = results[0]
print hit.teaser(words) # default is ‘html’
# prints Coffee: $2.00 <strong>Toast</strong>: $1.00 Eggs (2, …

# 另一种强调搜索关键字:
words = results.hintWords()

print hit.teaser(words, format=’rst’)
# prints Coffee: $2.00 **Toast**: $1.00 Eggs (2, …

属性搜索
~~~~~~~~~~~~

hypy 使用一种功能强大的语法支持属性搜索

cond = HCondition()
cond.addAttr(u’@id STREQ 5′)

print db.search(cond)[0][u’@id’]
# prints 5

后面的例子就比较轻松了, 我们也可以稍事休息一会儿:

# 为了让后面的例子清晰, 我定义了下面的函数:
def attrSx(expr):
cond = HCondition()
cond.addAttr(expr)
return u’ ‘.join(db.search(cond).pluck(u’@id’))

# 给 doc6 添加一些有趣的属性
doc6[u’maxprice’] = u’100′
doc6[u’minprice’] = u’0′
doc5[u’date’] = u’2009-01-01′
doc6[u’date’] = u’2009-02-02′

# 提交这个 doc, 注意刚才的代码并没有刷新索引库, 在这个例子里, 我们使用 autoflush
db.putDoc(doc6)
db.putDoc(doc5)

数字属性搜索:

print attrSx(u’maxprice NUMGE 50′)
# prints 6

# 注意: 没有这个属性的文档, 会被看作是拥有这个属性, 且属性值为 “0″. 因此所有的文档都匹配下面这个查询条件:
print attrSx(u’minprice NUMLE 50′)
# prints 2 6 5

# 两个文档匹配下面这个条件:
print attrSx(u’minprice NUMLE 0.99′)
# prints 2 5

日期比较视为数字比较:

print attrSx(u’date NUMGE 2008-12-31′)
# prints 6 5

print attrSx(u’date NUMGE 2009-01-30′)
# prints 6

支持正则表达式:

print attrSx(u’@uri STRRX (pricelist.txt|spam.txt)’)
# prints 2 6

你可以对条件取反, 得到 1 个匹配:

print attrSx(u’@uri !STRRX (pricelist.txt|spam.txt)’)
# prints 5

当然啦, 你能够在短语搜索条件基础之上增加属性条件, 条件之间默认是 与 结合.

cond = HCondition(u’spam’)
cond.addAttr(u’minprice NUMLE 50′)
print db.search(cond)[0][u’@id’]
# prints 6

其他搜索选项
~~~~~~~~~~~~~~

唷!这么多搜索选项!. 呵呵, 这还不是全部. 你还可以限制返回的结果总数, 或者改变搜索结果的顺序.

print db.search(HCondition(u’e*’)).pluck(u’@id’)
# prints [u’5′, u’2′, u’6′]
print db.search(HCondition(u’e*’, max=2)).pluck(u’@id’)
# prints [u’5′, u’2′] .. what did you expect?

如果你喜欢 max, 那么你也可能会喜欢 skip.

print db.search(HCondition(u’e*’, skip=2)).pluck(u’@id’)
# prints [u’6′]

要改变结果顺序, 在 condition 对象上调用 setOrder(order) 方法.  Hyper Estraier 用户指南上有一个完整的 order 表达式参考. 下面的例子通过设置排序方法改变查询结果的默认顺序.

cond = HCondition(u’e*’)

# natural (scored) order
print db.search(cond).pluck(u’@id’)
# prints [u’5′, u’2′, u’6′]

# numeric ascending
cond.setOrder(u’@id NUMA’)
print db.search(cond).pluck(u’@id’)
# prints [u’2′, u’5′, u’6′]

# numeric descending
cond.setOrder(u’@id NUMD’)
print db.search(cond).pluck(u’@id’)
# prints [u’6′, u’5′, u’2′]

其他参考文档
————-

已经讲的足够多啦, 如果你觉得还不过瘾, 好办!请接着去深挖:

1. API 文档, 真的很棒!
2. Hyper Estraier 用户指南描述了关键字搜索语法, 属性搜索语法和排序语法.
3. Hypy 的单元测试文件中有丰富的搜索语法的例子特别是 TestDatabase.test_queries 和 TestDatabase.test_condExtras 中. 这些测试付给了 lib.py 中 100% 的代码. 他们有完善的 文档字符串 和 注释; 象 skip 和 max 搜所还有各种各样的比较例子等等全都有!

更多