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

时间:2009-02-26 14:24:19

标签: regex html-parsing

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

为什么不呢?我知道那里有引用 - 不引用“真正的”HTML解析器,如Beautiful Soup,我相信它们是强大而有用的,但如果你只是做一些简单,快速或肮脏的事情那么为什么在一些正则表达式语句运行得很好的时候,为什么要使用这么复杂的东西?

此外,是否有一些基本的东西,我不了解正则表达式,这使得它们一般是解析的错误选择?

18 个答案:

答案 0 :(得分:202)

使用正则表达式无法进行整个HTML解析,因为它取决于匹配开头和结束标记,这是正则表达式无法实现的。

正则表达式只能与regular languages匹配,但HTML是context-free language不是常规语言(正如@StefanPochmann指出的那样,常规语言也是无上下文的,所以无上下文并不一定意味着不定期)。你可以用HTML上的regexp做的唯一的事情就是启发式,但这并不适用于所有条件。应该可以呈现一个HTML文件,它将被任何正则表达式错误地匹配。

答案 1 :(得分:34)

对于quick'n'dirty regexp会很好。但要知道的基本事情是不可能构建一个正确正确解析HTML的正则表达式。

原因是regexp无法处理任意嵌套表达式。见Can regular expressions be used to match nested patterns?

答案 2 :(得分:19)

(来自http://htmlparsing.com/regexes

假设您有一个HTML文件,您尝试从中提取网址 < 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'>

<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数据做的任何事情,正则表达式只是未来心痛的一个秘诀。

答案 3 :(得分:16)

两个快速的原因:

  • 编写一个可以抵御恶意输入的正则表达式很难;比使用预建工具更难的方法
  • 写一个可以使用你将不可避免地被困的荒谬标记的正则表达式很难;比使用预建工具更难的方法

关于正则表达式一般用于解析的适用性:它们不适合。您是否见过解析大多数语言所需的各种正则表达式?

答案 4 :(得分:16)

就解析而言,正则表达式在“词法分析”(lexer)阶段非常有用,其中输入被分解为标记。它在实际的“构建解析树”阶段中没那么有用。

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

答案 5 :(得分:8)

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

答案 6 :(得分:7)

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

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

如果您只想找到所有类似http://.../的网址,那么您可以使用正则表达式。但是如果你想找到a-Element中具有类'mylink'的所有URL,你最好使用适当的解析器。

答案 7 :(得分:6)

正则表达式并非设计用于处理嵌套标记结构,并且最好处理所有可能的边缘情况(最坏的情况下,不可能)。

答案 8 :(得分:6)

我相信答案在于计算理论。对于使用正则表达式解析的语言,它必须按照定义“常规”(link)。 HTML不是常规语言,因为它不符合常规语言的许多标准(与html代码中固有的许多嵌套级别有很大关系)。如果您对计算理论感兴趣,我建议this本书。

答案 9 :(得分:4)

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

  • 不带引号/引用的属性,
  • 单/双引号,
  • 在属性内转义引号,
  • 等于标志的空间,
  • 任意数量的属性,
  • 仅检查标记内的属性
  • 转义评论和
  • 管理属性值中的不同引号。

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

Check it out。使用“gisx”标志可以更好地工作,就像演示一样。

答案 10 :(得分:3)

HTML / XML分为标记和内容 正则表达式只对词法标记解析很有用 我想你可以推断出内容 这对SAX解析器来说是个不错的选择 标签和内容可以传递给用户
定义函数,其中元素的嵌套/闭合
可以跟踪。

只要解析标签,就可以用 正则表达式,用于从文档中剥离标签。

经过多年的测试,我发现了这个秘密 浏览器解析标签,既好又坏。

使用以下形式解析普通元素:

这些标签的核心使用此正则表达式

 (?:
      " [\S\s]*? " 
   |  ' [\S\s]*? ' 
   |  [^>]? 
 )+

您会注意到此[^>]?作为替换之一 这将匹配不良标签的不平衡报价。

它也是正则表达式中所有邪恶的唯一 它的使用方式将引发碰撞,以满足它的贪婪,必须匹配 量化容器。

如果被动使用,则永远不会出现问题 但是,如果你强制某些东西要匹配,可以用蓝色点缀它 想要的属性/值对,并且不提供足够的保护 从回溯中,这是一场失控的噩梦。

这是普通旧标签的一般形式 注意代表标签名称的[\w:]
实际上,代表标签名称的 legal 字符为 是一个令人难以置信的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]*? )
           )
      )
 )
 >

答案 11 :(得分:3)

有些情况下,使用正则表达式从HTML解析某些信息是正确的方法 - 这在很大程度上取决于具体情况。

上面的共识是,一般来说这是一个坏主意。但是,如果HTML结构已知(并且不太可能改变),那么它仍然是一种有效的方法。

答案 12 :(得分:3)

“这取决于”。由于此处给出的所有原因,正则表达式不能并且无法准确地解析HTML。但是,如果错误的后果(例如不处理嵌套标签)很小,并且正如你的环境中的正则表达式非常方便(例如当你攻击Perl时),请继续。

假设你是哦,也许正在解析链接到你网站的网页 - 也许你发现它们带有谷歌链接搜索 - 你想要一个快速的方法来大致了解链接的上下文。您正在尝试运行一个可能会提醒您链接垃圾邮件的小报告。

在这种情况下,错误地分析一些文件并不是什么大问题。没有人,但你会看到错误,如果你非常幸运,那么你可以单独跟进。

我想我说这是一个权衡。有时实现或使用正确的解析器 - 尽可能简单 - 如果准确性并不重要,可能不值得。

小心你的假设。例如,如果你试图解析将公开显示的内容,我可以想到正则表达式快捷方式可能会适得其反。

答案 13 :(得分:2)

请注意,虽然HTML本身不是常规的,但您正在查看的网页部分可能是常规的。

例如,<form>标记嵌套是错误的;如果网页工作正常,那么使用正则表达式来抓取<form>将是完全合理的。

我最近只使用Selenium和正则表达式做了一些网页抓取。我侥幸成功,因为我想要的数据放在<form>中,并以简单的表格格式(因此我甚至可以指望<table><tr>和{{1}非嵌套 - 这实际上是非常不寻常的)。在某种程度上,正则表达式甚至几乎是必要的,因为我需要访问的一些结构是由注释分隔的。 (美丽的汤可以给你评论,但使用美丽的汤来抓住<td><!-- BEGIN -->块是很困难的。)

但是,如果我不得不担心嵌套表格,那么我的方法就不会有效了!我不得不依赖美丽的汤。但是,即便如此,有时您可以使用正则表达式来获取所需的块,然后从那里向下钻取。

答案 14 :(得分:2)

实际上,在PHP中完全可以使用正则表达式进行HTML解析。您只需使用strrpos向后解析整个字符串以查找<并使用ungreedy说明符从那里重复正则表达式,每次都可以克服嵌套标记。在大件事情上并不花哨而且非常慢,但我将它用于我自己的个人模板编辑器,用于我的网站。我实际上并没有解析HTML,而是我为查询数据库条目以显示数据表而制作的一些自定义标记(我的<#if()>标记可以通过这种方式突出显示特殊条目)。我不准备在几个自己创建的标签(其中包含非XML数据)中使用XML解析器。

所以,即使这个问题已经相当严重,它仍会出现在Google搜索中。我读了它并认为“挑战接受”并完成修复我的简单代码而不必更换所有内容。决定向寻找类似原因的任何人提供不同的意见。最后的答案也是4小时前发布的,所以这仍然是一个热门话题。

答案 15 :(得分:2)

我也试着用这个正则表达式。它主要用于查找与下一个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="...">
  • Escaped Quotes - <a title='John\'s Story'>
    (这不是真正有效的HTML,但我是一个好人)
  • 等于标志的空格 - <a href = '...'>
  • 有趣位的命名

不要触发格式错误的代码也很不错,例如当您忘记<>时。

如果你的正则表达式支持重复的命名捕获,那么你就是黄金,但Python re却不是(我知道正则表达式,但我需要使用vanilla Python)。这是你得到的:

  • content - 直到下一个标记的所有内容。你可以把它留下来。
  • markup - 包含所有内容的整个代码。
  • comment - 如果是评论,评论内容。
  • cdata - 如果是<![CDATA[...]]>,则为CDATA内容。
  • close_tag - 如果是关闭代码(</div>),则为代码名称。
  • tag - 如果是开放代码(<div>),则为代码名称。
  • attributes - 标记内的所有属性。如果没有重复的组,请使用此选项获取所有属性。
  • attribute - 重复,每个属性。
  • attribute_name - 重复,每个属性名称。
  • attribute_value - 重复,每个属性值。如果引用,则包括引号。
  • is_self_closing - 如果它是自动关闭标记,则为/,否则为空。
  • _q_v - 忽略这些;它们在内部用于反向引用。

如果您的正则表达式引擎不支持重复的命名捕获,那么可以使用一个可以用来获取每个属性的部分。只需在attributes组上运行该正则表达式,即可获得每个attributeattribute_nameattribute_value

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

答案 16 :(得分:1)

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

答案 17 :(得分:0)

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

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

那么,这是验证HTML的最终100%答案吗?不,但这是一个开始,只需要做一些工作,就可以做到。但是,尝试在一个正则表达式执行中执行它是不实际的,也不是有效的。