Whoosh + jieba 中文检索

背景

最近项目要用到 Whoosh 一个 Python 编写的索引检索模块,发现比较少中文资料并且看了学长的代码也好多不懂,故自己照着官网文档撸了一遍,把我自己的理解和官网一些不太清楚的解释写下来。

快速上手

几个核心对象

IndexSchema 对象

在使用 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 允许使用查询原语 ANDORNOT 就像 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)

还是很方便的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容