背景
最近项目要用到 Whoosh 一个 Python 编写的索引检索模块,发现比较少中文资料并且看了学长的代码也好多不懂,故自己照着官网文档撸了一遍,把我自己的理解和官网一些不太清楚的解释写下来。
快速上手
几个核心对象
Index
和 Schema
对象
在使用 Whoosh 前,首先需要创建的就是 index 对象,index 对象是一个全局索引。在创建 index 对象前首先要声明 index 对象的一些属性,所以需要在创建一个用于包装这些属性的 schema 对象。schema 有很多 Fields(一个 Field 是 index 对象的一个信息块,即需要被我们检索的内容)
举个栗子,以下代码创建了一个包含 "title" 和 "path" 和 "content" 三个 Fields 的 schema 对象
from whoosh.fields import Schema, TEXT, ID
schema = Schema(title=TEXT, path=ID, content=TEXT)
创建 schema 对象时需要用关键字来映射 Field name 和 Field type,如上的 title=TEXT
一旦创建好了 schema 对象,接着就是使用 create_in
方法来创建 schema 的索引
import os.path
from whoosh.index import create_in
if not os.path.exists("index"):
os.mkdir("index")
idx = create_in("index", schema)
接着可以用以下两种方法打开一个已创建的索引
# 方法一 使用FileStorage对象
from whoosh.filedb.filestore import FileStorage
storage = FileStorage(idx_path) #idx_path 为索引路径
idx = storage.open_index(indexname=indexname, schema=schema)
# 方法二 使用open_dir函数
from whoosh.index import open_dir
idx = open_dir(indexname=indexname) #indexname 为索引名
IndexWriter
对象
一旦有了 index 对象,我们就需要在 index 里写入需要被检索的信息,所以 IndexWriter
对象就是用来提供一个 add_document(**kwargs)
方法来在之前声明的各种 Fields 里写入数据
writer = idx.writer() #IndexWriter对象
writer.add_document(
title=u"Document Title",
path=u"/a",
content=u"Hello Whoosh"
) # Field 和 schema 中声明的一致
writer.commit() # 保存以上document
需要注意的是:
- 不是每个 Field 都要赋值
- Field 传值一定是 unicode 类型的值
如果有一个 Field 同时要被当做索引并保存之,那么可以用一个 unicode 值来做索引同时保存另一个对象
writer.add_document(title=u"Title to be indexed", _stored_title=u"Stored title")
如果需要异步处理可以创建异步的 IndexWriter 对象
from whoosh.writing import AsyncWriter
writer = AsyncWriter(index=index)
如果需要Buffer进行处理可以创建 BufferedWriter 对象
from whoosh.writing import BufferedWriter
# period是多次commit的最大间隔时间,limit是需要缓存的最大数量
writer = BufferedWriter(index=index, period=120, limit=20)
Searcher
对象
在开始搜索索引之前,我们需要创建 searcher 对象
searcher = idx.sercher()
但是一般来说不会这么创建搜索器 searcher ,这样做没法来索引检索完成后关闭搜索器释放内存(只要知道 searcher 很吃内存就行),我们一般用 with
来创建 searcher 对象从来保证搜索器使用完毕后可以被正确关闭
with idx.sercher() as searcher:
...
以上写法等同于
try:
searcher = idx.searcher()
...
finally:
searcher.close()
搜索器的 search()
方法需要传入一个 Query 对象,我们可以直接构造一个 Query 对象或者使用 query parser 来解析一个查询字段
举个栗子
# 直接构造查询对象
from whoosh.query import *
myquery = And([Term("content", u"apple"), Term("content", "bear")])
默认的 QueryParser 允许使用查询原语 AND
和 OR
和 NOT
就像 SQL 一样简单!
# 使用解析器解析查询字段
from whoosh.qparser import QueryParser
parser = QueryParser("content", idx.schema)
myquery = parser.parse(querystring)
构造完查询对象后,就可以使用搜索器的 search()
方法来进行检索
results = searcher.search(myquery)
print(results[0])
{"title": "Document", "content": "Hello Whoosh"}
更通常的我们使用分页查询 search_page()
的方法
results = searcher.search_page(myquery, page_num, page_len)
值得注意的是 search()
接收一个默认参数 weighting=BM25F
这是搜索的权重算法,它是个 whoosh.scoring.Weighting
对象,通过使用内置的 score 方法来计算搜索的优先级从而查询文档索引
结合 jieba 分词使用
Whoosh 的基本用法如上,接着我要在 QueryString 中加入结巴分词分析模块
由于 jieba 0.30 之后的版本已经添加用于 Whoosh 的分词接口: ChineseAnalyzer, 所以还是很方便的
首先在 Whoosh schema 对象的创建的 whoosh.fields.TEXT
,默认的声明 TEXT
时字段的 FieldAttributes
默认有个属性 analyzer
analyzer 是一个带有 __call__
魔术方法的类,用来进行 TEXT 词域的分析,在调用时会把 TEXT 域里的值进行 __call__
处理
analyzer 接收的参数是一个 unicode 字符串,返回值是字符串切分,举个栗子
e.g.(
param = "Mary had a little lamb"
return = ["Mary", "had", "a", "little", "lamb"]
)
使用的是 Whoosh 的 StandardAnalyzer
,是英文的分词器。为了对接上 jieba,做中文分词,需要把 TEXT(analyzer=analysis.StandardAnalyzer())
换成 jieba 的 ChineseAnalyzer
即可
from __future__ import unicode_literals
from jieba.analyse import ChineseAnalyzer
analyzer = ChineseAnalyzer()
schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True, analyzer=analyzer))
idx = create_in("test", schema)
writer = idx.writer()
writer.add_document(
title="test-document",
path="/c",
content="This is the document for test"
)
writer.commit()
searcher = idx.searcher()
parser = QueryParser("content", schema=idx.schema)
for keyword in ("水果","你","first","中文","交换机","交换"):
print("result of ",keyword)
q = parser.parse(keyword)
results = searcher.search(q)
for hit in results:
print(hit.highlights("content"))
print("="*10)
还是很方便的。