【问题标题】:Regex: Extracting readable (non-code) text and URLs from HTML documents正则表达式:从 HTML 文档中提取可读(非代码)文本和 URL
【发布时间】:2011-04-26 11:23:01
【问题描述】:

我正在创建一个应用程序,它将以 URL 作为输入,从网络上检索页面的 html 内容并提取 标签中不包含的所有内容。换句话说,该页面的访问者所看到的页面的文本内容。这包括“屏蔽”封装在 <script></script><style></style><!-- --> 中的所有内容,因为这些部分包含未封装在标签中的文本(但最好单独保留)。

我已经构建了这个正则表达式:

(?:<(?P<tag>script|style)[\s\S]*?</(?P=tag)>)|(?:<!--[\s\S]*?-->)|(?:<[\s\S]*?>)

它正确地选择了我想忽略的所有内容,并且只留下页面的文本内容。但是,这意味着我要提取的内容不会出现在匹配集合中(我在 Visual Studio 2010 中使用 VB.Net)。

有没有办法像这样“反转”整个文档的匹配,这样我就可以匹配上面正则表达式中匹配遗漏的所有文本字符串?

到目前为止,我所做的是在最后添加另一个替代方案,即选择“任何不包含 的序列”,这意味着剩余的文本。我在捕获组中命名了最后一位,当我遍历匹配项时,我检查“文本”组中是否存在文本。这行得通,但我想知道是否可以通过正则表达式完成所有操作,并且 just 最终会在纯文本上匹配。

这应该是通用的,不知道 html 中的任何特定标签。它应该提取 all 文本。此外,我需要保留原始 html,以便页面保留其所有链接和脚本 - 我只需要能够提取文本,以便我可以在其中执行搜索和替换,而不必担心“重命名”任何标签、属性或脚本变量等(所以我不能只对我得到的所有匹配项进行“替换”,因为即使我得到了我需要的东西,将其重新插入到正确的位置也是一件麻烦事功能齐全的文档)。

我想知道这是否可以使用正则表达式(我知道 HTML Agility Pack 和 XPath,但不喜欢)。

有什么建议吗?

更新: 这是我最终得到的(基于正则表达式的)解决方案:http://www.martinwardener.com/regex/,在一个演示 Web 应用程序中实现,该应用程序将显示活动的正则表达式字符串以及一个允许您在任何在线 html 页面上运行解析的测试引擎,给出您解析时间并提取结果(分别针对链接、url 和文本部分 - 以及在完整 HTML 文档中突出显示所有正则表达式匹配的视图)。

【问题讨论】:

  • 你可以用`字符包围短代码块。
  • 正则表达式在选择正确的部分方面做得很好——可能比使用任何 DOM 等方法(或..?)更优雅。所以除了“反转”部分,我对使用正则表达式非常满意——它非常紧凑,代码明智。我有两种候选方法来完成这项工作:向正则表达式 (|(?P&lt;text&gt;[^&lt;&gt;]*)) 添加一个额外的部分,它实际上将选择剩余文本作为隔离匹配 - 由于该捕获组有一个名称,因此可以在随后的迭代。这行得通,只是我注意到它也只找到了一些令我困惑的其他“匹配项”。
  • 另一种可能性(我还没有仔细考虑过,但它应该可以工作,虽然这是我想避免的繁琐)是在上面的主要帖子中使用正则表达式,省略了文本零件 - 并“手动”跟踪比赛。因为他们都让我知道匹配在页面中的哪个位置开始,以及匹配字符串的长度,所以我会使用一个匹配的索引+长度和下一个匹配的索引之间的差异来确定然后代表什么文档的纯文本部分,不包含在任何“掩码”中。
  • 这样我就可以在与匹配迭代相同的操作中进行单词搜索/替换,并且生成的文档将准备就绪,包括所有正确的 html 和脚本代码。
  • 更新:我不再困惑了。事实证明,没有将 RegexBuddy 语法正确转换为 .Net 正则表达式语法是我的错(它们在命名捕获组的方式上有所不同,我在更改它们时忽略了一个)。因此,该例程完美运行。我可以在 10 行代码中完成。

标签: html regex text extract invert


【解决方案1】:

我所做的是在最后添加另一个替代方案,即选择“任何不包含&lt;&gt; 的序列”,这意味着剩余的文本。我在捕获组中命名了最后一位,当我遍历匹配项时,我检查“文本”组中是否存在文本。

这就是人们通常会做的事情。或者更简单,将标记模式的每个匹配项替换为空字符串,剩下的就是您要查找的内容。

它有点工作,但似乎这里和那里有一个不应该被拾取的字符串。

嗯,是的,那是因为您的表达式(以及一般的正则表达式)不足以解析有效的 HTML,更不用说真实网络上的恐怖了。第一个提示,如果你真的想追逐这种徒劳的方法:属性值(以及一般的文本内容)可能包含未转义的 &gt; 字符。

我想再次提出 HTML Agility Pack 的好处。

ETA:既然您似乎想要它,这里有一些标记示例,看起来它会影响您的表达。

<a href=link></a> - unquoted
<a href= link></a> - unquoted, space at front matched but then required at back
<a href="~/link"></a> - very common URL char missing in group
<a href="link$!*'link"></a> - more URL chars missing in group
<a href=lïnk></a> - IRI
<a href
    ="link"> - newline (or tab)
<div style="background-image: url(link);"> - unquoted
<div style="background-image: url( 'link' );"> - spaced
<div style="background-image: u&#114;l('link');"> - html escape
<div style="background-image: ur\l('link');"> - css escape
<div style="background-image: url('link\')link');"> - css escape
<div style="background-image: url(\
'link')"> - CSS folding
<div style="background-image: url
('link')"> - newline (or tab)

这只是完全有效的标记,不会匹配正确的链接,而不是任何可能的无效标记,不应该但确实匹配链接的标记,或许多问题中的任何一个使用其他从文本中分离标记的技术。这只是冰山一角。

【讨论】:

  • 关于“工作类型”的事情:这让我感到困惑,因为当我在 RegexBuddy 中测试它时,正则表达式 正确 匹配它应该匹配的所有内容。只有在 .Net 中进行测试时才会出现几个脚本/样式字符串。
  • 不知道什么会导致这两个正则表达式引擎之间的差异——可能是默认的区分大小写选项?——但表达式原样会因为常见的有效 HTML 构造而失败,而这些构造可能不是在你的测试集中。要匹配标签,您至少需要深入了解属性值分隔符。在这一点上,你最终会得到一个笨重的正则表达式,它在任何方面都比 HTML 解析器更可取。
  • 啊,是的......像这样的高级功能在正则表达式引擎中通常是不同的。不过,说真的,我认为你并没有意识到自己在做什么。您一直在使用“干净”和“优雅”之类的词,但这实际上并非如此。使用正则表达式检测a href 属性绝对 简单,您首先需要通过带引号或不带引号的属性来拆分标签。一块格式不正确的标记或&gt;,您不会想到它,结果将分崩离析。这仅适用于极其有限的输入(即您自己创建的输入)。
  • 您认为上述冗长而凌乱的链接属性正则表达式“干净”吗?那我不知道该对你说什么了。这是一个复杂且远未完成的尝试,同时检测两种完全不同且不兼容的语法(CSS 和 HTML 属性),即使通过完全有效的标记也可以以大约一百种方式破坏。 HTML 转义、CSS 转义、属性中的&gt;、IRI、属性属性值中的错误匹配、引用属性周围的空格、url() 未正确检测到...这种事情真的很容易正确地完成 i> 使用 HTML 解析器,在普通的正则表达式中是不可能的。
  • DOM 构造解析器使用各种字符串方法(是的,可能包括正则表达式)来解析基本语法的低级标记。 (是的,我已经写了一个,是的,我也是一名汇编程序编码员,谢谢。)但是正则表达式确实没有解析更高级别结构的能力。我知道 CSS url 标记使用括号,但是,上面粘贴的表达式不包含任何文字括号,并且不会匹配这样的 URL。它似乎确实允许 url 作为不存在的属性。它也无法处理值内的 CSS 转义或 HTML 转义。
【解决方案2】:

Regex 对于检索 HTML 文档的文本内容并不可靠。正则表达式无法处理嵌套标签。假设文档不包含任何嵌套标签,正则表达式仍然要求每个标签都正确关闭。

如果您使用 PHP,为简单起见,我强烈建议您使用 DOM(文档对象模型)来解析/提取 HTML 文档。 DOM 库通常存在于每种编程语言中。

【讨论】:

  • 正如我在帖子中所说,我在 Visual Studio 2010 中使用 VB.Net。此外,这不是一个关键应用程序 - 它是一个娱乐实用程序,所以如果某些页面的 html 格式不正确,那就是油漆工作中的一个小划痕。另外,我不明白为什么它不能处理嵌套标签?据我所见,任何标签都被屏蔽了——在删除了诸如 SCRIPT、STYLE 和注释之类的“问题标签”之后,它根本不关心文档结构。哪个好。这里的重点是在不弄乱结构的情况下访问原始文本。这将如何在 DOM 中完成?
  • 我敢打赌,即使它不是一个关键的应用程序,正则表达式也应该无法解析和提取遍布 Internet 的不良 HTML 文档。正如我所说,DOM 库应该存在于每种编程语言中。此外,正则表达式无法解析嵌套标签。
  • 您能保证 HTML 文档中的每个标签都正确关闭,例如标签 P、H、LI 等吗?此外,SCRIPT 标签可能包含其中的标签。
  • 我看不出盲目掩盖 中的任何内容的正则表达式会如何失败(除了没有开始/结束括号的地方,在这种情况下,文档应该归咎于乱码结果)。正如您从正则表达式中看到的那样,它不仅通过搜索括号标记,而且通过搜索整个代码序列来屏蔽整个代码块(例如,从 &lt;script 开始并以 &lt;/script&gt; 结束。如果有孤立脚本标记某处,它会导致它自己被隔离(因为它会被通用 &lt;tag&gt; 匹配之后拾取,或者它掩盖了太多。
  • IMO,当浏览器可以正确显示带有未闭合标签的 HTML 文档时,我们的程序也应该能够正确提取它们。正如 HTML 4 规范所说,许多标签不需要有结束标签。这是官方规范。
【解决方案3】:

如果您要提取与正则表达式不匹配的字符串部分,只需将 匹配的部分替换为空字符串即可达到相同效果。

请注意,这可能有效的唯一原因是您有兴趣删除的标签&lt;script&gt;&lt;style&gt; 标签不能嵌套。

但是,一个&lt;script&gt; 标记包含以编程方式附加另一个&lt;script&gt; 标记的代码并不少见,在这种情况下,您的正则表达式将失败。如果任何标签没有正确关闭,它也会失败。

【讨论】:

  • 我不能这样做,因为 html 页面应该可以工作。它应该是原件的精确复制品,包括功能,只是文本内容发生了变化。如果我只是删除标签和代码,我会留下文本,是的,但这意味着我必须将每个删除的部分存储在其他索引中,以便在我完成处理后能够将它们全部粘合在一起文本。
  • 如果您真的对与非脚本、非样式标签进行交互感兴趣,请使用 xhtml 解析器。正则表达式不适合。
  • 我对与任何标签进行交互不感兴趣。我只想翻转页面上的某些单词。该应用程序获取一个网页(它从一个 URL - 从某个地方获取 - 并处理“源”代码,即当您在浏览器中单击“查看源”时获得的相同文档。它通过页面,检测相对链接,用绝对链接替换它们,然后将页面以紧凑的二进制形式存储在数据库中 - 以便以后可以直接检索页面并从与其来源不同的服务器显示。
  • &lt;script&gt; 标签如何更改已经在浏览器中显示的文档? javascript 是否能够在呈现后重写自己的文档?
  • 虽然标签本身不能嵌套在 DOM 中,但一个脚本标签可能包含像 $(body).append('&lt;script src="/myscript"&gt;&lt;/script&gt;'); 这样的语句,这会破坏您的正则表达式解决方案。
【解决方案4】:

好的,这就是我的做法:

使用我原来的正则表达式(添加了纯文本的搜索模式,恰好是标记搜索完成后剩下的任何文本):

(?:(?:&lt;(?P&lt;tag&gt;script|style)[\s\S]*?&lt;/(?P=tag)&gt;)|(?:&lt;!--[\s\S]*?--&gt;)|(?:&lt;[\s\S]*?&gt;))|(?P&lt;text&gt;[^&lt;&gt;]*)

然后在 VB.Net 中:

Dim regexText As New Regex("(?:(?:<(?<tag>script|style)[\s\S]*?</\k<tag>>)|(?:<!--[\s\S]*?-->)|(?:<[\s\S]*?>))|(?<text>[^<>]*)", RegexOptions.IgnoreCase)
Dim source As String = File.ReadAllText("html.txt")
Dim evaluator As New MatchEvaluator(AddressOf MatchEvalFunction)
Dim newHtml As String = regexText.Replace(source, evaluator)

文本的实际替换发生在这里:

Private Function MatchEvalFunction(ByVal match As Match) As String
    Dim plainText As String = match.Groups("text").Value
    If plainText IsNot Nothing AndAlso plainText <> "" Then
        MatchEvalFunction = match.Value.Replace(plainText, plainText.Replace("Original word", "Replacement word"))
    Else
        MatchEvalFunction = match.Value
    End If
End Function

瞧。 newHtml 现在包含原始的精确副本,除了页面中每次出现的“原始单词”(在浏览器中显示)都使用“替换单词”进行切换,并且所有 html 和脚本代码都保持不变。当然,人们可以/将采用更复杂的替换例程,但这显示了基本原理。这是 12 行代码,包括函数声明和 html 代码加载等。我很想看到一个并行解决方案,在 DOM 等中完成以进行比较(是的,我知道这种方法可能会被 某些 出现一些嵌套标签怪癖 - 在 SCRIPT 重写中 - 但由此造成的损害仍然非常有限(如果有的话)(请参阅上面的一些 cmets),而且通常这会很好地完成工作)。

【讨论】:

  • 对于那些感兴趣的人,这里是在 RegexBuddy 中工作的正则表达式本身的屏幕截图(显示此页面的 html 源代码:newscientist.com/article/…)。我还粘贴了一些评论者对正则表达式的几个“挑战”,因此很容易看出正则表达式是如何轻松地只提取它应该做的。此处使用的正则表达式没有尾随部分,它还选择文本部分并通过捕获组将它们标记为“文本” - 为清楚起见。每个彩色块显示来自正则表达式的匹配项。 martinwardener.com/regex.jpg
  • 这是一个在线实现,当应用于给定网页时显示正则表达式匹配:martinwardener.com/regex
【解决方案5】:

您不能使用正则表达式解析 HTML。

用正则表达式解析 HTML 会导致悲伤。

我知道你这样做只是为了好玩,但是那里有太多的包,而不是真正以正确的方式进行解析,并且可靠地执行,并且已经过测试。

不要重新发明轮子,这样做肯定会让你在路上感到沮丧。

【讨论】:

  • 但是,如果您阅读此处的 cmets,您会发现这不是解析 html 本身 - “只是”将 所有 屏蔽掉,以便可以单独对纯文本运行通用文本处理,保持 html 不变(因为这里的重点是保持整个 结构 完整,并简单地编辑文本内容)。 html 页面应该存储在本地(带有文本更改),然后按需提供 - 这就是为什么我还制作了一个正则表达式来提取(各种)链接,以便可以以绝对形式重建所有相对链接。查看其他 cmets 中的屏幕截图。
  • 我所寻求的解决方案已发布在我自己的答案中,并且只需 12 行程序代码即可非常可靠地执行此任务。正如我之前提到的,我不是一个有 DOM 经验的人,所以我真的很想看到以 DOM/HTML Agility/XPath 等方式完成相同任务的代码。
  • “掩盖所有”需要找到作为解析一部分的 HTML 标记。
  • 很公平。但是这个正则表达式非常有效地做到了这一点:(?:(?:&lt;(?P&lt;tag&gt;script|style)[\s\S]*?&lt;/(?P=tag)&gt;)|(?:&lt;!--[\s\S]*?--&gt;)|(?:&lt;[\s\S]*?&gt;))|(?P&lt;text&gt;[^&lt;&gt;]*)。在一些庞大的 html 源代码上尝试一下。我在其他一些答案的 cmets 中发布了几个指向 RegexBuddy 屏幕截图的链接(可以很好地可视化)。
【解决方案6】:

供您参考,

使用 JQuery 代替正则表达式,可以从 html 标记中单独提取文本。为此,您可以使用以下模式。

$("<div/>").html("#elementId").text()

你可以参考这个JSFIDDLE

【讨论】:

    猜你喜欢
    • 2023-03-18
    • 2010-12-20
    • 2019-09-14
    • 2012-10-21
    • 2010-09-15
    • 1970-01-01
    • 2011-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多