1. whoosh安装
pip install Whoosh
2. 添加索引
第一步:生成schema
第二步:根据schema生成index.index就是一个目录中的一堆文件
(针对某个目录,调用一个已存在的索引名字的index.create_in方法,会清空已存在索引的内容!).若想清除索引,可以把目录中的索引文件删掉。若目录中仅有一个索引,可以删除整个目录,然后从新生成目录即可。
第三步:根据index生成writer(调用index.writer())
第四步:writer.add_document即可
由于打开writer时会lock住索引文件,因此在某一时间,仅有一个线程或进程可打开一个writer.打开一个已被lock的writer会抛出LockError.whoosh提供了AsyncWriter和BufferWriter可以在lock状况下工作。
对于某字段,可以索引与存储分开,使用stored_<fieldname>字段即可。 writer不commit, 或者cancel, 其他线程或进程不可再打开writer
3. 编辑和删除索引
删除操作使用writer的以下三个方法
delete_document(docnum)方法 (docnum是索引查询结果的每条结果记录的docnum属性)
delete_by_term(field_name,termtext)方法 (特别适合删除ID,或KEYWORD字段)
delete_by_query(query)
删除一个index仅仅是把该document的docnum添加到一个deleted index列表中
编辑即replace,可以使用上面的delete_*方法删除后,使用add_document添加。也可以使用update_document。使用该方法时,在schema里定义的字段,至少有一个字段是定义有unique属性的。whoosh使用该unique字段查找文档,然后删除之。
1 from whoosh.fields import Schema, ID, TEXT 2 3 schema = Schema(path = ID(unique=True), content=TEXT) 4 ix = index.create_in("index") 5 writer = ix.writer() 6 writer.add_document(path=u"/a", content=u"The first document") 7 writer.add_document(path=u"/b", content=u"The second document") 8 writer.commit() 9 10 writer = ix.writer() 11 # Because "path" is marked as unique, calling update_document with path="/a" 12 # will delete any existing documents where the "path" field contains "/a". 13 writer.update_document(path=u"/a", content="Replacement for the first document") 14 writer.commit()
其实unique字段和update_document就是delete和add的一种shortcut.
4. 查询索引
第一步:若索引已添加了,调用open_dir("dir of index")即可返回index,若出现exception则说明,还没有添加任何索引。
第二步:通过调用index.searcher(),根据index生成searcher. searcher就是打开的一堆文件,用完一定要关闭。 searcher有很多获取索引信息的有用方法,
比如lexicon(fieldname):得到某字段的全部分词。当然其中最重要的方法就是search方法。
第三步: 生成查询对象, 有两种方式:
直接构建query对象:
类似于:myquery = And([Term("content", u"apple"), Term("content", "bear")])
query language:
就是解析查询的字符串后,生成一个query对象
step1, 通过 parser=QueryParser("content",index.schema),先生成一个parser. 其中content是若不定义字段时,默认的查询字段。
step2, 调用parser.parse(querystring),分析query,生成query对象
第四步: 以query对象为参数调用searcher的search方法.得到查询result.
默认的search方法的results最多仅返回10个匹配的文档。若想返回更多,就要使用limit参数查询了:
results = searcher.search(query,limit=20). 若要得到全部的结果,可把limit=None.
search_page(query,page)方法可已让你得到某page的结果,默认是一页10个hit,可以使用pagelen参数调整
search_page(query,page,pagelen=20)
Results对象表现的就像是list一样:可进行len(results), results[0], results[0:2]
由于默认的results仅返回10条记录,所以results的scored_length()可能会小于索引中实际匹配的总条目数。
而调用len(result),其实是运行了一个快速的非计分版本的查询,得到的是匹配的全部文档数。这个调用通常很快速,但是若是遇到一个很大的索引的话,会有明显的延迟。
若要避免这种延迟,可以使用has_exact_length(), estimated_length(), and estimated_min_length() 去估算匹配的文档数目而不是使用len()
len()返回的是全部的满足条件的文档数量,estimate_*返回的是预估的数量,若预估已确切,则是确切的数量.scored_length()返回的数值小于等于limit
可以在search方法中设置terms=True,来记录:在terms查询中,哪个term匹配哪个document.然后通过results.matched_terms()调用,返回results中匹配了哪些term.
在hit中调用matched_terms()返回,在hit中匹配了哪些terms, 设置terms=True,还可以加速查询结果高亮的处理
5. 使用查询结果
scoring:
一般而言,result文档列表使用score值来排序。whoosh.scoring模块包含了多个不同scoring算法的实现,默认是BM25F.
当初始化searcher时可以用weighting关键字去替换默认的scoring算法。可以继承WeightingModel实现定制权重算法.
过滤结果:
对searcher对象的search方法使用filter参数,可以限定results中的允许的文档。也可以使用mask参数对限定results中不允许的文档.参数值可以是query对象,results对象,也可以是承载docnum的set集合。
排序和分类:
对查询结果的排序和分类是基于facet的。在查询结果中的每个文档里的每个facet都会和一个值相关,使用这个数值,可以让你对结果排序或分组。
查询结果在默认情况下,是scoring最高的值排在前面。可以使用在search方法中使用sortedby参数定义其他的条件对查询结果排序。
生成一个可排序的字段:
为了可以针对某字段排序,在定义schema时,需对该字段添加sortable=True参数.也可以针对没定义sortable=True的字段排序,但是比较没有效率。
合并results对象:
Results.extend(results):把results加在Results后
Results.filter(results):把results中的文档从Results中移除
Results.upgrade(results):把出现在results中的Results文档,移到Results的前头
Results.upgrade_and_extend(result),出现在results中的Results文档,移动到Results的前头,而那些不在Results中的results文档,则添加到Results后头。
6. 摘要并高亮查询结果
高亮的过程就是一个pipeline的处理过程,和4个元件相关:
Fragments : 基于匹配的term在文档中的位置,把原始的文档砍成__fragments__
Scorers: 给每个fragment赋一个分值,允许系统根据分值排出最好的fragment.
Order functions: 控制展示给用户的fragment,是展示先出现在文档里的fragment,还是展示score最高的fragment.
Formatters: 把对应的fragment格式化为用户可读格式,如html
在对文本进行高亮处理前,需要去除格式标签,比如:html,和 wiki tags.
results = mysearcher.search(myquery) for hit in results: print(hit["title"]) # Assume "content" field is stored print(hit.highlights("content"))
若高亮的字段没有stored在index中,则需要通过其他途径得到这个字段的text.
whoosh默认仅从文本的前32k字符中抽取fragment. 可以通过调整参数来增加这个值。
results = mysearcher.search(myquery)
results.fragmenter.charlimit = 100000
或者
results.fragmenter.charlimit = None 直接关闭限制
也可以初始化一个定制的framenter, 直接在fragment上设置字符限制:
sf = highlight.SentenceFragmenter(charlimit=100000)
results.fragmenter = s
定制高亮:
fragment的数量:
可以使用top参数控制每个文档返回的fragment的数量
print hit.highlights("content", top=5)
fragment的size:
fragmenter的maxchar属性控制fragment的最大长度,默认是200. surround属性控制fragment里关键字前后截取的背景文字长度,默认是20
# Allow larger fragments results.fragmenter.maxchars = 300 # Show more context before and after results.fragmenter.surround = 50
Fragmenter:
Fragmenter控制如何从原始文本中提取摘录文字. Whoosh.hightlight包里已有以下预制的fragmenter:
whoosh.highlight.ContextFragmenter (默认)
这是一个默认的智能fragment,可以发现匹配的term, 并抽取周围的文字生成 fragment. 这个fragmenter只产生包含匹配term的fragment.
whoosh.highlight.SentenceFragmenter
依标点符号,按句子抽取
whoosh.highlight.WholeFragmenter
返回整个文本作为fragment,高效(不做处理,当然高效)适合小文本
不同的fragmenter有不同的选项参数
Scorer:
Scorer是一个可调用的对象,使用fragment作为参数,返回一个可排序的值(值越大,代表该fragment质量越好)。 高亮系统使用这个值去挑选出展示给用户的最佳fragment.
#定义一个新的scorer def StandardDeviationScorer(fragment): """Gives higher scores to fragments where the matched terms are close together. """ # Since lower values are better in this case, we need to negate the # value return 0 - stddev([t.pos for t in fragment.matched]) #使用这个scorer results.scorer = StandardDeviationScorer
Order:
order是一个函数,该函数以fragment为参数,返回一个可排序的值,通过该值对fragment进行排序。
whoosh.highlight模块有一下的排序函数:
FIRST(默认的)
按出现顺序
SCORE:
按得分顺序
还有其他不常用的: LONGER长fragment优先,SHORTER短fragment优先
使用非默认的排序函数:
results.order = hightlight.SCORE
Formatter:
formatter负责控制分值最高的fragment如何以格式化的方式呈现给用户。通过设置不同的formatter可以返回任何格式,包括:html,genshi事件流,SAX事件生成器,等。
highlight模块有一下预置实现
whoosh.highlight.HtmlFormatter
以带class属性的html标签包围的方式返回匹配的term
whoosh.highlight.UppercaseFormatter
把匹配的term以大写字母呈现
whoosh.highlight.GenshiFormatter
输出一个genshi事件流
替换formatter的方式和上面替换其他三个元件的方式一致,更改results.formatter属性即可