提问人:ntownsend 提问时间:2/26/2009 最后编辑:Andy Lesterntownsend 更新时间:12/17/2019 访问量:79103
使用正则表达式解析 HTML:为什么不呢?
Using regular expressions to parse HTML: why not?
问:
似乎 stackoverflow 上每个提问者使用正则表达式从 HTML 中获取一些信息的问题都不可避免地会有一个“答案”,即不要使用正则表达式来解析 HTML。
为什么不呢?我知道有像 Beautiful Soup 这样的引号-不引号“真正的”HTML 解析器,我相信它们功能强大且有用,但如果你只是在做一些简单、快速或肮脏的事情,那么当一些正则表达式语句就可以正常工作时,为什么还要使用如此复杂的东西呢?
此外,是否有一些我不了解的正则表达式的基本内容使它们成为一般解析的糟糕选择?
答:
两个快速原因:
- 编写一个能够经得起恶意输入的正则表达式是很困难的;比使用预构建工具更难
- 编写一个可以处理您不可避免地会遇到的荒谬标记的正则表达式是很困难的;比使用预构建工具更难
关于正则表达式通常用于解析的适用性:它们不适合。你有没有见过解析大多数语言所需的正则表达式?
评论
因为有很多方法可以“搞砸”HTML,浏览器会以相当宽松的方式处理,但要重现浏览器的自由行为以用正则表达式覆盖所有情况需要相当多的努力,所以你的正则表达式将不可避免地在某些特殊情况下失败,这可能会在你的系统中引入严重的安全漏洞。
评论
问题在于,大多数提出与 HTML 和正则表达式相关的问题的用户之所以这样做,是因为他们找不到自己的正则表达式。然后,人们必须考虑使用DOM或SAX解析器或类似的东西时,一切都会更容易。它们经过优化和构造,目的是使用类似 XML 的文档结构。
当然,有些问题可以使用正则表达式轻松解决。但重点在于容易。
如果您只想找到所有看起来可以使用正则表达式的 URL。但是,如果您想查找具有类“mylink”的 a-Element 中的所有 URL,您最好使用适当的解析器。http://.../
对于quick'n'dirt,正则表达式就可以了。但最基本的是,不可能构造一个能够正确解析 HTML 的正则表达式。
原因是正则表达式无法处理任意嵌套的表达式。请参阅可以使用正则表达式来匹配嵌套模式吗?
评论
使用正则表达式无法进行整个 HTML 解析,因为它依赖于匹配开始和结束标记,而正则表达式则无法做到这一点。
正则表达式只能匹配常规语言,但 HTML 是一种与上下文无关的语言,而不是常规语言(正如 @StefanPochmann 所指出的,常规语言也是与上下文无关的,因此与上下文无关并不一定意味着不规则)。在 HTML 上使用正则表达式唯一能做的就是启发式,但这并非在所有条件下都有效。应该可以呈现一个 HTML 文件,该文件将被任何正则表达式错误地匹配。
评论
正则表达式对于像 HTML 这样的语言来说还不够强大。当然,有一些示例可以使用正则表达式。但总的来说,它不适合解析。
就解析而言,正则表达式在“词法分析”(词法分析器)阶段很有用,在该阶段,输入被分解为标记。在实际的“构建解析树”阶段,它不太有用。
对于 HTML 解析器,我希望它只接受格式正确的 HTML,并且这需要正则表达式可以执行的功能之外的功能(它们不能“计数”并确保给定数量的开始元素由相同数量的结束元素平衡)。
Regular expressions were not designed to handle a nested tag structure, and it is at best complicated (at worst, impossible) to handle all the possible edge cases you get with real HTML.
I believe that the answer lies in computation theory. For a language to be parsed using regex it must be by definition "regular" (link). HTML is not a regular language as it does not meet a number of criteria for a regular language (much to do with the many levels of nesting inherent in html code). If you are interested in the theory of computation I would recommend this book.
评论
"It depends" though. It's true that regexes don't and can't parse HTML with true accuracy, for all the reasons given here. If, however, the consequences of getting it wrong (such as not handling nested tags) are minor, and if regexes are super-convenient in your environment (such as when you're hacking Perl), go ahead.
Suppose you're, oh, maybe parsing web pages that link to your site--perhaps you found them with a Google link search--and you want a quick way to get a general idea of the context surround your link. You're trying to run a little report that might alert you to link spam, something like that.
In that case, misparsing some of the documents isn't going to be a big deal. Nobody but you will see the mistakes, and if you're very lucky there will be few enough that you can follow up individually.
I guess I'm saying it's a tradeoff. Sometimes implementing or using a correct parser--as easy as that may be--might not be worth the trouble if accuracy isn't critical.
Just be careful with your assumptions. I can think of a few ways the regexp shortcut can backfire if you're trying to parse something that will be shown in public, for example.
There are definitely cases where using a regular expression to parse some information from HTML is the correct way to go - it depends a lot on the specific situation.
The consensus above is that in general it is a bad idea. However if the HTML structure is known (and unlikely to change) then it is still a valid approach.
Keep in mind that while HTML itself isn't regular, portions of a page you are looking at might be regular.
For example, it is an error for tags to be nested; if the web page is working correctly, then using a regular expression to grab a would be completely reasonable.<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 , and put in a simple table format (so I could even count on , and 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 and blocks using Beautiful Soup.)<form>
<table>
<tr>
<td>
<!-- BEGIN -->
<!-- END -->
If I had to worry about nested tables, however, my approach simply would not have worked! I would have had to fall back on Beautiful Soup. Even then, however, sometimes you can use a regular expression to grab the chunk you need, and then drill down from there.
Actually, HTML parsing with regex is perfectly possible in PHP. You just have to parse the whole string backwards using 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 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.strrpos
<
<#if()>
So, even though this question is considerably dead, it still shows up in a Google search. I read it and thought "challenge accepted" and finished fixing my simple code without having to replace everything. Decided to offer a different opinion to anyone searching for a similar reason. Also the last answer was posted 4 hours ago so this is still a hot topic.
评论
<tag >
<tag> <!-- </tag> -->
<Tag> </tAG>
(From http://htmlparsing.com/regexes)
假设您有一个 HTML 文件,您正在尝试从中提取 URL <img> 标记。
<img src="http://example.com/whatever.jpg">
所以你在Perl中写了一个这样的正则表达式:
if ( $html =~ /<img src="(.+)"/ ) {
$url = $1;
}
在这种情况下,确实会包含 .但是当
你开始得到这样的 HTML:$url
http://example.com/whatever.jpg
<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 进行一些修复以确保它成功。例如,删除不应该存在的结束标记:例如</img>
。然后,将结束的单个 HTML 正斜杠添加到缺少它们的元素中,等等。
例如,我会在编写一个库的上下文中使用它,该库将允许我执行类似于 JavaScript 的 HTML 元素检索。我只是将我在正则表达式的 DEFINE 部分中编写的功能拼接起来,并用它来单步执行元素树,一次一个。[x].getElementsByTagName()
那么,这会是验证 HTML 的最终 100% 答案吗?不。但这只是一个开始,只要再做一点工作,就可以完成。但是,尝试在一个正则表达式执行中执行此操作既不切实际,也不高效。
此表达式从 HTML 元素中检索属性。它支持:
- 未加引号/引号的属性,
- 单引号/双引号,
- 属性中的转义引号,
- 周围的空格等于符号,
- 任意数量的属性,
- 仅检查标签内的属性,
- 转义注释,以及
- 管理属性值中的不同引号。
(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)
一探究竟。它与“gisx”标志配合得更好,如演示中所示。
评论
<script>
我也为此尝试了正则表达式。它最适用于查找与下一个 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="...">
- 转义引号 -
(这不是真正有效的 HTML,但我是个好人)<a title='John\'s Story'>
- 周围的空格等于符号 -
<a href = '...'>
- 有趣位的命名捕获
在不格式错误的标签上触发也很好,例如当您忘记 or 时。<
>
如果你的正则表达式风格支持重复的命名捕获,那么你就是黄金,但 Python 没有(我知道正则表达式支持,但我需要使用普通的 Python)。这是你得到的:re
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
HTML/XML 分为标记和内容。 正则表达式仅在进行词法标记解析时有用。 我想你可以推断出内容。 对于 SAX 解析器来说,这将是一个不错的选择。 标记和内容可以交付给用户 定义函数,其中嵌套/闭合元素 可以跟踪。
至于只解析标签,可以通过 正则表达式,用于从文档中去除标签。
经过多年的测试,我找到了 浏览器解析标签的方式,包括格式良好和格式不佳的标记。
普通元素使用以下形式进行解析:
这些标记的核心使用此正则表达式
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
您会注意到这是交替之一。
这将匹配来自格式错误的标记的不平衡引号。[^>]?
它也是正则表达式最邪恶的根源。 它的使用方式会引发碰撞,以满足它的贪婪、必须匹配 量化容器。
如果被动使用,永远不会有问题 但是,如果你通过穿插它来强制匹配某些东西 所需的属性/值对,并且未提供足够的保护 从回溯来看,这是一场失控的噩梦。
这是普通旧标签的一般形式。
注意到表示标签名称了吗?
实际上,表示标记名称的合法字符
是一个令人难以置信的 Unicode 字符列表。[\w:]
<
(?:
[\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]*? )
)
)
)
>
评论