【问题标题】:Regular expression for extracting tag attributes提取标签属性的正则表达式
【发布时间】:2010-09-23 22:36:18
【问题描述】:

我正在尝试提取锚标记 (<a>) 的属性。到目前为止,我有这样的表达:

(?<name>\b\w+\b)\s*=\s*("(?<value>[^"]*)"|'(?<value>[^']*)'|(?<value>[^"'<> \s]+)\s*)+

适用于类似的字符串

<a href="test.html" class="xyz">

和(单引号)

<a href='test.html' class="xyz">

但不适用于没有引号的字符串:

<a href=test.html class=xyz>

如何修改我的正则表达式使其与不带引号的属性一起使用?或者有更好的方法吗?

更新: 感谢迄今为止所有好的 cmets 和建议。有一件事我没有提到:遗憾的是,我不得不修补/修改不是我编写的代码。而且没有时间/金钱从下往上重写这些东西。

【问题讨论】:

  • 对于 C#,我选择了 AngleSharp 及其 HtmlParser 类。

标签: html regex


【解决方案1】:

我建议您使用HTML Tidy 将HTML 转换为XHTML,然后使用合适的XPath 表达式提取属性。

【讨论】:

    【解决方案2】:

    如果您想通俗一点,则必须查看 a 标签的精确规范,例如here。但即便如此,如果您使用完美的正则表达式,如果您的 html 格式错误怎么办?

    我会建议去一个库来解析 html,这取决于你使用的语言:例如就像蟒蛇的美丽汤一样。

    【讨论】:

      【解决方案3】:

      Token Mantra 响应:您不应使用正则表达式调整/修改/收获/或以其他方式生成 html/xml。

      也可能存在必须考虑的极端情况条件,例如 \' 和 \"。最好使用适当的 DOM 解析器、XML 解析器或其他许多经过试验和测试的工具之一这份工作,而不是自己发明。

      我真的不在乎你使用哪一个,只要它被认可、测试过并且你使用它。

      my $foo  = Someclass->parse( $xmlstring ); 
      my @links = $foo->getChildrenByTagName("a"); 
      my @srcs = map { $_->getAttribute("src") } @links; 
      # @srcs now contains an array of src attributes extracted from the page. 
      

      【讨论】:

      • "必须考虑诸如 \' 和 \" 之类的极端情况条件" ...您不能在 HTML 属性中转义引号。包含它们的唯一方法是将它们编码为一个实体,“
      • 是的,HTML 的规范规定你应该对它们进行实体编码,但是,由于人们使用反斜杠浏览器适应使其工作,并且更多人使用它,因此, 你的解析器必须能够处理它:)
      • Chrome 79 中的 2020 年并非如此;反斜杠引号不被识别为转义引号,相反,斜杠被删除并且引号被识别为值分隔符或值的一部分,具体取决于其位置/环境。跨度>
      【解决方案4】:

      如果您使用 .NET,我推荐 HTML 敏捷包,即使是格式错误的 HTML 也非常强大。

      然后你可以使用 XPath。

      【讨论】:

        【解决方案5】:

        2021 年更新:Radon8472 在the comments 中提出正则表达式https://regex101.com/r/tOF6eA/1(注意regex101.com 在我最初写这个答案时不存在)

        <a[^>]*?href=(["\'])?((?:.(?!\1|>))*.?)\1?
        

        更新 2021 之二:Dave 在the comments 中提出,以考虑包含等号的属性值,如&lt;img src="test.png?test=val" /&gt;,如this regex101:

        (\w+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?
        

        更新(2020 年),Gyum Fox 提议 https://regex101.com/r/U9Yqqg/2(再次注意,当我最初写这个答案时,regex101.com 并不存在)

        (\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?
        

        适用于:

        <a href=test.html class=xyz>
        <a href="test.html" class="xyz">
        <a href='test.html' class="xyz">
        <script type="text/javascript" defer async id="something" onload="alert('hello');"></script>
        <img src="test.png">
        <img src="a test.png">
        <img src=test.png />
        <img src=a test.png />
        <img src=test.png >
        <img src=a test.png >
        <img src=test.png alt=crap >
        <img src=a test.png alt=crap >
        

        原始答案(2008 年): 如果你有一个像

        这样的元素
        <name attribute=value attribute="value" attribute='value'>
        

        此正则表达式可用于连续查找每个属性名称和值

        (\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?
        

        适用于:

        <a href=test.html class=xyz>
        <a href="test.html" class="xyz">
        <a href='test.html' class="xyz">
        

        它会产生:

        'href' => 'test.html'
        'class' => 'xyz'
        

        注意:这不适用于数字属性值,例如&lt;div id="1"&gt; 不起作用。

        已编辑:改进了正则表达式,用于获取没有值的属性和内部带有“'”的值。

        ([^\r\n\t\f\v= '"]+)(?:=(["'])?((?:.(?!\2?\s+(?:\S+)=|\2))+.)\2?)?
        

        适用于:

        <script type="text/javascript" defer async id="something" onload="alert('hello');"></script>
        

        它会产生:

        'type' => 'text/javascript'
        'defer' => ''
        'async' => ''
        'id' => 'something'
        'onload' => 'alert(\'hello\');'
        

        【讨论】:

        • “foo="bar' bar='bla"”怎么样?
        • @Gumbo:这个正则表达式应该考虑单引号或双引号,因为它使用了字符类 ['"]
        • 它当然不能管理属性值中的引号
        • 我知道。但 foo 的值将是“bar' bar='bla” 而不仅仅是“bar”。
        • @PauloCosta 你可以在regex101.com/r/bY3kM1/1 看到正则表达式:它会为你解压正则表达式。
        【解决方案6】:

        我会重新考虑只使用一个正则表达式的策略。当然,想出一个可以完成所有工作的正则表达式是一款不错的游戏。但就可维护性而言,您将两脚相向。

        【讨论】:

          【解决方案7】:

          只是同意其他人的观点:不要使用正则表达式解析 HTML。

          创建一个表达式来挑选出正确的 HTML 的属性是不可能的,更不用说所有可能的格式错误的变体了。即使没有尝试处理无效的引号缺失,您的正则表达式已经非常难以理解;进一步追寻现实世界 HTML 的恐怖,你会被一大堆无法维护的不可靠表达式逼疯。

          现有的库可以读取损坏的 HTML,或者将其更正为有效的 XHTML,然后您可以使用 XML 解析器轻松吞噬这些库。使用它们。

          【讨论】:

            【解决方案8】:

            虽然不通过正则表达式解析 HTML 的建议是有效的,但这里的表达式几乎可以满足您的要求:

            /
               \G                     # start where the last match left off
               (?>                    # begin non-backtracking expression
                   .*?                # *anything* until...
                   <[Aa]\b            # an anchor tag
                )??                   # but look ahead to see that the rest of the expression
                                      #    does not match.
                \s+                   # at least one space
                ( \p{Alpha}           # Our first capture, starting with one alpha
                  \p{Alnum}*          # followed by any number of alphanumeric characters
                )                     # end capture #1
                (?: \s* = \s*         # a group starting with a '=', possibly surrounded by spaces.
                    (?: (['"])        # capture a single quote character
                        (.*?)         # anything else
                         \2           # which ever quote character we captured before
                    |   ( [^>\s'"]+ ) # any number of non-( '>', space, quote ) chars
                    )                 # end group
                 )?                   # attribute value was optional
            /msx;
            

            “但是等等,”你可能会说。 “那*cmets呢?!?!”好的,那么您可以将非回溯部分中的. 替换为:(它还处理 CDATA 部分。)

            (?:[^<]|<[^!]|<![^-\[]|<!\[(?!CDATA)|<!\[CDATA\[.*?\]\]>|<!--(?:[^-]|-[^-])*-->)
            
            • 此外,如果您想在 Perl 5.10 下运行替换(我认为是 PCRE),您可以将 \K 放在属性名称之前,而不必担心捕获所有您想跳过的内容。

            【讨论】:

            • 未引用的值 "( [^>\s'"]+ )" 将失败,因为 在值中包含 '/'。它可能应该类似于(未经测试)"(.*?)(?:\s|>|/>|'|") # 字符到第一个空格,>,/>,引号"
            • 正是我要找的东西——我需要删除一堆属性(相同的属性,不同的值”和属性=“。*?”在我的情况下成功了……非常方便参考,谢谢@mheyman
            【解决方案9】:

            您不能对多个捕获使用相同的名称。因此,您不能在具有命名捕获的表达式上使用量词。

            所以要么不要使用命名捕获:

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

            或者不要在这个表达式上使用量词:

            (?<name>\b\w+\b)\s*=\s*(?<value>"[^"]*"|'[^']*'|[^"'<>\s]+)
            

            这也允许像bar=' baz='quux这样的属性值:

            foo="bar=' baz='quux"
            

            那么缺点是你必须在之后去掉前导引号和尾引号。

            【讨论】:

            • 比我的正则表达式精确得多。 +1。注意为什么 [^\s] 而 [^\s] 就足够了?
            【解决方案10】:

            这样的事情可能会有所帮助

            '(\S+)\s*?=\s*([\'"])(.*?|)\2
            

            【讨论】:

              【解决方案11】:

              提取元素:

              var buttonMatcherRegExp=/<a[\s\S]*?>[\s\S]*?<\/a>/;
              htmlStr=string.match( buttonMatcherRegExp )[0]
              

              然后使用jQuery解析并提取你想要的位:

              $(htmlStr).attr('style') 
              

              【讨论】:

                【解决方案12】:

                我也需要这个,写了一个解析属性的函数,你可以从这里获取:

                https://gist.github.com/4153580

                (注意:它不使用正则表达式)

                【讨论】:

                • 嘿 Furkan,正则表达式解决方案可能最适合这种情况,因为它更快:) 也请参阅我的答案
                • 我也认为正则表达式应该更好,但不想用正则表达式处理细节,比如像这个 value="Tester's Device" 这样的属性,那个单一的 qoute 会把东西和简单的东西混淆正则表达式模式,甚至有时值周围没有引号。我以一种万无一失的方式做到了。如果它在 C 中,它也会比正则表达式更快,但我不能对 php 说同样的话。
                【解决方案13】:

                飞溅,

                @VonC 解决方案部分有效,但如果标签混合了未引用和引用,则会出现一些问题

                这个适用于混合属性

                $pat_attributes = "(\S+)=(\"|'| |)(.*)(\"|'| |>)"
                

                测试一下

                <?php
                $pat_attributes = "(\S+)=(\"|'| |)(.*)(\"|'| |>)"
                
                $code = '    <IMG title=09.jpg alt=09.jpg src="http://example.com.jpg?v=185579" border=0 mce_src="example.com.jpg?v=185579"
                    ';
                
                preg_match_all( "@$pat_attributes@isU", $code, $ms);
                var_dump( $ms );
                
                $code = '
                <a href=test.html class=xyz>
                <a href="test.html" class="xyz">
                <a href=\'test.html\' class="xyz">
                <img src="http://"/>      ';
                
                preg_match_all( "@$pat_attributes@isU", $code, $ms);
                
                var_dump( $ms );
                

                $ms 将包含第二个和第三个元素的键和值。

                $keys = $ms[1];
                $values = $ms[2];
                

                【讨论】:

                • 这似乎确实是一个更强大的解决方案。 +1
                • 不错。但是,我尝试加入如下: array_combine($ms[1],$ms[3]) (3 而不是 2)
                【解决方案14】:

                我创建了一个PHP function,它可以提取任何 HTML 标记的属性。它还可以处理像disabled这样没有值的属性,也可以通过检查content结果来判断标签是独立标签(没有结束标签)还是不是(有结束标签):

                /*! Based on <https://github.com/mecha-cms/cms/blob/master/system/kernel/converter.php> */
                function extract_html_attributes($input) {
                    if( ! preg_match('#^(<)([a-z0-9\-._:]+)((\s)+(.*?))?((>)([\s\S]*?)((<)\/\2(>))|(\s)*\/?(>))$#im', $input, $matches)) return false;
                    $matches[5] = preg_replace('#(^|(\s)+)([a-z0-9\-]+)(=)(")(")#i', '$1$2$3$4$5<attr:value>$6', $matches[5]);
                    $results = array(
                        'element' => $matches[2],
                        'attributes' => null,
                        'content' => isset($matches[8]) && $matches[9] == '</' . $matches[2] . '>' ? $matches[8] : null
                    );
                    if(preg_match_all('#([a-z0-9\-]+)((=)(")(.*?)("))?(?:(\s)|$)#i', $matches[5], $attrs)) {
                        $results['attributes'] = array();
                        foreach($attrs[1] as $i => $attr) {
                            $results['attributes'][$attr] = isset($attrs[5][$i]) && ! empty($attrs[5][$i]) ? ($attrs[5][$i] != '<attr:value>' ? $attrs[5][$i] : "") : $attr;
                        }
                    }
                    return $results;
                }
                

                测试代码

                $test = array(
                    '<div class="foo" id="bar" data-test="1000">',
                    '<div>',
                    '<div class="foo" id="bar" data-test="1000">test content</div>',
                    '<div>test content</div>',
                    '<div>test content</span>',
                    '<div>test content',
                    '<div></div>',
                    '<div class="foo" id="bar" data-test="1000"/>',
                    '<div class="foo" id="bar" data-test="1000" />',
                    '< div  class="foo"     id="bar"   data-test="1000"       />',
                    '<div class id data-test>',
                    '<id="foo" data-test="1000">',
                    '<id data-test>',
                    '<select name="foo" id="bar" empty-value-test="" selected disabled><option value="1">Option 1</option></select>'
                );
                
                foreach($test as $t) {
                    var_dump($t, extract_html_attributes($t));
                    echo '<hr>';
                }
                

                【讨论】:

                • 不检测文档类型标签
                【解决方案15】:

                看看这个 Regex & PHP - isolate src attribute from img tag

                也许您可以遍历 DOM 并获得所需的属性。它对我来说很好,从 body-tag 获取属性

                【讨论】:

                  【解决方案16】:

                  PHP (PCRE) 和 Python

                  简单的属性提取(See it working):

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

                  或带有标签打开/关闭验证、标签名称检索和评论转义。这个表达式预见到不带引号的/带引号的、单引号/双引号、属性内的转义引号、等号周围的空格、不同数量的属性、仅检查标签内的属性以及管理属性值内的不同引号。 (See it working):

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

                  (使用“gisx”标志效果更好。)


                  Javascript

                  由于Javascript 正则表达式不支持look-behinds,它不支持我建议的之前表达式的大部分功能。但如果它可能适合某人的需要,你可以试试这个版本。 (See it working)。

                  (\S+)=[\'"]?((?:(?!\/>|>|"|\'|\s).)+)
                  

                  【讨论】:

                  • 您的正则表达式提取不准确。查看来自测试用例分支的更新示例:regex101.com/r/y3DOf5/1
                  • 是的,没有办法根据检查前一个字符的出现进行条件搜索(在这种情况下是单引号/双引号)。不是使用单个正则表达式。这就是为什么解析器是要走的路。不幸的是,这个正则表达式只是近似值。
                  • 实际上,只能使用正则表达式使用前瞻和后瞻组来创建条件搜索。当我找到更多时间时,我会尽快发布一个正确的答案。
                  • 感谢您的提示,@SlavikMeltser!我用一些回顾更新了我的答案。
                  • @choise 真的!我为 JS 添加了一个简化的表达式。
                  【解决方案17】:

                  这对我有用。它还考虑了我遇到的一些最终情况。

                  我将这个正则表达式用于 XML 解析器

                  (?<=\s)[^><:\s]*=*(?=[>,\s])
                  

                  【讨论】:

                    【解决方案18】:

                    这是我在 HTML 标签中提取属性的最佳正则表达式:

                    # 修剪引号内的匹配项(单引号或双引号)

                    (\S+)\s*=\s*([']|["])\s*([\W\w]*?)\s*\2
                    

                    # 没有修剪

                    (\S+)\s*=\s*([']|["])([\W\w]*?)\2
                    

                    优点:

                    • 您可以修剪引号内的内容。
                    • 匹配引号内的所有特殊 ASCII 字符。
                    • 如果您有 title="You're mine",则 RegEx 不会损坏

                    缺点:

                    • 返回3组;首先是属性,然后是引号 ("|'),最后是引号内的属性,即:&lt;div title="You're"&gt; 结果是第 1 组:标题,第 2 组:",第 3 组:你是。

                    这是在线 RegEx 示例: https://regex101.com/r/aVz4uG/13



                    我通常使用这个 RegEx 来提取 HTML 标签:

                    如果您不使用 &lt;div、&lt;span 等标签类型,我建议您这样做。

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

                    例如:

                    <div title="a>b=c<d" data-type='a>b=c<d'>Hello</div>
                    <span style="color: >=<red">Nothing</span>
                    # Returns 
                    # <div title="a>b=c<d" data-type='a>b=c<d'>
                    # <span style="color: >=<red">
                    

                    这是在线 RegEx 示例: https://regex101.com/r/aVz4uG/15

                    这个正则表达式的错误是:

                    <div[^/]+?(?:\".*?\"|'.*?'|.*?)*?>
                    

                    在这个标签中:

                    <article title="a>b=c<d" data-type='a>b=c<div '>Hello</article>
                    

                    返回&lt;div '&gt;,但不应返回任何匹配项:

                    Match:  <div '>
                    

                    要“解决”这个问题,请删除 [^/]+? 模式:

                    <div(?:\".*?\"|'.*?'|.*?)*?>
                    


                    #317081 的答案很好,但与这些情况不匹配:

                    <div id="a"> # It returns "a instead of a
                    <div style=""> # It doesn't match instead of return only an empty property
                    <div title = "c"> # It not recognize the space between the equal (=)
                    

                    这是改进:

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

                    对

                    (\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?
                    

                    避免相等信号之间的空格: (\S+)\s*=\s*((?:...

                    更改最后一个 + 和 .为了: |[>"']))?[^"']*)["']?

                    这是在线 RegEx 示例: https://regex101.com/r/aVz4uG/8

                    【讨论】:

                      【解决方案19】:

                      HTML 中的标签和属性具有以下形式

                      <tag 
                         attrnovalue 
                         attrnoquote=bli 
                         attrdoublequote="blah 'blah'"
                         attrsinglequote='bloob "bloob"' >
                      

                      要匹配属性,您需要一个正则表达式attr 来查找四种形式之一。然后您需要确保仅在 HTML 标记中报告匹配项。假设您有正确的正则表达式,总的正则表达式将是:

                      attr(?=(attr)*\s*/?\s*>)
                      

                      前瞻确保只有其他属性和结束标记跟随该属性。我对attr 使用以下正则表达式:

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

                      不重要的组不被捕获。第一个匹配组$1 为您提供属性名称,其值为$2 或$3 或$4 之一。我使用$2$3$4 来提取值。 最终的正则表达式是

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

                      注意:我在前瞻中删除了所有不必要的组,并使所有剩余的组不被捕获。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-02-01
                        • 2011-07-28
                        • 2013-11-29
                        • 1970-01-01
                        • 1970-01-01
                        • 2010-12-17
                        • 2011-04-18
                        相关资源
                        最近更新 更多