在Elasticsearch中存储HTML文档

14

场景

我有一些HTML文档,比如说邮件。我想把这些邮件存储在Elasticsearch上,并且搜索HTML邮件中的普通文本。

问题

Elasticsearch将索引所有的HTML标签和属性,但我不想要这些。我想要搜索的是纯文本中的span,而不是HTML元素。例如<span>span</span>可以被匹配到,但是<span>其他内容</span>不能匹配到。

问题

你会推荐在文档中存储一个已经去除HTML标签的字段和一个HTML字段吗?还是应该将HTML文档存储在S3上,而在Elasticsearch中只存储一个去除HTML标签的版本?这样做是否有意义?

我并不知道如果Elasticsearch索引HTML文档会发生什么,但我可以想象它也会索引

以及所有属性。这些都是我完全不需要搜索的东西。因此:任何解决这个问题的建议都将是极好的!

现在我正在做什么?

现在,在将文档存储在ES之前,我检查文档类型的索引是否存在。如果不存在,我会创建一个带有给定映射的集合。该映射如下:

{
    "analysis": {
        "analyzer": {
            "htmlStripAnalyzer": {
                "type": "custom",
                "tokenizer": "standard",
                "filter": "standard",
                "char_filter": [
                    "html_strip"
                ]
            }
        }
    },
    "mappings": {
        "${type}": {
            "dynamic_templates": [
                {
                    "_metadata": {
                        "path_match": "_metadata.*",
                        "mapping": {
                            "type": "keyword"
                        }
                    }
                }
            ],
            "properties": {
                "_tags": {
                    "type": "nested",
                    "dynamic": true
                }
            }
        }
    }
}

警告:忽略现有的映射。它与我的意图无关。它们只是存在。

我将使用文档类型替换${type},比如emails如果要告诉ES不要索引HTML内容,该怎么做呢?


你可能想要定义一个静态映射,仅索引与HTML文档相关的字段,并可能定义一种检索文档的方式(路径或ID)。有关 Elasticsearch 映射的更多信息,请参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html - Adonis
我明白了,基本上dynamic应该设置为false(或strict),并且您必须明确提供要在文档中索引的内容。这意味着您将需要解析HTML,然后构建一个JSON查询(自己或通过ES API),仅向ES提供相关部分。如果您感兴趣,我可以写一个小例子。 - Adonis
我很想看一个例子 - 因为我还不明白你的意思。只是提一下,我是一个ES的初学者。 - AmazingTurtle
如果我没有漏掉什么,你只需要这样做:"properties":{"_tags":{"type":"nested","dynamic":true},"html":{"type":"text","analyzer":"htmlStripAnalyzer"}},就可以了。 - Andrei Stefan
@AndreiStefan 忽略现有的映射。它与我的意图无关。它们只是存在。但我会检查你在另一个评论中提供的解决方案。 - AmazingTurtle
显示剩余2条评论
2个回答

21

一个完整的测试用例:

DELETE /test
PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "htmlStripAnalyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"],
          "char_filter": [
            "html_strip"
          ]
        }
      }
    }
  },
  "mappings": {
    "test": {
      "properties": {
        "html": {
          "type": "text",
          "analyzer": "htmlStripAnalyzer"
        }
      }
    }
  }
}

POST /test/test/1
{
  "html": "<td><tr>span<td></tr>"
}
POST /test/test/2
{
  "html": "<span>whatever</span>"
}
POST /test/test/3
{
  "html": "<html> <body> <h1 style=\"font-family: Arial\">Test</h1> <span>More test</span> </body> </html>"
}

POST /test/_search
{
  "query": {
    "match": {
      "html": "span"
    }
  }
}

POST /test/_search
{
  "query": {
    "match": {
      "html": "body"
    }
  }
}

POST /test/_search
{
  "query": {
    "match": {
      "html": "more"
    }
  }
}

更新 Elasticsearch 版本 >=7(删除类型

DELETE /test
PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "htmlStripAnalyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"],
          "char_filter": [
            "html_strip"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "html": {
        "type": "text",
        "analyzer": "htmlStripAnalyzer"
      }
    }
  }
}

POST /test/_doc/1
{
  "html": "<td><tr>span<td></tr>"
}
POST /test/_doc/2
{
  "html": "<span>whatever</span>"
}
POST /test/_doc/3
{
  "html": "<html> <body> <h1 style=\"font-family: Arial\">Test</h1> <span>More test</span> </body> </html>"
}

POST /test/_search
{
  "query": {
    "match": {
      "html": "span"
    }
  }
}

POST /test/_search
{
  "query": {
    "match": {
      "html": "body"
    }
  }
}

POST /test/_search
{
  "query": {
    "match": {
      "html": "more"
    }
  }
}

我怀疑这也会剥离titlealt属性内的<img>标签相关内容。因此,对于网页搜索来说并不是很好。例如:<img src="/beach.jpg" title="My favorite beach picture" />将被完全删除,并且无法匹配查询beach - Tyler
1
添加“lowercase”过滤器的目的是什么?此外,需要稍微更新以适应Elasticsearch 7.6。 - mbroshi
1
@mbroshi,“标准”分析器(在其最简单的形式中)由“标准”分词器和“小写”过滤器组成(https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html#_definition_4),因此这是上面自定义的“htmlStripAnalizer”的基础。 - Lee Netherton
注意,在较新版本的Elasticsearch中,您需要更改映射定义如下: "mappings": { "properties": { "html": { "type": "text", "analyzer": "htmlStripAnalyzer" } } } 您必须删除"html":部分。 - jonasstr

1
默认情况下,Elasticsearch 在索引过程中会动态地添加新字段(参见 this):

当 Elasticsearch 在文档中遇到一个以前未知的字段时,它使用动态映射来确定该字段的数据类型并自动将新字段添加到类型映射中。

要禁用此行为(有关更多详细信息,请参见 doc),最简单的方法是将 dynamic 设置为 false(防止自动创建)或 strict(抛出异常并不创建新文档)。在这种情况下,您需要显式编写希望保留在 _tags 部分内的标签映射,并对 HTML 文档进行预处理,以便将感兴趣的标记提供给 Elasticsearch。
因此,假设您有以下 HTML 文档:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>A simple example</title>
</head>
<body>
  <div>
    <p><span class="ref">A sentence I want to reference from this HTML document</span></p>
    <p><span class="">Something less important</span></p>
</body>
</html>

您需要的是在Elasticsearch中设置静态映射,我建议执行以下操作(假设ref是一个字符串):
PUT html
{

"mappings": {
  "test":{
    "dynamic": "strict",
    "properties": {
      "ref":{
        "type": "string"
      }
    }
  }
}

现在,如果您以这种方式添加文档,它将成功:
PUT html/test/1
{
  "ref": "A sentence I want to reference from this HTML document"
}

但这不会成功:

但这不会成功:

PUT html/test/2
{
  "ref": "A sentence I want to reference from this HTML document",
  "some_field": "Some field"
}

现在唯一需要做的就是解析HTML以检索“ref”字段,并创建上述查询(使用您喜欢的任何语言,如Java、Python...)。 编辑:实际上,要存储HTML而不进行索引,在您的映射中,您只需要将index设置为no(请参见这里)。
"_tags": {
          "type": "nested",
          "dynamic": true,
          "index": "no"
         }

好的,谢谢你的详细解释,这对我很有帮助。 我也可以用Java去除HTML,但我想将原始HTML文档存储在Elasticsearch中(以便以后查看)。但我不想索引所有HTML内容。所以基本上,我必须告诉ES不要索引所有HTML内容。你明白了吗? - AmazingTurtle
@BlackHat 好的,只需将 index 设置为 no,然后将您想要索引的其他字段放入不同的属性中即可。请查看我的编辑。 - Adonis
我最好澄清一下。我想将包含所有HTML标签的整个HTML文档存储在ES中。当我搜索时,我想检索HTML文档。但是,例如当我搜索“span”时不想找到HTML文档。 - AmazingTurtle
你找到任何解决方案了吗? - user7367392
@AmazingTurtle,我需要相同的东西——保存原始HTML以显示搜索结果的位置。你最终是如何解决的? - Lior
1
@Lior 说实话,我认为你应该先使用一个爬虫解析HTML,然后以任何你想要的方式将结果发送到logstash。 - Adonis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接