似乎在stackoverflow上的每个问题中,提问者使用regex从HTML中获取一些信息,将不可避免地有一个“答案”,说不要使用regex解析HTML。
为什么不呢?我知道有一些所谓的“真正的”HTML解析器,比如Beautiful Soup,我相信它们是强大而有用的,但如果您只是在做一些简单、快速或简单的事情,那么当一些正则表达式语句就可以很好地工作时,为什么要麻烦使用如此复杂的东西呢?
此外,是否只是因为我不理解正则表达式的某些基本原理,才使得它们在解析中成为一个糟糕的选择?
似乎在stackoverflow上的每个问题中,提问者使用regex从HTML中获取一些信息,将不可避免地有一个“答案”,说不要使用regex解析HTML。
为什么不呢?我知道有一些所谓的“真正的”HTML解析器,比如Beautiful Soup,我相信它们是强大而有用的,但如果您只是在做一些简单、快速或简单的事情,那么当一些正则表达式语句就可以很好地工作时,为什么要麻烦使用如此复杂的东西呢?
此外,是否只是因为我不理解正则表达式的某些基本原理,才使得它们在解析中成为一个糟糕的选择?
当前回答
请记住,虽然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.)
但是,如果我不得不担心嵌套表,那么我的方法根本就行不通!我就只能靠《美丽汤》了。但是,即使这样,有时也可以使用正则表达式获取所需的块,然后从那里展开。
其他回答
(来自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标记配对的内容块,它不查找匹配的结束标记,但它将拾取结束标记。用你自己的语言滚动一堆来检查这些。
与“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和正则表达式有关,因为他们找不到自己的正则表达式。然后,必须考虑使用DOM或SAX解析器或类似的东西是否会更容易一些。它们是为处理类似xml的文档结构而优化和构造的。
当然,有些问题可以用正则表达式轻松解决。但重点在于容易。
如果您只想找到所有看起来像http://.../的url,那么使用regexp是没问题的。但是如果你想要找到a- element中所有具有'mylink'类的url,你可能最好使用合适的解析器。
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]*? )
)
)
)
>
对于快速´n´dirty regexp就可以了。但是要知道的基本问题是,不可能构造一个正确解析HTML的regexp。
原因是regexp不能处理任意嵌套的表达式。参见正则表达式能否用于匹配嵌套模式?