Crates.io | bing |
lib.rs | bing |
version | 0.1.4 |
created_at | 2025-07-02 14:24:25.645402+00 |
updated_at | 2025-07-02 18:02:52.398381+00 |
description | bing |
homepage | https://github.com/i18n-site/rust/tree/dev/bing |
repository | https://github.com/i18n-site/rust.git |
max_upload_size | |
id | 1735127 |
size | 95,031 |
核心原理 Tantivy 的摘要生成并非简单地在关键词前后截取固定长度的文本。它采用了一种更智能的评分机制,以找出最能体现搜索结果与查询相关性的文本片段。
依赖位置信息:首先,你的索引字段必须包含词元的位置信息。这需要在定义 Schema 时,将字段的 IndexRecordOption 设置为 WithFreqsAndPositions。同时,为了能从索引中取回原文来生成摘要,字段必须设置为 STORED。
片段评分 (Fragment Scoring):SnippetGenerator 会在内部将整篇文档分割成多个小的“片段”(fragments)。然后,它会根据以下几点为每个片段打分:
关键词密度:片段中包含的搜索关键词越多,分数越高。
关键词集中度:如果多个搜索关键词在片段中紧密相邻地出现,这个片段的相关性就更高,得分也更高。
长度预算:SnippetGenerator 会尽量选择长度接近你设定的最大字符数(例如200)的片段。
最佳片段选择:综合评分后,SnippetGenerator 会选出得分最高的那个片段作为最终的摘要(Snippet)。
HTML高亮:选出最佳片段后,其内部的 Highlighter 会将与查询匹配的关键词用指定的 HTML 标签(默认为 )包裹起来,最终通过调用 .to_html() 方法生成高亮后的 HTML 字符串。
代码演示 下面的 Rust 代码完整演示了如何建立索引、添加长文档、进行搜索,并最终生成一个大约200字符的高亮摘要。
Rust
use tantivy::collector::TopDocs; use tantivy::query::QueryParser; use tantivy::schema::*; use tantivy::{doc, Index, ReloadPolicy}; use tantivy::snippet::SnippetGenerator; use tempfile::TempDir;
fn main() -> tantivy::Result<()> { // 1. 定义 Schema let mut schema_builder = Schema::builder(); // 确保字段配置正确:启用存储(storing)和位置信息(positions) let text_options = TextOptions::default() .set_storing_enabled(true) .set_indexing_options( TextFieldIndexing::default() .set_tokenizer("en_stem") .set_index_option(IndexRecordOption::WithFreqsAndPositions), ); let title = schema_builder.add_text_field("title", text_options.clone()); let body = schema_builder.add_text_field("body", text_options); let schema = schema_builder.build();
// 2. 创建索引
let index_path = TempDir::new()?;
let index = Index::create_in_dir(&index_path, schema.clone())?;
let mut index_writer = index.writer(50_000_000)?;
// 3. 添加一篇长文档
let long_text = "Tantivy is a full-text search engine library written in Rust. \
It is highly inspired by Apache Lucene. It is designed to be a modern, \
performant, and easy-to-use library for building search applications. \
One of the key features of a search engine is the ability to display a snippet \
of the document with the search terms highlighted. This is crucial for users \
to quickly understand the context of the search results. Tantivy provides a \
powerful SnippetGenerator for this purpose. This generator can create a concise \
and relevant fragment of the original text. The length of this fragment, or snippet, \
can be controlled. For instance, we can aim for a snippet of around 200 characters. \
The highlighter will then wrap the matched terms with HTML tags, like <b>, to make them stand out. \
This combination of snippet generation and highlighting is essential for a good user experience in any search interface.";
index_writer.add_document(doc!(
title => "Tantivy Highlighting Example",
body => long_text
))?;
index_writer.commit()?;
// 4. 准备搜索
let reader = index.reader_builder().reload_policy(ReloadPolicy::OnCommit).try_into()?;
let searcher = reader.searcher();
// 注意:QueryParser 需要知道哪些字段是可搜索的
let query_parser = QueryParser::for_index(&index, vec![title, body]);
// 5. 执行搜索
let query = query_parser.parse_query("search library")?;
let top_docs = searcher.search(&query, &TopDocs::with_limit(10))?;
// 6. 为 'body' 字段创建 SnippetGenerator 并生成高亮摘要
// `SnippetGenerator` 需要知道你想从哪个字段生成摘要
let mut snippet_generator = SnippetGenerator::from_query(&query, &searcher, body);
// **核心:设置摘要的最大字符数**
snippet_generator.set_max_num_chars(200);
for (score, doc_address) in top_docs {
let retrieved_doc = searcher.doc(doc_address)?;
println!("文档得分: {}", score);
let snippet = snippet_generator.snippet_from_doc(&retrieved_doc);
// 生成高亮后的 HTML
let highlighted_html = snippet.to_html();
println!("高亮摘要 (约200字符):");
println!("{}", highlighted_html);
println!("---");
}
Ok(())
} 2. 如果我有多个字段(例如标题和正文),应该怎么处理? 当你的搜索查询可能匹配多个字段(如 title 和 body)时,你需要决定如何展示高亮摘要。通常有两种策略:
为每个匹配的字段都生成摘要:分别对标题和正文生成摘要,然后都展示给用户。
选择最佳摘要:对每个可能匹配的字段都生成摘要,然后通过某种逻辑(例如,哪个摘要包含的关键词更多,或者哪个字段更重要)选择一个最佳的摘要展示。
SnippetGenerator 本身在创建时是针对单个字段的。SnippetGenerator::from_query(&query, &searcher, field) 的第三个参数 field 明确了它将从哪个字段中提取原文并生成摘要。
因此,处理多字段高亮的正确做法是:为每个需要高亮的字段分别创建一个 SnippetGenerator。
代码演示 (处理多字段) 以下代码展示了如何为 title 和 body 两个字段分别生成高亮摘要。
Rust
// ... (接续上一个例子的 main 函数,从步骤 6 开始修改)
// 6. 为多个字段创建 SnippetGenerator 并生成高亮摘要 let fields_to_highlight = vec![title, body]; // 定义你想要高亮的字段列表
for (score, doc_address) in top_docs { let retrieved_doc = searcher.doc(doc_address)?; println!("文档得分: {}", score); println!("文档: {}", schema.to_json(&retrieved_doc)); println!("---");
for field in &fields_to_highlight {
// 为当前循环的字段创建一个 SnippetGenerator
let mut snippet_generator = SnippetGenerator::from_query(&query, &searcher, *field);
snippet_generator.set_max_num_chars(200); // 同样可以设置长度限制
let snippet = snippet_generator.snippet_from_doc(&retrieved_doc);
// 检查生成的 snippet 是否为空(即该字段中没有匹配的词)
if !snippet.is_empty() {
// 获取字段名称,以便更好地展示
let field_name = schema.get_field_name(*field);
println!("字段 '{}' 的高亮摘要:", field_name);
println!("{}", snippet.to_html());
println!("---");
}
}
}
多字段处理逻辑解析
定义目标字段:我们创建了一个 Vec
遍历字段:在获取到每个搜索结果文档 (retrieved_doc) 后,我们遍历这个字段列表。
独立创建 SnippetGenerator:在循环内部,我们为每一个字段都创建了一个新的 SnippetGenerator 实例。这是因为每个生成器都需要知道它具体要处理哪个字段的原文。
生成并检查摘要:调用 snippet_from_doc 生成摘要。由于搜索词可能只存在于部分字段中,所以我们用 snippet.is_empty() 来判断当前字段是否真的匹配到了关键词并成功生成了摘要。
分别展示:如果摘要不为空,我们就将其高亮后的 HTML 打印出来,并附上字段名,让用户清楚地知道这个摘要来自标题还是正文。
这种方法提供了最大的灵活性,你可以根据你的业务需求,决定是将所有字段的摘要都展示出来,还是根据摘要的质量(例如,摘要的评分 snippet.score())只选择最好的一个。
use std::time::Duration;
use anyhow::Result;
use bing::Doc;
#[static_init::constructor(0)]
extern "C" fn _loginit() {
loginit::init();
}
#[test]
fn test_search_engine() -> Result<()> {
let path = "/tmp/bing";
let db = bing::doc::open(path)?;
let mut writer = db.writer()?;
let id = 99;
let doc1 = Doc {
id,
uid: 100,
org_id: 2,
repo_id: 10,
tag_li: vec!["abc".into(), "电动车品牌".into(), "xyz".into()],
ts: 1640995200, // 2022-01-01
title: "YES Good 搜索引擎".into(),
txt: String::from_utf8_lossy(include_bytes!("./blog.md")).into(),
};
let doc_id = writer.add(doc1)?;
dbg!(doc_id);
let mut seacher = db.searcher()?;
let li = seacher.search(bing::search::Query {
query: "品牌".into(),
uid: 0,
org_id: 0,
repo_id_li: vec![],
tag_li: vec![],
ts_begin: None,
ts_end: None,
limit: 10,
offset: 0,
snippet_max_num_chars: 300,
})?;
dbg!(li);
writer.rm(id)?;
std::thread::sleep(Duration::from_secs(1));
let li = seacher.search(bing::search::Query {
query: "品牌".into(),
..Default::default()
})?;
dbg!(li);
Ok(())
}
This project is an open-source component of i18n.site ⋅ Internationalization Solution.
i18 : MarkDown Command Line Translation Tool
The translation perfectly maintains the Markdown format.
It recognizes file changes and only translates the modified files.
The translated Markdown content is editable; if you modify the original text and translate it again, manually edited translations will not be overwritten (as long as the original text has not been changed).
i18n.site : MarkDown Multi-language Static Site Generator
Optimized for a better reading experience
本项目为 i18n.site ⋅ 国际化解决方案 的开源组件。
翻译能够完美保持 Markdown 的格式。能识别文件的修改,仅翻译有变动的文件。
Markdown 翻译内容可编辑;如果你修改原文并再次机器翻译,手动修改过的翻译不会被覆盖(如果这段原文没有被修改)。
i18n.site : MarkDown多语言静态站点生成器 为阅读体验而优化。