正则表达式:从HTML文档中提取可读(非代码)文本和URL

时间:2010-10-17 00:47:44

标签: html regex text extract invert

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

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

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

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

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

到目前为止,我所做的是在最后添加另一个替代方案,选择“任何不包含&lt;或&gt;”的序列,这意味着剩余的文本。我在捕获组中命名了最后一位,当我迭代匹配时,我检查“text”组中是否存在文本。这是有效的,但我想知道是否有可能通过正则表达式完成所有操作而只是最终会在纯文本上进行匹配。

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

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

有什么建议吗?

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

6 个答案:

答案 0 :(得分:2)

  

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

这就是人们通常会做的事情。或者甚至更简单,用空字符串替换标记模式的每个匹配,你剩下的就是你正在寻找的东西。

  

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

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

我想再次提出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)

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

如果您想要提取与正则表达式不匹配的字符串部分,您可以简单地将 匹配的部分替换为空字符串以获得相同的效果。

请注意,这可能起作用的唯一原因是因为您有兴趣移除的代码<script><style>代码无法嵌套。

但是,一个<script>标记包含以编程方式附加另一个<script>标记的代码并不罕见,在这种情况下,正则表达式将失败。在任何标签未正确关闭的情况下,它也会失败。

答案 3 :(得分:0)

好的,所以这就是我的方式:

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

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

然后在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重写中 - 但是如果有的话,它的损坏仍然非常有限(参见上面的一些评论),一般来说这样做的工作相当不错)。

答案 4 :(得分:0)

您无法使用正则表达式解析HTML。

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

我知道你只是为了好玩而做,但是有很多软件包,而不是以正确的方式解析,并且可靠地完成,并且已经过测试。

不要重新发明轮子,这样做的方式几乎可以保证让你在路上受挫。

答案 5 :(得分:0)

供您参考,

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

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

您可以参考此JSFIDDLE