似乎在stackoverflow上,每个问询者都在使用正则表达式从HTML中获取某些信息,每个问题不可避免地会有一个“答案”,说不使用正则表达式来解析HTML。
为什么不?我知道那里有没有引号的“真实” HTML解析器,例如Beautiful Soup,而且我敢肯定它们功能强大且有用,但是如果您只是在做简单,快速或肮脏的事情,那为什么呢?当使用一些正则表达式语句就可以了吗?
此外,对于正则表达式,我是否不了解某些基本知识,因而使它们成为一般解析的错误选择?
似乎在stackoverflow上,每个问询者都在使用正则表达式从HTML中获取某些信息,每个问题不可避免地会有一个“答案”,说不使用正则表达式来解析HTML。
为什么不?我知道那里有没有引号的“真实” HTML解析器,例如Beautiful Soup,而且我敢肯定它们功能强大且有用,但是如果您只是在做简单,快速或肮脏的事情,那为什么呢?当使用一些正则表达式语句就可以了吗?
此外,对于正则表达式,我是否不了解某些基本知识,因而使它们成为一般解析的错误选择?
Answers:
正则表达式无法进行整个HTML解析,因为它取决于匹配开始和结束标记,而正则表达式则无法实现。
正则表达式只能匹配正则语言,但是HTML是无上下文语言,而不是正则语言(正如@StefanPochmann所指出的,正则语言也是无上下文的,因此无上下文不一定意味着不正则)。您可以对HTML的正则表达式执行的唯一操作是启发式,但这并不适用于所有条件。应该有可能呈现一个HTML文件,该文件将被任何正则表达式错误地匹配。
对于quick´n´dirty正则表达式将很好。但是要知道的基本事情是,不可能构造一个可以正确解析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元素检索属性。它支持:
(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)
检查一下。如演示中所示,它与“ gisx”标志一起使用效果更好。
<script>
标记中的一段JavaScript代码)匹配明显的URL 。
HTML / XML分为标记和内容。正则表达式仅在进行词法标签解析时有用。我想您可以推断出内容。对于SAX解析器来说,这将是一个不错的选择。标签和内容可以传递给用户定义的功能,在该功能中可以跟踪元素的嵌套/关闭。
就解析标签而言,它可以使用正则表达式完成,并用于从文档中剥离标签。
经过多年的测试,我发现浏览器解析格式正确和格式错误的标签的秘密。
普通元素使用以下形式解析:
这些标签的核心使用此正则表达式
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
您会注意到这[^>]?
是其中的一种交替。这将匹配格式错误的标签中不平衡的报价。
它也是所有正则表达式最邪恶的根源。它的使用方式将触发颠簸,以满足其贪婪的,必须匹配的量化容器。
如果以被动方式使用,则永远不会有问题。但是,如果您通过将某些内容与所需的属性/值对相互穿插来强迫某些内容匹配,并且没有提供足够的防止回溯的保护,则这将是一场失控的噩梦。
这是普通旧标签的一般形式。注意[\w:]
标记名称的 代表吗?实际上,代表标签名称的合法字符是令人难以置信的Unicode字符列表。
<
(?:
[\w:]+
\s+
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
\s* /?
)
>
继续,我们还看到您只是在不解析所有标签的情况下无法搜索特定标签。我的意思是可以,但是必须使用动词的组合,例如(* SKIP)(* FAIL),但仍然必须解析所有标签。
原因是标签语法可能隐藏在其他标签等内部。
因此,要被动地解析所有标签,需要像下面这样的正则表达式。这个特定的内容也匹配不可见的内容。
随着新的HTML或xml或任何其他新的结构的开发,只需将其添加为替代之一即可。
网页说明-我从未见过
遇到此问题的网页(或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]*? )
)
)
)
>
不过,“取决于”。确实,由于此处给出的所有原因,正则表达式不能且不能以正确的精度解析HTML。但是,如果出错的后果很小(例如不处理嵌套标签),并且正则表达式在您的环境中非常方便(例如,当您入侵Perl时),请继续。
假设您正在解析链接到您网站的网页(也许您通过Google链接搜索找到了它们),并且您想要一种快速的方法来大致了解链接周围的上下文。您正在尝试运行一些报告,可能会提醒您链接垃圾邮件,诸如此类。
在这种情况下,对某些文档进行错误的解析不会有什么大不了的。除了错误以外,没有人会看到错误,而且如果您很幸运,将很少有可以单独跟进的。
我想我是说这是一个权衡。如果准确性不是很关键,有时实现或使用正确的解析器(尽管可能如此简单)可能不值得麻烦。
请小心您的假设。我可以想到一些正则表达式快捷方式会适得其反的方法,例如,当您尝试解析将公开显示的内容时。
请记住,尽管HTML本身不是常规的,但是您正在查看的页面部分可能是常规的。
例如,<form>
嵌套标签是错误的。如果网页正常运行,则使用正则表达式来抓取a <form>
是完全合理的。
我最近仅使用Selenium和正则表达式进行了一些Web抓取。我之所以无法使用它,是因为我想要的数据以形式放置<form>
,并以简单的表格格式放置(因此我什至可以依靠<table>
,<tr>
并且<td>
无需嵌套,这实际上是非常不寻常的)。在某种程度上,正则表达式甚至几乎是必需的,因为我需要访问的某些结构由注释分隔。(“美丽的汤”可以给您评论,但是使用“美丽的汤” 将很难抓住<!-- BEGIN -->
和<!-- END -->
阻止。)
但是,如果我不得不担心嵌套表,那么我的方法根本行不通!我本来只能依靠美丽汤。但是,即使那样,有时您仍可以使用正则表达式来获取所需的块,然后从那里向下钻取。
实际上,用regex进行HTML解析在PHP中是完全可行的。您只需要向后解析整个字符串,即可使用每次使用不愉快的说明符从那里strrpos
查找<
并重复该正则表达式来克服嵌套标签。在大型事物上并不花哨并且非常慢,但是我将其用于我自己的网站个人模板编辑器。我实际上并不是解析HTML,而是为查询数据库条目以显示数据表而制作了一些自定义标签(我的<#if()>
标签可以以此方式突出显示特殊条目)。我不准备只在几个自创建的标签(其中包含非常非XML数据)上使用XML解析器。
因此,即使这个问题已经相当严重,它仍然会显示在Google搜索中。我读了一下,以为“接受了挑战”,完成了我的简单代码的修复,而不必替换所有内容。决定为任何出于类似原因的人提供不同的意见。另外,最后一个答案是在4个小时前发布的,因此这仍然是一个热门话题。
<tag >
)您是否考虑了注释掉的结束标签?(例如,<tag> <!-- </tag> -->
)您是否考虑过CDATA?您是否考虑了大小写不一致的标记?(例如,<Tag> </tAG>
)你有没有考虑这个呢?
我也为此尝试了一个正则表达式。这对于查找与下一个HTML标签配对的内容块最有用,并且它不查找匹配的 close标签,但是它将拾取close标签。用您自己的语言滚动堆栈以检查这些内容。
与“ sx”选项一起使用。如果您感到幸运,也可以使用'g':
(?P<content>.*?) # Content up to next tag
(?P<markup> # Entire tag
<!\[CDATA\[(?P<cdata>.+?)]]>| # <![CDATA[ ... ]]>
<!--(?P<comment>.+?)-->| # <!-- Comment -->
</\s*(?P<close_tag>\w+)\s*>| # </tag>
<(?P<tag>\w+) # <tag ...
(?P<attributes>
(?P<attribute>\s+
# <snip>: Use this part to get the attributes out of 'attributes' group.
(?P<attribute_name>\w+)
(?:\s*=\s*
(?P<attribute_value>
[\w:/.\-]+| # Unquoted
(?=(?P<_v> # Quoted
(?P<_q>['\"]).*?(?<!\\)(?P=_q)))
(?P=_v)
))?
# </snip>
)*
)\s*
(?P<is_self_closing>/?) # Self-closing indicator
>) # End of tag
这是专为Python设计的(它可能适用于其他语言,还没有尝试过,它使用正向先行,负向后退和命名的反向引用)。支持:
<div ...>
</div>
<!-- ... -->
<![CDATA[ ... ]]>
<div .../>
<input checked>
<div style='...'>
<div style="...">
<a title='John\'s Story'>
<a href = '...'>
关于不触发格式错误的标签也很不错,例如您忘记了<
或时>
。
如果您的regex风格支持重复的命名捕获,那么您就可以了,但是Python re
却没有(我知道regex可以,但是我需要使用香草Python)。这是您得到的:
content
-所有内容,直到下一个标签。您可以忽略这个。markup
-包含所有内容的整个标签。comment
-如果是评论,则评论内容。cdata
-如果是<![CDATA[...]]>
,则CDATA内容。close_tag
-如果是关闭标签(</div>
),则为标签名称。tag
-如果是开放标签(<div>
),则为标签名称。attributes
-标签内的所有属性。如果您没有重复的组,请使用它来获取所有属性。attribute
-重复每个属性。attribute_name
-重复,每个属性名称。attribute_value
-重复每个属性值。如果包含引号,则包括引号。is_self_closing
-这是/
自闭标签,否则为空。_q
和_v
-忽略这些; 它们在内部用于反向引用。如果您的正则表达式引擎不支持重复的命名捕获,则可以调用一个部分来获取每个属性。刚上运行正则表达式attributes
组获得各attribute
,attribute_name
和attribute_value
出来。
演示在这里:https : //regex101.com/r/mH8jSu/11
你知道...还有很多你的心态CAN NOT做到这一点,我认为在两侧护栏,每个人都对与错。你CAN做到这一点,但它需要一点点的不只是运行反对一个正则表达式更多的处理。以这个(我在一个小时内写完)为例。它假定HTML完全有效,但是根据您使用哪种语言来应用上述正则表达式,您可以对HTML进行一些修复以确保其成功。例如,删除不应存在的结束标记:</img>
例如。然后,将结尾的单个HTML正斜杠添加到缺少它们的元素中,依此类推。
我将在编写库的上下文中使用此库,例如,该库将允许我执行类似于JavaScript的HTML元素检索[x].getElementsByTagName()
。我只是拼接了我在正则表达式的DEFINE部分中编写的功能,并将其用于单步进入元素树的内部。
那么,这将是验证HTML的最终100%答案吗?否。但这是一个开始,只需做更多的工作就可以完成。但是,尝试在一个正则表达式执行中执行此操作既不切实际,也不有效。