似乎在stackoverflow上的每个问题中,提问者使用regex从HTML中获取一些信息,将不可避免地有一个“答案”,说不要使用regex解析HTML。
为什么不呢?我知道有一些所谓的“真正的”HTML解析器,比如Beautiful Soup,我相信它们是强大而有用的,但如果您只是在做一些简单、快速或简单的事情,那么当一些正则表达式语句就可以很好地工作时,为什么要麻烦使用如此复杂的东西呢?
此外,是否只是因为我不理解正则表达式的某些基本原理,才使得它们在解析中成为一个糟糕的选择?
似乎在stackoverflow上的每个问题中,提问者使用regex从HTML中获取一些信息,将不可避免地有一个“答案”,说不要使用regex解析HTML。
为什么不呢?我知道有一些所谓的“真正的”HTML解析器,比如Beautiful Soup,我相信它们是强大而有用的,但如果您只是在做一些简单、快速或简单的事情,那么当一些正则表达式语句就可以很好地工作时,为什么要麻烦使用如此复杂的东西呢?
此外,是否只是因为我不理解正则表达式的某些基本原理,才使得它们在解析中成为一个糟糕的选择?
两个简单的原因:
编写一个能够抵御恶意输入的正则表达式是困难的;比使用预先构建的工具难多了 编写一个正则表达式来处理你不可避免地会遇到的荒谬的标记是困难的;比使用预先构建的工具难多了
关于正则表达式在解析中的适用性:它们并不合适。您是否见过解析大多数语言所需的正则表达式类型?
因为有很多方法可以“搞砸”HTML,浏览器会以一种相当自由的方式对待它们,但要用正则表达式重现浏览器的自由行为来覆盖所有情况需要相当大的努力,所以你的正则表达式将不可避免地在某些特殊情况下失败,这可能会在你的系统中引入严重的安全漏洞。
问题是,大多数用户问的问题都与HTML和正则表达式有关,因为他们找不到自己的正则表达式。然后,必须考虑使用DOM或SAX解析器或类似的东西是否会更容易一些。它们是为处理类似xml的文档结构而优化和构造的。
当然,有些问题可以用正则表达式轻松解决。但重点在于容易。
如果您只想找到所有看起来像http://.../的url,那么使用regexp是没问题的。但是如果你想要找到a- element中所有具有'mylink'类的url,你可能最好使用合适的解析器。
对于快速´n´dirty regexp就可以了。但是要知道的基本问题是,不可能构造一个正确解析HTML的regexp。
原因是regexp不能处理任意嵌套的表达式。参见正则表达式能否用于匹配嵌套模式?
正则表达式无法解析整个HTML,因为它依赖于匹配开始标记和结束标记,而正则表达式则无法匹配。
正则表达式只能匹配常规语言,但HTML是一种与上下文无关的语言,而不是常规语言(正如@StefanPochmann所指出的,常规语言也是与上下文无关的,因此与上下文无关并不一定意味着不常规)。在HTML上使用regexp唯一能做的事情是启发式,但这并不适用于所有条件。任何正则表达式都可以错误地匹配HTML文件。
就解析而言,正则表达式在“词法分析”(lexer)阶段很有用,在这个阶段,输入被分解成标记。它在实际的“构建解析树”阶段用处不大。
对于HTML解析器,我希望它只接受格式良好的HTML,而这需要正则表达式所不能做到的功能(它们不能“计数”并确保给定数量的开始元素与相同数量的结束元素相平衡)。
我相信答案就在计算理论中。对于使用正则表达式解析的语言,根据定义必须是“regular”(链接)。HTML不是常规语言,因为它不符合常规语言的许多标准(与HTML代码中固有的多层嵌套有很大关系)。如果你对计算理论感兴趣,我推荐这本书。
“这要看情况”。由于这里给出的所有原因,正则表达式不能也不能准确地解析HTML,这是事实。但是,如果错误的后果(比如不处理嵌套标记)很小,如果正则表达式在您的环境中非常方便(比如在入侵Perl时),那么就继续。
假设您正在解析链接到您站点的网页——也许您是通过谷歌链接搜索找到它们的——并且您想要一种快速方法来大致了解链接周围的上下文。您试图运行一个小报告,可能会提醒您链接垃圾邮件,诸如此类。
在这种情况下,对某些文档进行错误解析并不是什么大问题。除了你自己,没有人会发现错误,如果你足够幸运,你可以单独跟进。
我想我是说这是一种权衡。有时,实现或使用正确的解析器(尽管很简单)可能不值得麻烦,如果准确性不是至关重要的话。
小心你的假设。例如,如果您试图解析将公开显示的内容,我可以想到regexp快捷方式可能适得其反的几种情况。
在某些情况下,使用正则表达式解析HTML中的某些信息是正确的——这在很大程度上取决于具体情况。
上面的共识是,总的来说,这是一个坏主意。然而,如果HTML结构是已知的(并且不太可能改变),那么它仍然是一种有效的方法。
请记住,虽然HTML本身不是规则的,但您正在查看的页面的某些部分可能是规则的。
例如,<form>标签被嵌套是一个错误;如果网页正常工作,那么使用正则表达式获取<form>将是完全合理的。
I recently did some web scraping using only Selenium and regular expressions. I got away with it because the data I wanted was put in a <form>, and put in a simple table format (so I could even count on <table>, <tr> and <td> to be non-nested--which is actually highly unusual). In some degree, regular expressions were even almost necessary, because some of the structure I needed to access was delimited by comments. (Beautiful Soup can give you comments, but it would have been difficult to grab <!-- BEGIN --> and <!-- END --> blocks using Beautiful Soup.)
但是,如果我不得不担心嵌套表,那么我的方法根本就行不通!我就只能靠《美丽汤》了。但是,即使这样,有时也可以使用正则表达式获取所需的块,然后从那里展开。
Actually, HTML parsing with regex is perfectly possible in PHP. You just have to parse the whole string backwards using strrpos to find < and repeat the regex from there using ungreedy specifiers each time to get over nested tags. Not fancy and terribly slow on large things, but I used it for my own personal template editor for my website. I wasn't actually parsing HTML, but a few custom tags I made for querying database entries to display tables of data (my <#if()> tag could highlight special entries this way). I wasn't prepared to go for an XML parser on just a couple of self created tags (with very non-XML data within them) here and there.
所以,即使这个问题已经死了,它仍然会出现在谷歌搜索中。我读了它,并认为“接受挑战”,并完成了修复我的简单代码,而不需要替换所有东西。决定给有类似理由的人提供不同的意见。最后一个答案是4小时前发布的,所以这仍然是一个热门话题。
(来自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数据,正则表达式只会让你将来头疼。
You, know...there's a lot of mentality of you CAN'T do it and I think that everyone on both sides of the fence are right and wrong. You CAN do it, but it takes a little more processing than just running one regex against it. Take this (I wrote this inside of an hour) as an example. It assumes the HTML is completely valid, but depending on what language you're using to apply the aforementioned regex, you could do some fixing of the HTML to make sure that it will succeed. For example, removing closing tags that are not supposed to be there: </img> for example. Then, add the closing single HTML forward slash to elements that are missing them, etc.
我将在编写一个库的上下文中使用它,该库允许我执行类似于JavaScript的[x]. getelementsbytagname()的HTML元素检索。我只是拼接了我在正则表达式的DEFINE部分中编写的功能,并使用它来进入元素树,一次一个。
那么,这将是验证HTML的最终100%答案吗?不。但这只是个开始,只要再努力一点,就可以做到。然而,试图在一个正则表达式执行中完成它是不实际的,也不有效。
这个表达式从HTML元素中检索属性。它支持:
未加引号/加引号的属性, 单引号/双引号, 属性中的转义引号, 等号周围的空格, 任意数量的属性, 只检查标签内的属性, 转义注释,以及 在一个属性值中管理不同的引号。
(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|。)*?-\-\>)|(?:<(\ S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\ s*?[\"']?((?:(?<=\"))(?:(?<=\\)\"|[^\"])*|(?<=')(? : (?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+)[\ "']?\s*)
来看看。在演示中,使用“gisx”标志效果更好。
我也试着用正则表达式来做这个。它主要用于查找与下一个HTML标记配对的内容块,它不查找匹配的结束标记,但它将拾取结束标记。用你自己的语言滚动一堆来检查这些。
与“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 - <![CDATA[…]] > 自关闭标签- <div…/> 可选属性值- <input checked> 未加引号/加引号的属性值- <div style='…'> 单引号/双引号- <div style="…" > 转义引号- <a title='John\'s Story'> (这不是真正有效的HTML,但我是一个好人) 等号周围的空格- <a href = '…'> 命名捕获感兴趣的位
它还可以很好地避免在格式错误的标记上触发,比如当您忘记了<或>时。
如果你的regex支持重复命名捕获,那么你是黄金,但Python re不支持(我知道regex支持,但我需要使用香草Python)。以下是你得到的结果:
content - All of the content up to the next tag. You could leave this out. markup - The entire tag with everything in it. comment - If it's a comment, the comment contents. cdata - If it's a <![CDATA[...]]>, the CDATA contents. close_tag - If it's a close tag (</div>), the tag name. tag - If it's an open tag (<div>), the tag name. attributes - All attributes inside the tag. Use this to get all attributes if you don't get repeated groups. attribute - Repeated, each attribute. attribute_name - Repeated, each attribute name. attribute_value - Repeated, each attribute value. This includes the quotes if it was quoted. is_self_closing - This is / if it's a self-closing tag, otherwise nothing. _q and _v - Ignore these; they're used internally for backreferences.
如果您的正则表达式引擎不支持重复的命名捕获,则可以使用一个被调用的部分来获取每个属性。只需在属性组上运行该正则表达式,从中获得每个属性、attribute_name和attribute_value。
演示在这里:https://regex101.com/r/mH8jSu/11
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]*? )
)
)
)
>