【问题标题】:How do I tell preg_match_all() to possibly expect, but ignore certain strings?我如何告诉 preg_match_all() 可能期望,但忽略某些字符串?
【发布时间】:2013-07-31 13:07:15
【问题描述】:

我当然遗漏了一些明显的东西,因为这非常简单,但这是我遇到的问题。

在抓取网站时,我会提取一些格式类似于以下内容的链接:

<a href="/test.php?var1=123&var2=456&var3=789">SomeString</a>

虽然有时一些链接看起来像:

<a href="/test.php?var1=123&var2=456&var3=789" title="sometitle">SomeString</a>

使用像下面这样的正则表达式有效,但它添加了我不想要的匹配:

'/<a href=\"/test.php\?var1=([0-9]+)&var2=([0-9]+)&var3=([0-9]+)\"(^.*?)>(^.*?)<\/a>'

所以很明显我在这里得到了一个我不想要的匹配,但它确实考虑了“标题”变量出现的可能性。有没有办法告诉 preg_match_all() “这里可能有东西,但你应该忽略它”或者如果它找到它,它匹配它?

谢谢。

编辑,因为不清楚:

在我的原始示例中,我想找出 var1、var2、var3 等于什么,以及和之间的文本(在我的示例中为 SomeString)。如果我正在浏览一个有很多结果的页面,有时字符串中会包含“title=”,这会使我的正则表达式失效。所以我想告诉它“这可能在这里,但不匹配,忽略它”。

【问题讨论】:

  • 如果您不想使用极其复杂的正则表达式,您可能会发现简单地将所有 title="sometitle" 实例替换为 ""(无)会更简单。
  • 你能说出你想用正则表达式做什么吗?你不想得到数字结果?
  • 不要使用正则表达式解析 HTML。使用适当的 HTML 解析模块。 您无法使用正则表达式可靠地解析 HTML,并且您将面临悲伤和挫败感。一旦 HTML 与您的期望发生变化,您的代码就会被破坏。有关如何使用已经编写、测试和调试的 PHP 模块正确解析 HTML 的示例,请参阅 htmlparsing.com/php
  • 这可能不是正则表达式的工作,而是你选择的语言的现有工具。正则表达式不是你在碰巧涉及字符串的每个问题上挥舞的魔杖.您可能希望使用已经编写、测试和调试过的现有代码。在 PHP 中,使用 parse_url 函数。 Perl:URI module。红宝石:URI module。 .NET:'Uri' class
  • @AndyLester: parse_url 不适用于相对网址。

标签: php regex html-parsing preg-match-all


【解决方案1】:

请记住,使用正则表达式解析 html 并不是最好的方法,您可以使用这种更便携的解决方案:

$pattern = <<<'LOD'
~
(?:                       # open a non-capturing group
    <a\s                  # begining of the a tag
    (?:                   # open a non capturing group
        [^h>]+            # all characters but "h" and "<" one or more times
      |                   # OR
        \Bh+              # one or more "h" not preceded by a word bundary
      |                   # OR
        h(?!ref\b)        # "h" not followed by "ref"
    )*+                   # repeat the group zero or more times
    href\s*=\s*"[^?]+\?   # href with the begining of the link until the "?"
    \K                    # reset all the match (this part is not needed)
  |                       # OR
    \G(?!\A)              # a contiguous match
)                         # close the non-capturing group
(?:                       # open a non capturing group
    (?<key>[^=&]++)       # take the key
    =                     # until the "="
    (?<value>[^&"]++)     # take the value
    (?: & | (?=") )       # a "&" or followed by a double quote
  |                       # OR
    "[^>]*>               # a double quote and the end of the opening tag
    (?<content>           # open the content named capturing group
        (?:               # open a non capturing group
            [^<]+         # all characters but "<" one or more times
          |               # OR
            <(?!/a\b)     # a "<" not followed by "/a" (the closing a tag)
        )*+               # repeat the group zero or more times
    )                     # close the named capturing group
    </a>                  # the closing tag (can be removed)
)                         # close the non-capturing group
~xi
LOD;

这种模式允许做几件事:

  • 它不关心a标签中属性的顺序或数量

  • 它不关心键/值对的数量(它需要全部)

  • 它会忽略 url 中没有键/值的标签

  • 这里允许空格(href = "

  • 在内容部分支持html标签

但是提取结果有点困难:

preg_match_all($pattern, $subject, $matches);

foreach($matches['key'] as $k => $v) {
    if (empty($v)) {
        $result[] = array('values'  => $keyval,
                          'content' => $matches['content'][$k]);
        unset($keyval);
    } else {
        $keyval[] = array($v => $matches['value'][$k]);
    }
}
print_r($result);

DOM 方式

这种方式的主要兴趣在于 DOM 解析器具有与浏览器(也是解析器)相似的行为,因为它不关心属性的数量或位置,简单、双精度或否引号,以及标签之间的内容类型。

$doc = new DOMDocument();
@$doc->loadHTML($yourhtml);
$linkNodeList = $doc->getElementsByTagName("a");

foreach($linkNodeList as $linkNode) {
    if (preg_match('~var1=(?<var1>\d+)&var2=(?<var2>\d+)&var3=(?<var3>\d+)~i',
                   $linkNode->getAttribute('href'), $match)) {
       foreach($match as $k => &$v) {
           if (is_numeric($k)) unset($v);
       }
       // take the content between "a" tags
       $content= ''; 
       $children = $linkNode->childNodes; 
       foreach ($children as $child) { 
           $content .= $child->ownerDocument->saveXML( $child ); 
       }

       $result[] = array('values' => $match, 'content' => $content);
    }
}

print_r($result);   

【讨论】:

    【解决方案2】:

    说明

    这个正则表达式将:

    • 找到合适的href值
    • 允许href值被单引号或双引号,或者根本不被引用
    • 提取 var1、var2 和 var3 的查询字符串值
    • 避免锚标记中的任何其他属性
    • 允许属性以任意顺序出现
    • 避免使用正则表达式难以匹配 html 文本的困难边缘情况

    &lt;a\b(?=\s)(?=(?:[^&gt;=]|='[^']*'|="[^"]*"|=[^'"][^\s&gt;]*)*?\shref=(['"]?)\/test.php\?var1=([0-9]+)&amp;var2=([0-9]+)&amp;var3=([0-9]+)\1(?:\s|\/&gt;|&gt;))(?:[^&gt;=]|='[^']*'|="[^"]*"|=[^'"][^\s&gt;]*)*&gt;(.*?)&lt;\/a&gt;

    示例

    Live demo

    示例文本

    注意鼠标悬停属性中相当困难的边缘情况

    &lt;a onmouseover=' href="/test.php?var1=666&amp;var2=666&amp;var3=666" ; if ( 6 &gt; a ) { funRotate(href) } ; ' href="/test.php?var1=123&amp;var2=456&amp;var3=789" title="sometitle"&gt;SomeString&lt;/a&gt;

    匹配项

    组 0 获取从打开到关闭的整个标签
    第 1 组获取报价,然后在内部使用以确保使用正确的报价来关闭 href 值
    第 2-4 组从 var1、var2 和 var3 中获取值
    第 5 组获取 &lt;a...&gt;...&lt;/a&gt; 之间的内部字符串

    [0][0] = <a onmouseover=' href="/test.php?var1=666&var2=666&var3=666" ; if ( 6 > a ) { funRotate(href) } ; ' href="/test.php?var1=123&var2=456&var3=789" title="sometitle">SomeString</a>
    [0][1] = "
    [0][2] = 123
    [0][3] = 456
    [0][4] = 789
    [0][5] = SomeString
    

    【讨论】:

      【解决方案3】:

      您可以使用?* 字符。 ? 被称为非贪婪,但我认为它只是“可选”的。 * 匹配“零个或多个”。

      您的正则表达式应更改为

      '/<a href=\"/test.php?var1=([0-9]+)&var2=([0-9]+)&var3=([0-9]+)\"(^.*?)?>(^.*?)<\/a>'
                                                                             ^
      

      '/<a href=\"/test.php?var1=([0-9]+)&var2=([0-9]+)&var3=([0-9]+)\"(^.*?)*>(^.*?)<\/a>'
                                                                             ^
      

      如果您不想对title="something" 进行分组,则可以通过在正则表达式中使用(?:) 来避免捕获。所以

      '/<a href=\"/test.php?var1=([0-9]+)&var2=([0-9]+)&var3=([0-9]+)\"(?:^.*?)*>(^.*?)<\/a>'
                                                                       ^^^^^^^^^
      

      【讨论】:

      • 这几乎奏效了,但有一点需要注意。 (?:.*?) 完美运行,并且在违规行上找到了该字符串,并被 preg_match_all 忽略。 (?:^.*?) 和 (?:^.*?)* 都失败了。
      • 抱歉,return 添加评论而不是换行。
      【解决方案4】:

      我认为这应该可行:

      ^<a\shref=\"\/test.php\?var1=([0-9]+)&var2=([0-9]+)&var3=([0-9]+)\"(?:.*?)>(.*)?<\/a>$
      

      问号应该在正则表达式中转义...否则匹配

       <a href="/test.phvar1=123&var2=456&var3=789">SomeString</a>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-25
        • 2019-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多