Elasticsearch 使用誤區(qū)—富文本內(nèi)容寫(xiě)入前不清洗
0、引言
在很多應(yīng)用場(chǎng)景中,我們會(huì)將富文本內(nèi)容(如 HTML 格式的網(wǎng)頁(yè)內(nèi)容)存儲(chǔ)到 Elasticsearch 中,以實(shí)現(xiàn)全文檢索。
然而,在實(shí)際使用過(guò)程中,我們可能會(huì)遇到一些問(wèn)題,比如在檢索時(shí),HTML 標(biāo)簽被截?cái)?,或者檢索結(jié)果中會(huì)顯示部分 HTML 標(biāo)簽內(nèi)容。這種現(xiàn)象不僅影響了檢索體驗(yàn),還可能對(duì)頁(yè)面展示造成困擾。
本文將詳細(xì)介紹如何解決這種富文本處理中的常見(jiàn)問(wèn)題,幫助大家在使用 Elasticsearch 時(shí)更加高效地處理和展示富文本內(nèi)容。
1、具體問(wèn)題描述
Elasticsearch 所存儲(chǔ)的數(shù)據(jù)包含但不限于:
- 業(yè)務(wù)數(shù)據(jù)
- 日志數(shù)據(jù)
- 互聯(lián)網(wǎng)采集數(shù)據(jù)
- 其他數(shù)據(jù) 如果遇到互聯(lián)網(wǎng)采集數(shù)據(jù)場(chǎng)景,Elasticsearch 的 content 字段極大可能需要存儲(chǔ)的是富文本內(nèi)容(HTML)。
遇到這種情況,咱們得非常的慎重。
不信,你看!如下 QQ 群群友出現(xiàn)的問(wèn)題可見(jiàn)一斑。
圖片
圖片
什么問(wèn)題呢?
在輸入關(guān)鍵詞進(jìn)行檢索時(shí),可能會(huì)出現(xiàn) HTML 標(biāo)簽被截?cái)嗟那闆r,導(dǎo)致返回的結(jié)果中顯示部分 HTML 標(biāo)簽內(nèi)容。
這種情況對(duì)用戶(hù)展示極為不友好。
那么,我們應(yīng)該如何處理這種問(wèn)題呢?
2、原因分析
Elasticsearch 本質(zhì)上是一個(gè)文本檢索引擎,它會(huì)對(duì)輸入的文本進(jìn)行分析、分詞、索引,并對(duì)用戶(hù)的搜索關(guān)鍵詞進(jìn)行相應(yīng)匹配。
富文本(HTML)的特殊性在于,它不僅包含可見(jiàn)的文字,還包含大量的標(biāo)記語(yǔ)言(如 <div>、<p>、<span> 等標(biāo)簽),這些標(biāo)簽在檢索過(guò)程中可能會(huì)被 Elasticsearch 解析和處理,進(jìn)而影響檢索結(jié)果。
3、解決方案
針對(duì)這種情況,我們可以通過(guò)以下幾個(gè)步驟來(lái)解決問(wèn)題,確保檢索結(jié)果更加干凈、準(zhǔn)確。
3.1. 清洗 HTML 內(nèi)容
在將 HTML 內(nèi)容寫(xiě)入 Elasticsearch 之前,最好先對(duì)其進(jìn)行清洗。
清洗操作的目的是去除不必要的 HTML 標(biāo)簽,提取其中的有用文本信息。這可以通過(guò)編寫(xiě)自定義算法或使用 HTML 解析庫(kù)來(lái)實(shí)現(xiàn)。
常用的 HTML 解析庫(kù)包括 Jsoup、BeautifulSoup 等,其實(shí)采集團(tuán)隊(duì)或許已經(jīng)做過(guò)處理。
如果公司采集團(tuán)隊(duì)和存儲(chǔ)不是一個(gè)團(tuán)隊(duì),建議深入交流一下字段細(xì)節(jié)內(nèi)容。
3.2. 利用 Elasticsearch 的 HTML Strip 字符過(guò)濾器
Elasticsearch 提供了 html_strip 字符過(guò)濾器,可以用來(lái)自動(dòng)去除 HTML 標(biāo)簽。
詳細(xì)參見(jiàn): https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-htmlstrip-charfilter.html
通過(guò)該過(guò)濾器,我們可以確保在索引過(guò)程中,HTML 標(biāo)簽不會(huì)被保留下來(lái),只有純文本內(nèi)容會(huì)被處理和檢索。
示例如下:
PUT my-index
{
"settings": {
"analysis": {
"char_filter": {
"html_strip": {
"type": "html_strip"
}
},
"analyzer": {
"html_analyzer": {
"type": "custom",
"char_filter": [
"html_strip"
],
"tokenizer": "standard"
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "html_analyzer"
}
}
}
解釋?zhuān)?/h6>- char_filter 部分定義了 HTML 過(guò)濾器,用于去除 HTML 標(biāo)簽。
- html_analyzer 是自定義的分析器,首先使用 html_strip 過(guò)濾器去除 HTML 標(biāo)簽,隨后通過(guò)標(biāo)準(zhǔn)分詞器 standard 進(jìn)行分詞和處理。
使用這個(gè)分析器進(jìn)行索引和搜索時(shí),HTML 標(biāo)簽會(huì)自動(dòng)被過(guò)濾掉,確保只有純文本內(nèi)容參與搜索,進(jìn)而避免了 HTML 標(biāo)簽截?cái)嗟膯?wèn)題。
3.3 存儲(chǔ)多版本的內(nèi)容
為了兼顧搜索和展示,推薦在存儲(chǔ)時(shí)將富文本內(nèi)容存為兩個(gè)字段:
- 原始 HTML 內(nèi)容:用于在前端完整展示。
- 純文本版本:用于索引和檢索。
并且負(fù)責(zé)任告訴大家,真實(shí)互聯(lián)網(wǎng)采集數(shù)據(jù)存儲(chǔ)與展示場(chǎng)景,就得這么干!
例如,我們可以在 Elasticsearch 中定義兩種字段:
"mappings": {
"properties": {
"html_content": {
"type": "text",
"index": false // 不參與索引,僅用于展示
},
"cleaned_content": {
"type": "text",
"analyzer": "html_analyzer" // 純文本版本,參與檢索
}
}
}
這樣,html_content 字段保留原始 HTML,用于頁(yè)面回顯;
而 cleaned_content 字段則經(jīng)過(guò)清洗處理,專(zhuān)用于檢索操作。
3.4. 通過(guò)前端處理回顯內(nèi)容
為了避免搜索結(jié)果中顯示部分 HTML 標(biāo)簽的情況,我們可以在前端對(duì)回顯的內(nèi)容進(jìn)行進(jìn)一步處理:
- 使用前端的 HTML 渲染器,比如 Vue.js 或 React.js 的 v-html 和 dangerouslySetInnerHTML,確保內(nèi)容正確渲染。
- 結(jié)合高亮功能,確保用戶(hù)能夠清晰地看到與查詢(xún)關(guān)鍵詞匹配的文本部分,而非 HTML 標(biāo)簽。
3.5. 關(guān)鍵詞高亮功能
為了增強(qiáng)用戶(hù)體驗(yàn),Elasticsearch 提供了高亮顯示功能,可以在返回結(jié)果時(shí)高亮顯示與關(guān)鍵詞匹配的部分。
通過(guò)以下 DSL 語(yǔ)句,我們可以實(shí)現(xiàn)對(duì)匹配部分的高亮顯示:
POST my-index/_search
{
"query": {
"match": {
"cleaned_content": "搜索關(guān)鍵詞"
}
},
"highlight": {
"fields": {
"cleaned_content": {}
}
}
}
這樣,在展示時(shí),只會(huì)高亮純文本部分,HTML 標(biāo)簽將不會(huì)出現(xiàn)在檢索結(jié)果中。
4、完整示例,徹底解決富文本存儲(chǔ)
Elasticsearch 處理富文本內(nèi)容(HTML)的方案如下:
4.1 創(chuàng)建索引并配置 HTML 過(guò)濾器
如前方案探討所述,我們首先創(chuàng)建一個(gè)新的索引,使用 Elasticsearch 的 html_strip 過(guò)濾器來(lái)去除 HTML 標(biāo)簽,并配置兩個(gè)字段:一個(gè)存儲(chǔ)清洗后的純文本內(nèi)容用于檢索,另一個(gè)存儲(chǔ)原始 HTML 內(nèi)容用于展示。
PUT html_content_index
{
"settings": {
"analysis": {
"char_filter": {
"html_strip": {
"type": "html_strip"
}
},
"analyzer": {
"html_analyzer": {
"type": "custom",
"char_filter": [
"html_strip"
],
"tokenizer": "standard"
}
}
}
},
"mappings": {
"properties": {
"html_content": {
"type": "text",
"index": false // 原始 HTML 內(nèi)容不參與索引,僅用于展示
},
"cleaned_content": {
"type": "text",
"analyzer": "html_analyzer" // 清洗后用于檢索的純文本
}
}
}
}
4.2 向索引中寫(xiě)入數(shù)據(jù)
我們將兩種不同的內(nèi)容分別寫(xiě)入到 html_content 和 cleaned_content 字段。
html_content 保存的是原始 HTML,而 cleaned_content 保存的是清洗后的純文本。
POST /html_content_index/_doc
{
"html_content": "<p>這是一段關(guān)于<strong>Elasticsearch</strong>的文章。</p>",
"cleaned_content": "這是一段關(guān)于 Elasticsearch 的文章。"
}
在這個(gè)例子中,html_content 保留了 HTML 標(biāo)簽,而 cleaned_content 則去除了 HTML 標(biāo)簽,只保留了純文本內(nèi)容。
4.3 進(jìn)行搜索并返回高亮結(jié)果
接下來(lái),我們對(duì) cleaned_content 字段進(jìn)行全文檢索,同時(shí)啟用高亮功能,確保關(guān)鍵詞匹配部分能夠在前端得到突出顯示。
GET /html_content_index/_search
{
"query": {
"match": {
"cleaned_content": "Elasticsearch"
}
},
"highlight": {
"fields": {
"cleaned_content": {}
}
}
}
返回結(jié)果示例如下:
圖片
{
"took": 34,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.2876821,
"hits": [
{
"_index": "html_content_index",
"_id": "INbUKZIBSx0oX-FAHL8S",
"_score": 0.2876821,
"_source": {
"html_content": "<p>這是一段關(guān)于<strong>Elasticsearch</strong>的文章。</p>",
"cleaned_content": "這是一段關(guān)于 Elasticsearch 的文章。"
},
"highlight": {
"cleaned_content": [
"這是一段關(guān)于 <em>Elasticsearch</em> 的文章。"
]
}
}
]
}
}
4.4 前端展示:結(jié)合原始 HTML 和高亮結(jié)果
在前端,我們可以使用 html_content 字段來(lái)展示完整的 HTML 內(nèi)容,而使用 highlight 字段來(lái)突出顯示用戶(hù)搜索的關(guān)鍵詞。這樣,用戶(hù)既可以看到完整的富文本格式的內(nèi)容,又能清楚地識(shí)別出匹配的關(guān)鍵詞。
偽代碼:
<!-- 顯示原始 HTML 內(nèi)容 -->
<div v-html="html_content"></div>
<!-- 高亮顯示匹配的關(guān)鍵詞 -->
<div v-html="highlighted_cleaned_content"></div>
4.5 預(yù)處理 HTML 清洗
除了在 Elasticsearch 中使用 html_strip 過(guò)濾器,我們還可以在存入 Elasticsearch 之前預(yù)先清洗 HTML 內(nèi)容。
以下是使用 Java 的 Jsoup 庫(kù)來(lái)清洗 HTML 的示例:
import org.jsoup.Jsoup;
public class HTMLCleaner {
public static String cleanHTML(String html) {
return Jsoup.parse(html).text(); // 將 HTML 內(nèi)容轉(zhuǎn)換為純文本
}
}
在保存數(shù)據(jù)到 Elasticsearch 之前,先調(diào)用 cleanHTML 方法去除不必要的 HTML 標(biāo)簽,然后將純文本存入 cleaned_content 字段。
5、小結(jié)
在處理富文本內(nèi)容時(shí),合理的清洗和過(guò)濾是避免 HTML 標(biāo)簽影響檢索結(jié)果的關(guān)鍵。
處理 HTML 內(nèi)容的關(guān)鍵在于:清洗 + 分離存儲(chǔ) + 高亮展示。
通過(guò)預(yù)處理富文本內(nèi)容,使用 html_strip 過(guò)濾器,以及前端渲染和高亮展示,可以有效地避免 HTML 標(biāo)簽截?cái)鄦?wèn)題,從而確保用戶(hù)獲得干凈、準(zhǔn)確的搜索體驗(yàn)。
富文本不做處理直接寫(xiě)入是 Elasticsearch 使用中的常見(jiàn)誤區(qū)之一,希望本文提供的解決方案能夠幫助大家在日常工作中更好地應(yīng)對(duì)類(lèi)似問(wèn)題。