似乎在stackoverflow上的每个问题中,提问者使用regex从HTML中获取一些信息,将不可避免地有一个“答案”,说不要使用regex解析HTML。

为什么不呢?我知道有一些所谓的“真正的”HTML解析器,比如Beautiful Soup,我相信它们是强大而有用的,但如果您只是在做一些简单、快速或简单的事情,那么当一些正则表达式语句就可以很好地工作时,为什么要麻烦使用如此复杂的东西呢?

此外,是否只是因为我不理解正则表达式的某些基本原理,才使得它们在解析中成为一个糟糕的选择?


当前回答

HTML/XML分为标记和内容。 Regex只对词法标记解析有用。 我想你可以推断出内容。 对于SAX解析器来说,这是一个很好的选择。 标签和内容可以传递给用户 定义了嵌套/闭包元素的函数 可以被追踪。

只要解析标记就可以了 正则表达式,用于从文档中删除标记。

经过多年的测试,我发现了秘密 浏览器解析标签的方式,包括良好的和不良的形式。

普通元素的解析形式如下:

这些标记的核心使用这个正则表达式

 (?:
      " [\S\s]*? " 
   |  ' [\S\s]*? ' 
   |  [^>]? 
 )+

你会注意到这个[^>]?作为一种替代。 这将匹配格式不正确的标签中的不平衡引号。

它也是正则表达式的所有邪恶之源。 它的使用方式将触发一个碰撞,以满足它的贪婪,必须匹配 量化的容器。

如果被动地使用,就永远不会有问题 但是,如果你通过穿插一些东西来强制匹配 一个需要的属性/值对,并且没有提供足够的保护 从回溯来看,这是一场失控的噩梦。

这是普通旧标签的一般形式。 注意到代表标记名称的[\w:]了吗? 实际上,表示标记名称的合法字符 是一个难以置信的Unicode字符列表。

 <     
 (?:
      [\w:]+ 
      \s+ 
      (?:
           " [\S\s]*? " 
        |  ' [\S\s]*? ' 
        |  [^>]? 
      )+
      \s* /?
 )
 >

继续前进,我们还看到您不能搜索特定的标记 无需解析所有标记。 我的意思是你可以,但它必须使用的组合 像(*SKIP)(*FAIL)这样的动词,但仍然必须解析所有标签。

原因是标记语法可能隐藏在其他标记中,等等。

因此,要被动地解析所有标签,需要一个如下所示的正则表达式。 这个特殊的匹配不可见内容。

作为新的HTML或xml或任何其他开发的新结构,只需将其添加为 其中一种变化。


网页注释-我从未见过一个网页(或xhtml/xml),这 有麻烦。如果你找到了,请告诉我。

性能说明-它很快。这是我见过的最快的标记解析器 (也许会更快,谁知道呢)。 我有几个具体的版本。它也是优秀的刮板 (如果你是亲力亲为的类型)。


完成原始正则表达式

<(?:(?:(?:( applet | style |物体|脚本嵌入| | noframes | noscript | noembed) (: \ s + (? > [s \ s] * ?”|’s \ s ]*?'|(?:(?!/>)[^>])?)+)?\ s * >) [s \ ' s] * ? < / 1 \ s *(?=>))|(?:/?[\ w: +] \ s */?)|(?:[\ w: + s + s(?):“\ \ s] * ?’”| [s \ ]*?'|[^>]?)+\ s * / ?) | " s \ \ ? [ ]*?\?|(?:!(?:(?: DOCTYPE [S \ S ]*?)|(?:\[ CDATA [S \ \ S ]*?\]\])|(?:--[\ S \ ]*?--)|(?: ATTLIST [S \ S] *) |(?:实体[S \ S] *) |(?:元素[S \ S] * ?)) >

格式化的看

 <
 (?:
      (?:
           (?:
                # Invisible content; end tag req'd
                (                             # (1 start)
                     script
                  |  style
                  |  object
                  |  embed
                  |  applet
                  |  noframes
                  |  noscript
                  |  noembed 
                )                             # (1 end)
                (?:
                     \s+ 
                     (?>
                          " [\S\s]*? "
                       |  ' [\S\s]*? '
                       |  (?:
                               (?! /> )
                               [^>] 
                          )?
                     )+
                )?
                \s* >
           )

           [\S\s]*? </ \1 \s* 
           (?= > )
      )

   |  (?: /? [\w:]+ \s* /? )
   |  (?:
           [\w:]+ 
           \s+ 
           (?:
                " [\S\s]*? " 
             |  ' [\S\s]*? ' 
             |  [^>]? 
           )+
           \s* /?
      )
   |  \? [\S\s]*? \?
   |  (?:
           !
           (?:
                (?: DOCTYPE [\S\s]*? )
             |  (?: \[CDATA\[ [\S\s]*? \]\] )
             |  (?: -- [\S\s]*? -- )
             |  (?: ATTLIST [\S\s]*? )
             |  (?: ENTITY [\S\s]*? )
             |  (?: ELEMENT [\S\s]*? )
           )
      )
 )
 >

其他回答

(来自http://htmlparsing.com/regexes)

假设您有一个HTML文件,您试图从中提取url < img >标签。

<img src="http://example.com/whatever.jpg">

所以你可以用Perl写一个这样的正则表达式:

if ( $html =~ /<img src="(.+)"/ ) {
    $url = $1;
}

在本例中,$url确实包含 http://example.com/whatever.jpg。但是当 你会得到这样的HTML:

<img src='http://example.com/whatever.jpg'>

or

<img src=http://example.com/whatever.jpg>

or

<img border=0 src="http://example.com/whatever.jpg">

or

<img
    src="http://example.com/whatever.jpg">

否则你就会得到假阳性

<!-- // commented out
<img src="http://example.com/outdated.png">
-->

它看起来很简单,对于一个单一的、不变的文件来说可能很简单,但是对于任意HTML数据,正则表达式只会让你将来头疼。

问题是,大多数用户问的问题都与HTML和正则表达式有关,因为他们找不到自己的正则表达式。然后,必须考虑使用DOM或SAX解析器或类似的东西是否会更容易一些。它们是为处理类似xml的文档结构而优化和构造的。

当然,有些问题可以用正则表达式轻松解决。但重点在于容易。

如果您只想找到所有看起来像http://.../的url,那么使用regexp是没问题的。但是如果你想要找到a- element中所有具有'mylink'类的url,你可能最好使用合适的解析器。

“这要看情况”。由于这里给出的所有原因,正则表达式不能也不能准确地解析HTML,这是事实。但是,如果错误的后果(比如不处理嵌套标记)很小,如果正则表达式在您的环境中非常方便(比如在入侵Perl时),那么就继续。

假设您正在解析链接到您站点的网页——也许您是通过谷歌链接搜索找到它们的——并且您想要一种快速方法来大致了解链接周围的上下文。您试图运行一个小报告,可能会提醒您链接垃圾邮件,诸如此类。

在这种情况下,对某些文档进行错误解析并不是什么大问题。除了你自己,没有人会发现错误,如果你足够幸运,你可以单独跟进。

我想我是说这是一种权衡。有时,实现或使用正确的解析器(尽管很简单)可能不值得麻烦,如果准确性不是至关重要的话。

小心你的假设。例如,如果您试图解析将公开显示的内容,我可以想到regexp快捷方式可能适得其反的几种情况。

两个简单的原因:

编写一个能够抵御恶意输入的正则表达式是困难的;比使用预先构建的工具难多了 编写一个正则表达式来处理你不可避免地会遇到的荒谬的标记是困难的;比使用预先构建的工具难多了

关于正则表达式在解析中的适用性:它们并不合适。您是否见过解析大多数语言所需的正则表达式类型?

正则表达式无法解析整个HTML,因为它依赖于匹配开始标记和结束标记,而正则表达式则无法匹配。

正则表达式只能匹配常规语言,但HTML是一种与上下文无关的语言,而不是常规语言(正如@StefanPochmann所指出的,常规语言也是与上下文无关的,因此与上下文无关并不一定意味着不常规)。在HTML上使用regexp唯一能做的事情是启发式,但这并不适用于所有条件。任何正则表达式都可以错误地匹配HTML文件。