使用正则表达式解析 HTML:为什么不呢?

Using regular expressions to parse HTML: why not?

提问人:ntownsend 提问时间:2/26/2009 最后编辑:Andy Lesterntownsend 更新时间:12/17/2019 访问量:79103

问:

似乎 stackoverflow 上每个提问者使用正则表达式从 HTML 中获取一些信息的问题都不可避免地会有一个“答案”,即不要使用正则表达式来解析 HTML。

为什么不呢?我知道有像 Beautiful Soup 这样的引号-不引号“真正的”HTML 解析器,我相信它们功能强大且有用,但如果你只是在做一些简单、快速或肮脏的事情,那么当一些正则表达式语句就可以正常工作时,为什么还要使用如此复杂的东西呢?

此外,是否有一些我不了解的正则表达式的基本内容使它们成为一般解析的糟糕选择?

正则表达式 HTML 解析

评论

3赞 jcrossley3 2/26/2009
我认为这是 stackoverflow.com/questions/133601 的骗局
27赞 takeshin 6/23/2010
因为只有 Chuck Norris 才能使用正则表达式解析 HTML(正如这个著名的 Zalgo 事件中所解释的那样:stackoverflow.com/questions/1732348/......
1赞 mac 7/20/2011
这个问题促使我问另一个不知何故相关的问题。如果您有兴趣: 为什么不能使用正则表达式来解析 HTML/XML:通俗易懂的正式解释
0赞 Kelly S. French 1/13/2012
当心扎尔戈
0赞 aliteralmind 4/10/2014
此问题已添加到 Stack Overflow 正则表达式常见问题解答的“常见验证任务”下。

答:

17赞 Hank Gay 2/26/2009 #1

两个快速原因:

  • 编写一个能够经得起恶意输入的正则表达式是很困难的;比使用预构建工具更难
  • 编写一个可以处理您不可避免地会遇到的荒谬标记的正则表达式是很困难的;比使用预构建工具更难

关于正则表达式通常用于解析的适用性:它们不适合。你有没有见过解析大多数语言所需的正则表达式?

评论

2赞 Hank Gay 7/20/2011
哇?2+ 年后投反对票?如果有人想知道,我没有说“因为理论上是不可能的”,因为这个问题显然是“快速而肮脏”,而不是“正确”。OP显然已经阅读了涵盖理论上不可能的领域的答案,但仍然不满意。
1赞 Adam Jensen 8/22/2014
在 5+ 年后投赞成票。:)至于为什么你会收到反对票,我没有资格说,但就我个人而言,我希望看到一些例子或解释,而不是结束的反问句。
3赞 Kuba hasn't forgotten Monica 7/28/2015
从本质上讲,在交付产品或内部工具时完成的所有快速而肮脏的 html 解析最终都会成为一个巨大的安全漏洞,或者一个等待发生的错误。必须兴致勃勃地劝阻它。如果可以使用正则表达式,则可以使用适当的 html 解析器。
8赞 Tamas Czinege 2/26/2009 #2

因为有很多方法可以“搞砸”HTML,浏览器会以相当宽松的方式处理,但要重现浏览器的自由行为以用正则表达式覆盖所有情况需要相当多的努力,所以你的正则表达式将不可避免地在某些特殊情况下失败,这可能会在你的系统中引入严重的安全漏洞。

评论

1赞 ntownsend 2/26/2009
没错,大多数 HTML 似乎都很可怕。我不明白失败的正则表达式如何引入严重的安全漏洞。你能举个例子吗?
4赞 Tamas Czinege 2/26/2009
ntownsend:例如,你认为你已经从 HTML 中删除了所有的脚本标签,但你的正则表达式失败涵盖了一种特殊情况(比方说,这只适用于 IE6):繁荣,你有一个 XSS 漏洞!
1赞 Tamas Czinege 2/26/2009
这是一个严格假设的例子,因为大多数现实世界的例子都太复杂了,无法放入这些评论中,但你可以通过快速谷歌搜索这个主题来找到一些。
3赞 j_random_hacker 2/26/2009
+1 提及安全角度。当你与整个互联网连接时,你不能写出“大部分时间都有效”的代码。
7赞 okoman 2/26/2009 #3

问题在于,大多数提出与 HTML 和正则表达式相关的问题的用户之所以这样做,是因为他们找不到自己的正则表达式。然后,人们必须考虑使用DOM或SAX解析器或类似的东西时,一切都会更容易。它们经过优化和构造,目的是使用类似 XML 的文档结构。

当然,有些问题可以使用正则表达式轻松解决。但重点在于容易

如果您只想找到所有看起来可以使用正则表达式的 URL。但是,如果您想查找具有类“mylink”的 a-Element 中的所有 URL,您最好使用适当的解析器。http://.../

37赞 kmkaplan 2/26/2009 #4

对于quick'n'dirt,正则表达式就可以了。但最基本的是,不可能构造一个能够正确解析 HTML 的正则表达式。

原因是正则表达式无法处理任意嵌套的表达式。请参阅可以使用正则表达式来匹配嵌套模式吗?

评论

2赞 Ondra Žižka 8/6/2011
一些正则表达式库可以执行递归正则表达式(有效地使它们成为非正则表达式:)
236赞 Johannes Weiss 2/26/2009 #5

使用正则表达式无法进行整个 HTML 解析,因为它依赖于匹配开始和结束标记,而正则表达式则无法做到这一点。

正则表达式只能匹配常规语言,但 HTML 是一种与上下文无关的语言,而不是常规语言(正如 @StefanPochmann 所指出的,常规语言也是与上下文无关的,因此与上下文无关并不一定意味着不规则)。在 HTML 上使用正则表达式唯一能做的就是启发式,但这并非在所有条件下都有效。应该可以呈现一个 HTML 文件,该文件将被任何正则表达式错误地匹配。

评论

27赞 ntownsend 2/26/2009
到目前为止最好的答案。如果它只能匹配常规语法,那么我们需要一个无限大的正则表达式来解析像 HTML 这样的上下文无关语法。我喜欢这些事情有明确的理论答案。
2赞 Hank Gay 2/27/2009
我以为我们讨论的是Perl类型的正则表达式,它们实际上不是正则表达式。
6赞 Alex Paven 9/17/2010
实际上,.Net 正则表达式在某种程度上可以使用平衡组和精心设计的表达式来匹配开始标记和结束标记。当然,将所有这些包含在正则表达式中仍然很疯狂,它看起来像伟大的代码 Chtulhu,并且可能还会召唤真正的代码。最后,它仍然不适用于所有情况。他们说,如果你编写一个可以正确解析任何 HTML 的正则表达式,宇宙就会坍缩到自己身上。
5赞 Ondra Žižka 8/6/2011
一些正则表达式库可以执行递归正则表达式(有效地使它们成为非正则表达式:)
48赞 NikiC 9/18/2011
-1 这个答案从错误的论点(“因为 HTML 不是一种常规语言”)中得出了正确的结论(“使用正则表达式解析 HTML 是个坏主意”)。现在大多数人说“正则表达式”(PCRE)时所指的意思是,它不仅能够解析上下文无关的语法(实际上微不足道),而且还能够解析上下文相关的语法(参见 stackoverflow.com/questions/7434272/...
0赞 Gumbo 2/26/2009 #6

正则表达式对于像 HTML 这样的语言来说还不够强大。当然,有一些示例可以使用正则表达式。但总的来说,它不适合解析。

17赞 Vatine 2/26/2009 #7

就解析而言,正则表达式在“词法分析”(词法分析器)阶段很有用,在该阶段,输入被分解为标记。在实际的“构建解析树”阶段,它不太有用。

对于 HTML 解析器,我希望它只接受格式正确的 HTML,并且这需要正则表达式可以执行的功能之外的功能(它们不能“计数”并确保给定数量的开始元素由相同数量的结束元素平衡)。

5赞 Peter Boughton 2/26/2009 #8

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.

5赞 taggers 2/26/2009 #9

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.

评论

2赞 ntownsend 2/26/2009
I've actually read that book. It just didn't occur to me that HTML is a context-free language.
2赞 catfood 2/26/2009 #10

"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.

2赞 Jason 4/29/2011 #11

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.

2赞 alpheus 2/13/2013 #12

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.

0赞 Deji 2/13/2013 #13

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.

评论

4赞 rmunn 7/10/2014
-1 for suggesting a TERRIBLE idea. Did you consider whitespace between the tag and the closing angle bracket? (E.g., ) Did you consider commented-out closing tags? (E.g., ) Did you consider CDATA? Did you consider inconsistent-case tags? (E.g., ) Did you consider this as well?<tag ><tag> <!-- </tag> --><Tag> </tAG>
1赞 rmunn 7/10/2014
In the particular case of your few custom tags, yes, regular expressions work well. So it's not that your use of them was a mistake in your particular case. That's not HTML, though, and saying "HTML parsing with regex is perfectly possible in PHP" is just flat-out false, and a TERRIBLE idea. The inconsistencies of real HTML (and there are way more than the few I listed) are why you should never parse real HTML with regular expressions. See, well, all the other answers to this question, as well as the one I linked to in my other comment above.
2赞 Deji 7/11/2014
PHP is a turing-complete language, so it's not flat-out false at all. Everything computationally possible is possible, including parsing HTML. Spaces in tags were NEVER a problem and I've since adapted it to list tag elements in-order. My use automatically corrected tags with inconsistent casing, stripped commented stuff at the very first stage and after some later additions all sorts of tags can be easily added (though it's case-sensitive, by my own choice). And I'm pretty sure CDATA is actually an XML element, not a HTML one.
2赞 Deji 7/11/2014
My old method (that I described here) was pretty inefficient and I've recently started a re-write of a lot of the content editors. When it comes to doing these things, possibility isn't the issue; the best way is always the main concern. The real answer is "there's no EASY way to do it in PHP". NO ONE says there's no way to do it in PHP or that it's a terrible idea, but that it's impossible with regex, which I've honestly never tried, but the one major flaw in my answer is I assumed the question was referring to regex within the context of PHP, which is not necessarily the case.
32赞 Andy Lester 9/11/2013 #14

(From http://htmlparsing.com/regexes)

假设您有一个 HTML 文件,您正在尝试从中提取 URL <img> 标记。

<img src="http://example.com/whatever.jpg">

所以你在Perl中写了一个这样的正则表达式:

if ( $html =~ /<img src="(.+)"/ ) {
    $url = $1;
}

在这种情况下,确实会包含 .但是当 你开始得到这样的 HTML:$urlhttp://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 数据执行的任何事情,正则表达式只是未来心痛的秘诀。

评论

4赞 Smit Johnth 8/6/2015
这似乎是真正的答案 - 虽然可能使用正则表达式解析任意 HTML,因为今天的正则表达式不仅仅是一个有限的自动机,为了解析任意 html,而不仅仅是一个具体的页面,你必须在正则表达式中重新实现 HTML 解析器,并且正则表达式肯定会变得 1000 次不可读。
1赞 Ivan Chaer 10/18/2016
嘿安迪,我花时间想出了一个支持你提到的案例的表达方式。stackoverflow.com/a/40095824/1204332让我知道你的想法!:)
2赞 Sz. 3/16/2017
这个答案中的推理已经过时了,而且在今天比原来更不适用(我认为它没有)。(引用OP的话:“如果你只是在做一些简单、快速或肮脏的事情......”。
-1赞 Erutan409 11/22/2015 #15

你知道的。。。有很多你做不到的心态,我认为栅栏两边的每个人都是对的,也是错的。你可以这样做,但它需要更多的处理,而不仅仅是对它运行一个正则表达式。以这个(我在一小时内写完这个)为例。它假设 HTML 是完全有效的,但根据您用于应用上述正则表达式的语言,您可以对 HTML 进行一些修复以确保它成功。例如,删除不应该存在的结束标记:例如</img>。然后,将结束的单个 HTML 正斜杠添加到缺少它们的元素中,等等。

例如,我会在编写一个库的上下文中使用它,该库将允许我执行类似于 JavaScript 的 HTML 元素检索。我只是将我在正则表达式的 DEFINE 部分中编写的功能拼接起来,并用它来单步执行元素树,一次一个。[x].getElementsByTagName()

那么,这会是验证 HTML 的最终 100% 答案吗?不。但这只是一个开始,只要再做一点工作,就可以完成。但是,尝试在一个正则表达式执行中执行此操作既不切实际,也不高效。

3赞 Ivan Chaer 10/18/2016 #16

此表达式从 HTML 元素中检索属性。它支持:

  • 未加引号/引号的属性,
  • 单引号/双引号,
  • 属性中的转义引号,
  • 周围的空格等于符号,
  • 任意数量的属性,
  • 仅检查标签内的属性,
  • 转义注释,以及
  • 管理属性值中的不同引号。

(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)

一探究竟。它与“gisx”标志配合得更好,如演示中所示。

评论

1赞 Eric Duminil 12/15/2016
这很有意思。不可读,可能很难调试,但仍然:令人印象深刻的工作!
0赞 tripleee 4/2/2019
这仍然模糊地假设 HTML 的格式是正确的。如果没有上下文匹配,这将在您通常不想匹配的上下文中匹配明显的 URL,例如在标记内的一段 JavaScript 代码中。<script>
1赞 Hounshell 12/28/2016 #17

我也为此尝试了正则表达式。它最适用于查找与下一个 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

如果正则表达式引擎不支持重复的命名捕获,则可以调用一个部分来获取每个属性。只需在组上运行该正则表达式即可获取每个正则表达式,然后从中取出。attributesattributeattribute_nameattribute_value

在这里演示: https://regex101.com/r/mH8jSu/11

4赞 user557597 6/16/2017 #18

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]*? )
           )
      )
 )
 >

评论

0赞 Cemstrian 6/17/2023
正则表达式格式不正确