用 Lucene 构造垂直搜索引擎

Lucene 是用于全文检索的开源库,Apache 软件基金会提供支持。它由 Java 语言开发,也提供 Python 接口调用。

本文介绍使用开源项目 Lupyne 构建垂直搜索引擎,搜索本地网页中的内容。它使用 Python 语言编写,搜索功能用 Lucene 引擎实现,使用 html2text 从本地网页中提取数据,实现对网页中文本的搜索,前端调用 CherryPy 框架(flask 的 web server 常用作开发测试。而 cherrypy 的 web server 常用于生产环境),提供网页搜索功能。

运行文中实例需要匹配 Java,Python,Lucene 等各个软件版本,环境配置比较复杂,因而基于 Lucene 提供的 docker image 环境构建。

Lucene 元素

使用 Lucene 之前,先来了解一些相关概念。

Directory:指定如何保存构建的索引,测试时常保存在内存中,实际应用中,一般将其保存在文件系统中,本例将索引保存在/tmp/a 目录下。

Analyzer:分析器,用于处理文本,如分词,去停用词等。

IndexWriter:对索引进行增删查改的工具,一般的操作都围绕 IndexWriter 展开。

Document:构造搜索的基本单位,一般是网页、文档、邮件等。

Field:一个 Document 可能包含多个域 Field,比如标题、正文、作者等。

实例

下面介绍垂直搜索引擎的具体构建方法。

下载 Lupyne 源码

1
$ git clone [https://github.com/coady/lupyne](https://github.com/coady/lupyne) 

下载 docker 镜像(在 Linux 系统中运行)$ docker pull coady/pylucene # 约 2.52G

运行镜像

1
$ docker run --rm -v /exports:/exports --net=host -it coady/pylucene bash

启动 docker 时使用了 --net=host,使 docker 内外使用相同端口号访问,-v 将宿主机目录映射到镜像内部目录。

在镜像内安装支持软件

1
2
3
4
5
6
7
$ cd /exports/xxx # 切换到lupyne源码所在目录
$ python setup.py install
$ pip install cherrypy
$ pip install pytest
$ pip install clients
$ pip install jupyter # 供进一步开发使用
$ pip install html2text # 解析html内容

运行 jupyter 开发环境:

1
$ jupyter notebook --allow-root -y --no-browser --ip=0.0.0.0

在 docker 之外的浏览器中访问示例文件:http://localhost:8888/notebooks/docs/examples.ipynb,其中对比使用 lupyne 封装与直接使用 pylucene 分别建立索引、查询、检索、排序和分组的不同代码,可直接运行。

读取目录下的网页内容,写入索引数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import lucene
from lupyne import engine
from org.apache.lucene import analysis, document, index, queryparser, search, store
import os
import html2text

def parse_html(root_dir, path, indexer, debug=False):
f = open(path,"r")
content = f.read()
text = html2text.html2text(content)
strings = [i for i in text.split("\n") if len(i) > 0]
for i in strings:
if debug:
print(len(strings), "insert", i)
path = path.replace(root_dir, "http://localhost:8080/")
indexer.add(content=i, path=path)
indexer.commit()

def insert_dir(root_dir, book_dir):
html_dir = os.path.join(root_dir, book_dir)
for root, dirs, files in os.walk(html_dir):
for f in files:
if f.endswith("html") or f.endswith("htm"):
path = os.path.join(root, f)
print(path)
parse_html(root_dir, path, indexer)

def search(keyword):
hits = indexer.search(keyword, field='content')
for hit in hits:
print(hit)

if __name__ == '__main__':
BOOK_ROOTDIR = '/exports/books/contents/' # 数据根目录,与Lucene中配置文件一致
assert lucene.getVMEnv() or lucene.initVM()
indexer = engine.Indexer(directory="/tmp/a/") # 数据存储目录
indexer.set('content', engine.Field.Text, stored=True)
indexer.set('path', engine.Field.Text, stored=True)
# 插入
insert_dir(BOOK_ROOTDIR, '知识图谱/text')
# 搜索
search('Lucene')
indexer.close()

设置 Lupyne 的配置文件:

1
2
3
4
[global]
tools.staticdir.on=True
tools.staticdir.dir='/exports/books/contents/'
tools.staticdir.section=''

启动 Lupyne 服务

1
$ python -m lupyne.server /tmp/a -c a.cfg

搜索:在浏览器打开 http://localhost:8080/search?q=content:lucene 如只取前三个使用:http://localhost:8080/search?q=content:lucene&count=3

打开被搜索网页:http://localhost:8080/知识图谱/text/part0013_split_006.html

此时,就实现了搜索本地网页的最简版本垂直搜索引擎,尽管网页还比较简陋。

扩展

Elasticsearch (ES) 是一个开源的搜索引擎,建立在 Lucene 基础之上。Lucene 可能是目前存在的,不论开源还是私有的,拥有最先进,高性能和全功能搜索引擎功能的库。但是 Lucene 仅仅只是一个库,相对来说 ES+Kibana 更像是一套相对成熟的方案。它提供 Web 服务,客户端可通过 Http 请求或者 API 接口增删查改,它相对于 Lupyne 更复杂,功能也更多。当然无论使用何种底层库,都需要自己解释和插入数据。