使用 Lucene 搜索你的 Bean —— Highlighting

作者:来自 Elastic David Pilato

假设我们找到了一些与查询匹配的文档。但我们怎么知道,它具体匹配了文本中的什么位置?让我们尝试将存储的 title、artist 或 genre 中匹配的 token 加粗。

Suggest 和 highlighting 在 UI 中看起来很相似 ------ 两者都会用 <b> 包裹匹配的字母 ------ 但它们并不是同一个调用。

AnalyzingInfixSuggester.lookup(..., highlight=true) 从第二个 dictionary 中返回一个显示字符串 (highlightKey)。chip 的值保持干净(key);加粗 markup 从不会被存储。

UnifiedHighlighter 运行在 track 索引 上,使用刚刚为搜索结果评分的相同 Query 。它读取存储的字段文本并标记 token 的 offset ------ 包括尾随的 PrefixQuery(sincla → <b>Sinclar</b>)。

一个是"帮我编写 query"。另一个是"告诉我这一行为什么匹配"。

高亮我的搜索结果

Highlighting 位于自己的 artifact 中:

xml 复制代码
`

1.  <dependency>
2.    <groupId>org.apache.lucene</groupId>
3.    <artifactId>lucene-highlighter</artifactId>
4.    <version>10.5.1</version>
5.  </dependency>

`AI写代码

WholeBreakIterator 会将完整的存储值作为一个 fragment ------ 对于较短的 metadata 字段,不进行句子切分。使用与 playground 相同的 builder:

java 复制代码
`

1.  Query bob = new BooleanQuery.Builder()
2.    .add(new BoostQuery(new TermQuery(new Term("title", "bob")), 4.0f), BooleanClause.Occur.SHOULD)
3.    .add(new BoostQuery(new PrefixQuery(new Term("title", "bob")), 1.0f), BooleanClause.Occur.SHOULD)
4.    .add(new BoostQuery(new TermQuery(new Term("artist", "bob")), 3.0f), BooleanClause.Occur.SHOULD)
5.    .add(new BoostQuery(new PrefixQuery(new Term("artist", "bob")), 0.75f), BooleanClause.Occur.SHOULD)
6.    // ... genre^2 / album^1.5 / label^1 / comment^0.5 (+ prefixes) ...
7.    .setMinimumNumberShouldMatch(1)
8.    .build();

10.  // Search and retrieve the 10 first hits
11.  TopDocs hits = searcher.search(bob, 10);

13.  UnifiedHighlighter highlighter = UnifiedHighlighter.builder(searcher, analyzer)
14.      .withMaxLength(10_000)
15.      .withBreakIterator(WholeBreakIterator::new)
16.      .build();
17.  Map<String, String[]> hl = highlighter.highlightFields(
18.          new String[]{"title", "artist", "genre", "album", "label", "comment"},
19.          bob, hits);

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

字段必须设置为 Field.Store.YES,否则 Lucene 无法提取源数据来进行 highlighting。

每个字段都对应一个并行数组 ------ 每个搜索结果对应一个 snippet。缺失或为空的 snippet 表示该字段没有为该文档贡献 markup。将这个 map 作为仅用于显示的 HTML 交给模板;chip 和 URL 仍然使用干净的 Bean。

q=Bob 会在 token 匹配的位置将其包裹起来:

css 复制代码
`

1.  Outro Lugar - <b>Bob</b> Sinclar Remix
2.  Crazy (<b>Bob</b> Sinclar vs. Dimitri Vegas & Like Mike remix)

`AI写代码

即使对于 PrefixQuery 也是如此,尽管高亮的是整个 term,而不仅仅是输入的前缀:

css 复制代码
`Outro Lugar - Bob <b>Sinclar</b> Remix`AI写代码

Lucene 中的工作原理

搜索回答的是"哪些文档包含这个 term?"Highlighting 回答的是"存储的字符串中的什么位置?"两者读取的是相同的倒排索引 ------ 只是向下深入了一层。

还记得我们在上一篇文章中生成的 posting list 吗?

markdown 复制代码
`

1.  artist:bob       →  1, 2, 3
2.  artist:claude    →  4, 5
3.  artist:francois  →  4, 5, 6
4.  artist:marley    →  2
5.  artist:sinclar   →  1, 3
6.  artist:valery    →  6

`AI写代码

实际上,它不仅为每个 term 生成了 posting list,还记录了这些 term 在每个文档中的位置。

Phrase query 的 positions

每个 posting 还会存储 positions:该 term 在该字段 token stream 中的位置序号(从 0 开始)。

例如,考虑 artist 名称"Claude François",它被分词为两个 term:claude 和 francois。这些 term 在文档中的位置如下:

位置 Term
0 claude
1 francois

对于 artist 名称 François "Valéry"(被分词为 francois 和 valery),位置如下:

位置 Term
0 francois
1 valery

对于 "Earth Wind and Fire ",位置如下:

位置 Term
0 earth
1 wind
2 and
3 fire

如果你搜索 francois valery,你会得到文档"François Valéry",因为其中一个查询 term 匹配了该文档。如果你搜索 valery francois,它会返回完全相同的两个文档,因为在简单的 conjunction query 中,term 的顺序并不重要。

如果顺序很重要呢?这种情况下,你需要使用 phrase query 或 span query 来强制要求 term 的顺序。这就是 term positions 发挥作用的地方。

Offsets 标记字符

但 highlighting 还需要再多一步:将 position 映射回存储的 原始字符串(Field.Store.YES)中的字符。

在分析阶段,每个 token 还会携带指向原始字符串的字符 offset ------ start 包含,end 不包含:

markdown 复制代码
`

1.  "Earth Wind and Fire"
2.   earth         → [0, 5)
3.   wind          → [6, 10)
4.   and           → [11, 14)
5.   fire          → [15, 19)

`AI写代码

UnifiedHighlighter 会在存储的值上重新运行相同的 analyzer,保留满足 query 的 token(TermQuery wind,或者匹配 wind 的 PrefixQuery win*),然后使用 <b></b> HTML 标签包裹这些 [start, end) 切片:

css 复制代码
`Earth <b>Wind</b> and Fire`AI写代码

现在,你只需要调整 CSS,使其呈现出你希望的样式即可。

q=wind ------ 在 Earth, Wind & Fire 以及 Ride Like the Wind 等标题中显示橙色的 <b>Wind</b>。

完整 Demo 位于 GitHub:lucene-search-tracks。

原文:Search your beans with Lucene --- Highlighting | David Pilato

相关推荐
Elasticsearch1 小时前
使用 Lucene 搜索你的 Bean —— Elasticsearch
elasticsearch
Elasticsearch6 小时前
最好的 LLM 只有 59% 的时间能写出正确的 Elasticsearch ES|QL。以下是另外 41% 出错的原因
elasticsearch
垚垚学技术_聚焦云原生6 小时前
Ansible Role 生产环境标准目录结构与规范化实践
java·elasticsearch·ansible
小林ixn7 小时前
从 MySQL 的 LIKE 到 ES 倒排索引:一次把全文检索和混合检索讲透
sql·elasticsearch·全文检索·agent·关键词
MayBaymax7 小时前
ES 基础总结
大数据·elasticsearch·搜索引擎
Elastic 中国社区官方博客7 小时前
两个依赖和一个配置块:通过 Prometheus 远程写入将 Spring Boot 指标发送到 Elasticsearch
大数据·数据库·spring boot·elasticsearch·搜索引擎·全文检索·prometheus
SelectDB技术团队8 小时前
ELK 太占磁盘、ES 总报写入拒绝:从归因到可执行的优化清单
大数据·clickhouse·elk·elasticsearch·全文检索·复杂查询·实时更新
frjc18 小时前
数据库选型:如何从众多数据库中选出最理想的那一个
redis·mysql·clickhouse·elasticsearch
xbgRS1 天前
Elasticsearch的分词器
大数据·elasticsearch·搜索引擎