【问题标题】:Pattern optimization模式优化
【发布时间】:2011-12-26 12:56:26
【问题描述】:

我需要使用 Java 从 HTTP 响应中抓取一些内容。响应中的必填字段为:foo、bar 和 bla。我目前的模式很慢。有什么想法可以改进吗?

回复:

...
<div class="ui-a">
<div class="ui-b">
    <p><strong>foo</strong></p>
    <p>bar</p>
</div>
<div class="ui-c">
    <p><strong>bla</strong></p>
    <p>...</p>
</div>
</div>

<div class="ui-a">
<div class="ui-b">
    <p><strong>foo1</strong></p>
    <p>bar1</p>
</div>
<div class="ui-c">
    <p><strong>bla1</strong></p>
    <p>...</p>
</div>

图案:

.*?<div class="ui-a">.*?<strong>(.*?)</strong>.*?<p>(.*?)</p>.*?</div>.*?<div class="ui-c">.*?<strong>(.*?)</strong>.*?

【问题讨论】:

  • XPath 表达式是否可行?
  • 您是否尝试过常规的字符串搜索(indexOf 等)?
  • 此外,如果您无法控制正在解析的 HTML,则标签中可能会出现换行符和空格,这将使您的 Pattern 无法匹配。见jsfiddle.net/qhAPa

标签: java regex performance optimization screen-scraping


【解决方案1】:

由于您无法使用 HTML 解析器,请尝试以下方法:

import java.util.regex.*;

public class Main {
    public static void main (String[] args) {
        String html =
                "...\n" +
                "<div class=\"ui-a\">\n" +
                "<div class=\"ui-b\">\n" +
                "    <p><strong>foo</strong></p>\n" +
                "    <p>bar</p>\n" +
                "</div>\n" +
                "<div class=\"ui-c\">\n" +
                "    <p><strong>bla</strong></p>\n" +
                "    <p>...</p>\n" +
                "</div>\n" +
                "</div>\n" +
                "\n" +
                "<div class=\"ui-a\">\n" +
                "<div class=\"ui-b\">\n" +
                "    <p><strong>foo1</strong></p>\n" +
                "    <p>bar1</p>\n" +
                "</div>\n" +
                "<div class=\"ui-c\">\n" +
                "    <p><strong>bla1</strong></p>\n" +
                "    <p>...</p>\n" +
                "</div>";

        Pattern p = Pattern.compile(
                "(?sx)                               # enable DOT-ALL and COMMENTS     \n" +
                "<div\\s+class=\"ui-a\">             # match '<div...ui-a...>'         \n" +
                "(?:(?!<strong>).)*+                 # match everything up to <strong> \n" +
                "<strong>([^<>]++)</strong>          # match <strong>...</strong>      \n" +
                "(?:(?!<p>).)*+                      # match up to <p>                 \n" +
                "<p>([^<>]++)</p>                    # match <p>...</p>                \n" +
                "(?:(?!<div\\s+class=\"ui-c\">).)*+  # match up to '<div...ui-a...>'   \n" +
                "<div\\s+class=\"ui-c\">             # match '<div...ui-c...>'         \n" +
                "(?:(?!<strong>).)*+                 # match everything up to <strong> \n" +
                "<strong>([^<>]++)</strong>          # match <strong>...</strong>      \n"
        );

        Matcher m = p.matcher(html);

        while(m.find()) {
            System.out.println("---------------");
            for(int i = 1; i <= m.groupCount(); i++) {
                System.out.printf("group(%d) = %s\n", i, m.group(i));
            }
        }
    }
}

这会将以下内容打印到控制台:

---------------
组(1)= foo
组(2)=酒吧
组(3)= bla
---------------
组(1)= foo1
组(2)= bar1
组(3)= bla1

注意我的更改:

  • *+++: http://www.regular-expressions.info/possessive.html
  • 我使用了(?:(?!...).)*+,而不是.*?。第一个,.*? 将跟踪它所做的所有可能的匹配,以便能够在稍后阶段回溯。后者 (?:(?!...).)*+ 不会跟踪这些匹配。

这应该会更快(不确定多少......)。

【讨论】:

  • 完美,你的图案真的很棒。之前的执行时间是 200 毫秒,现在是 35 毫秒。
  • 在最后两行你写的模式:"(?:(?!).)*+"([^]++)" .所以我在最后一个强标签中有一个定义的后缀。知道为什么这个改变不起作用:“(?:(?!).)*+”([^]++)suffix "
  • @CannyDuck,因为[^&lt;&gt;]++是所有格,一旦匹配后缀,就永远不会回溯,导致模式失败。如果你有一个大后缀,匹配 un-greedy:([^&lt;&gt;]+?)suffix,如果它是一个小后缀,匹配贪婪:([^&lt;&gt;]+)suffix(但不是所有格!所以去掉多余的+)。花点时间阅读regular-expressions.info/possessive.html,它比我更好地解释了所有格匹配的机制(尤其是在这些小评论框中!):)
  • @CannyDuck,哦,所有出现的\\s+都可以替换为\\s++
【解决方案2】:

看来,您正在寻找的只是标签之间,您可以使用:

<strong>([a-zA-Z0-9]+)</strong>

此外,根据strong标签内的内容,您可以更改模式,例如如果您确定文本总是小写,您可以从上述模式中删除 A-Z,或者如果它仅包含 4 个字符,您可以在模式后使用 {4}。

【讨论】:

  • 匹配速度较慢,如果使用您的建议。我试过了:我的模式(200ms)和你一起修改(300ms)
【解决方案3】:

你所有的字符串都放入&lt;p&gt;标签中,这样你就可以搜索它包含的内容(并删除&lt;strong&gt;)。 但是如果你使用解析器而不是正则表达式可能会更好。 Search all &lt;p&gt;; If &lt;p&gt; has childNode then get &lt;p&gt;.text; else get &lt;p&gt;.text.

【讨论】:

    【解决方案4】:

    考虑改用JSoup。有一些well-known problems 使用正则表达式来解析 HTML。

    【讨论】:

    • 好主意,但在我的情况下是不可能的,因为对于 android 项目来说,lib 太大了。
    【解决方案5】:

    如果您不依赖正则表达式来验证 html,并且您无权修改 html 的结构。此外,删除最后一个 .*? 是必要的,因为第一个将与后续匹配发生冲突。本质上,您拥有.*?.*?,因为引擎将尝试查找最后一个&lt;strong&gt; 标记和下一个&lt;div class="ui-a"&gt; 标记之间所有字符的所有可能排列。非常低效。试试这个:

    .*?<div class="ui-a">.*?<strong>(.*?)</strong>.*?<p>(.*?)</p>.*?</div>.*?<div class="ui-c">.*?<strong>(.*?)</strong>
    

    附带说明:您确定要在&lt;div class="ui-a"&gt; 内找到第一个&lt;strong&gt; 标记吗,因为第一个&lt;strong&gt; 标记似乎出现在&lt;div class="ui-b"&gt; 内,在这种情况下:

    .*?<div class="ui-b">.*?<strong>(.*?)</strong>.*?<p>(.*?)</p>.*?</div>.*?<div class="ui-c">.*?<strong>(.*?)</strong>
    

    更准确。

    如果你知道你想要的捕获组中没有嵌套标签,你可以进一步优化它:

    .*?<div class="ui-b">.*?<strong>([^<]*)</strong>.*?<p>([^<]*)</p>.*?</div>.*?<div class="ui-c">.*?<strong>([^<]*)</strong>
    

    【讨论】:

    • 感谢您的建议。我试过你所有的版本。 #3 是禁食的,但我认为还有更多的优化空间,你的实现速度不到 10 %。
    【解决方案6】:

    您的正则表达式既有领先又有训练。*?我不明白为什么。如果数据格式正确,你真的只是指一定数量的空格,是吗?为什么不呢:

    Pattern p = "<div class=\"ui-b\">\s*<p><strong>([^<]*)</strong></p>\s*<p>([^<]*)</p>\s*</div>\s*<div class=\"ui-c\">\s*<p><strong>([^<]*)</strong></p>";
    Matcher m = p.matcher(responseText);
    
    while (m.find()) {
       String foo = m.group(1);
       String bar = m.group(2);
       String bla = m.group(3);
    
       /* do whatever w/ foo, bar, bla */
    }
    

    我已经放弃了你所有的.*?

    并用空格替换了内部的空格(或者还有更多你要遗漏的地方——也许)。但不管怎样,你为什么需要开头和结尾 .*?

    如果格式正确,只进行空格搜索应该会大大增加它。

    【讨论】:

      猜你喜欢
      • 2011-01-04
      • 1970-01-01
      • 2019-05-17
      • 2019-01-10
      • 1970-01-01
      • 2012-03-08
      • 2018-08-10
      • 1970-01-01
      • 2023-03-06
      相关资源
      最近更新 更多