在stackoverflow上,似乎每一个使用正则表达式从HTML中获取信息的提问者都会不可避免地得到一个“答案”,该答案说不要使用正则表达式解析HTML。
为什么不呢?我知道有所谓的“真正”的HTML解析器,比如Beautiful Soup,我相信它们很强大和有用,但是如果你只是做一些简单、快速或粗糙的事情,那么为什么要使用这么复杂的东西,当几个正则表达式语句也可以工作得很好呢?
此外,是否有一些基本的东西我没有理解,使得正则表达式在解析中总是不好的选择?
在stackoverflow上,似乎每一个使用正则表达式从HTML中获取信息的提问者都会不可避免地得到一个“答案”,该答案说不要使用正则表达式解析HTML。
为什么不呢?我知道有所谓的“真正”的HTML解析器,比如Beautiful Soup,我相信它们很强大和有用,但是如果你只是做一些简单、快速或粗糙的事情,那么为什么要使用这么复杂的东西,当几个正则表达式语句也可以工作得很好呢?
此外,是否有一些基本的东西我没有理解,使得正则表达式在解析中总是不好的选择?
对于快速而简单的正则表达式来说,这样做已经足够了。但是需要知道的根本事实是,构建一个可以正确解析HTML的正则表达式是不可能的。
原因在于正则表达式无法处理任意嵌套的表达式。请参见“ 正则表达式是否能用于匹配嵌套的模式?”
(来自http://htmlparsing.com/regexes)
假设您有一个HTML文件,您想从其中提取<img>标签中的URL。
<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'>
或者<img src=http://example.com/whatever.jpg>
或者<img border=0 src="http://example.com/whatever.jpg">
或者<img
src="http://example.com/whatever.jpg">
否则您将开始收到来自假阳性的反馈
<!-- // commented out
<img src="http://example.com/outdated.png">
-->
看起来很简单,如果只是针对一个不会改变的文件可能确实很简单,但如果是处理任意HTML数据,使用正则表达式只会在未来带来烦恼。
两个快速的原因:
关于正则表达式在解析上的适用性: 它们并不适用。您是否曾见过解析大多数语言所需的正则表达式?
就解析而言,正则表达式可以在“词法分析”(词法分析器)阶段中很有用,这里输入被分成标记。但它在实际的“构建解析树”阶段中不太有用。
对于HTML解析器,我期望它只接受格式良好的HTML,而这需要超出正则表达式所能做的能力(它们无法“计数”,并确保一定数量的开放元素与相同数量的关闭元素平衡)。
由于有许多方法可以“搞砸”HTML,而浏览器会以相当宽松的方式处理它,但要复制浏览器的宽松行为以覆盖所有情况需要付出相当大的努力,因此您的正则表达式不可避免地会在某些特殊情况下失败,并可能在系统中引入严重的安全漏洞。
http://.../
的URL地址,那么使用正则表达式即可。但如果您想查找所有位于class为“mylink”的a元素中的URL,则最好使用适当的解析器。正则表达式并不适用于处理嵌套标签结构,而且在处理真实HTML时,要处理所有可能的边缘情况最好也是很复杂的(最坏的情况下是不可能的)。
HTML/XML被分为标记和内容。使用正则表达式只能进行词法标记解析。我猜您可以推断出内容。这对于SAX分析器来说是一个不错的选择。标记和内容可以传递给用户定义的函数,从而可以跟踪元素的嵌套/闭合。
就解析标签本身而言,可以使用正则表达式,并用于从文档中剥离标签。
经过多年的测试,我已经找到了浏览器解析标签的秘密,包括良好的和错误的标记。
这些常规元素的解析形式如下:
这些标记的核心使用此正则表达式
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
[^>]?
是其中一个选择项。这将匹配来自不正确格式的标记中不平衡引号。[\w:]
代表标记名称?实际上,代表标记名称的合法字符是一个包含大量Unicode字符的列表。 <
(?:
[\w:]+
\s+
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
\s* /?
)
>
另外,我们还看到您无法仅搜索特定的标签而不解析所有标签。 我是说你可以,但它必须使用像(*SKIP)(*FAIL)之类的动词组合,但仍然必须解析所有标签。
原因是标记语法可能隐藏在其他标记中等等。
因此,要被动解析所有标签,需要像下面这样的正则表达式。 这个特定的正则表达式也匹配不可见内容。
随着新的HTML或xml或任何其他开发新构造的出现,只需将其添加为其中一种选择即可。
网页注释-我从未见过一个web页面(或xhtml / xml)会有问题。如果你发现一个,请让我知道。
性能注释-它很快。这是我看过的最快的标记解析器(可能有更快的,谁知道)。
我有几个具体版本。 它也非常适合作为刮板(如果你是亲身体验者)。
完整的原始正则表达式
<(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\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]*?))))>
格式化的外观
<
(?:
(?:
(?:
# 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]*? )
)
)
)
>