首页 文章

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

提问于
浏览
197

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

为什么不?我知道有像Beautiful Soup那样引用-inquote "real" HTML解析器,而且我很有用,但是如果你只是在做一些简单,快速或者肮脏的事情,那么为什么要在使用某些东西时如此复杂呢?几个正则表达式的语句会运行得很好吗?

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

18 回答

  • 2

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

  • 34

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

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

  • 3

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

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

  • 2

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

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

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

  • 201

    两个快速的原因:

    • 编写一个可以抵御恶意输入的正则表达式很难;比使用预建工具更难

    • 写一个可以使用你将不可避免地被困的荒谬标记的正则表达式很难;比使用预建工具更难

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

  • 7

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

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

  • 2

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

    当然,有些问题可以通过正则表达式轻松解决 . 但重点在于 easily .

    如果您只是想找到所有看起来像 http://.../ 的网址're fine with regexps. But if you want to find all URLs that are in a a-Element that has the class ' mylink',您最好使用合适的解析器 .

  • 0

    正则表达式不是为处理嵌套标记结构而设计的,最好处理所有可能的边缘情况(最糟糕的是,不可能) .

  • 16

    我相信答案在于计算理论 . 对于使用正则表达式解析的语言,它必须按照定义"regular"(link) . HTML不是常规语言,因为它不符合常规语言的许多标准(与html代码中固有的多层嵌套有很大关系) . 如果你对计算理论感兴趣,我会推荐this book .

  • 16

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

    • 未引用/引用的属性,

    • 单/双引号,

    • 在属性中转义引号,

    • 周围等于标志的空间,

    • 任意数量的属性,

    • 仅检查标签内的属性,

    • 逃避评论,和

    • 管理属性值中的不同引号 .

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

    Check it out . 使用"gisx"标志可以更好地工作,就像在演示中一样 .

  • 3

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

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

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

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

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

  • 1

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

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

  • 3

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

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

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

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

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

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

    你会注意到这个 [^>]? 是其中一个变化 .
    这将匹配不良标签的不 balancer 报价 .

    它也是正则表达式中所有邪恶的最根本 .
    它贪婪的方式,必须匹配
    量化容器 .

    如果被动地使用,则永远不会有问题 .
    但是,如果你通过穿插它来强迫某些东西匹配
    想要的属性/值对,并且不提供足够的保护
    从回溯中,这是一场失控的噩梦 .

    这是普通旧标签的一般形式 .
    注意表示标签名称的 [\w:]
    实际上,代表标签名称的合法字符
    是一个令人难以置信的Unicode字符列表 .

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

    接下来,我们还发现您无法搜索特定标记
    没有解析所有标签 .
    我的意思是你可以,但它必须结合使用
    动词如(* SKIP)(* FAIL),但仍然需要解析所有标签 .

    原因是标签语法可能隐藏在其他标签内等 .

    因此,要被动地解析所有标签,需要像下面那样的正则表达式 .
    这个特殊的一个也匹配不可见的内容 .

    作为新的HTML或xml或任何其他开发新结构,只需将其添加为
    其中一个变化 .


    网页说明 - 我从未见过这样的网页(或xhtml / xml)
    遇到了麻烦 . 如果你找到一个,请告诉我 .

    表现说明 - 它已经看到了's quick. This is the fastest tag parser I'
    (可能会更快,谁知道) .
    我有几个特定的版本 . 它作为刮刀也很出色
    (如果你是动手型) .


    完整的原始正则表达式

    <(?:(?:(?:(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]*? )
               )
          )
     )
     >
    
  • 4

    请记住,虽然HTML本身并不常见,但您正在查看的页面部分可能是常规的 .

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

    我最近使用Selenium和正则表达式做了一些网页抓取 . 我逃脱了它,因为我想要的数据放在 <form> 中,并放入一个简单的表格式(所以我甚至可以指望 <table><tr><td> 是非嵌套的 - 这实际上非常不寻常) . 在某种程度上,正则表达式甚至几乎是必要的,因为我需要访问的一些结构是由注释分隔的 . (美丽的汤可以给你评论,但使用美丽的汤很难获得 <!-- BEGIN --><!-- END --> 块 . )

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

  • 6

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

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

  • 19

    我也试着用这个正则表达式 . 它's mostly useful for finding chunks of content paired with the next HTML tag, and it doesn'吨寻找 matching 关闭标签,但它会拾取关闭标签 . 用您自己的语言滚动堆栈来检查它们 .

    与'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设计的(它可能适用于其他语言,没有尝试过它,它使用积极的前瞻,负面的lookbehinds和命名的反向引用) . 支持:

    • 打开标签 - <div ...>

    • 关闭标签 - </div>

    • 评论 - <!-- ... -->

    • CDATA - <![CDATA[ ... ]]>

    • 自闭标签 - <div .../>

    • 可选属性值 - <input checked>

    • 未引用/引用的属性值 - <div style='...'>

    • 单/双引号 - <div style="...">

    • Escaped Quotes - <a title='John\'s Story'>
      (这不是一个好人)

    • 等于标志的空间 - <a href = '...'>

    • 有趣位的命名捕获

    不要触发格式错误的标签也很不错,例如当你忘记 <> 时 .

    如果你的正则表达式味道支持重复的命名捕获,那么你就是金色的,但是你得到的却是 re

    • content - 直到下一个标签的所有内容 . 你可以把它留下来 .

    • markup - 包含所有内容的整个标记 .

    • comment - 如果是评论,评论内容 .

    • cdata - 如果是 <![CDATA[...]]> ,则为CDATA内容 .

    • close_tag - 如果是关闭标记( </div> ),则为标记名称 .

    • tag - 如果是开放标签( <div> ),则为标签名称 .

    • attributes - 标签内的所有属性 . 如果没有重复的组,请使用此选项获取所有属性 .

    • attribute - 重复,每个属性 .

    • attribute_name - 重复,每个属性名称 .

    • attribute_value - 重复,每个属性值 . 如果引用,则包括引号 .

    • is_self_closing - 如果是自动关闭标签,则为 / ,否则为空 .

    • _q_v - 忽略这些;它们在内部用于反向引用 .

    如果您的正则表达式引擎没有't support repeated named captures, there'一个被调用的部分,您可以使用它来获取每个属性 . 只需在 attributes 组上运行该正则表达式,即可获得每个 attributeattribute_nameattribute_value .

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

  • 8

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

  • 6

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

    我是'd use this in the context of writing a library that would allow me to perform HTML element retrieval akin to that of JavaScript' s [x].getElementsByTagName() ,例如 . 我只是拼写我在正则表达式的DEFINE部分中编写的功能,并用它来踩到一个元素树,一次一个 .

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

相关问题