我怎么告诉preg_match_all()可能期望,但忽略某些字符串?

时间:2013-07-31 13:07:15

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

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

在抓取网站时,我正在拉一些格式类似于:

的链接
<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 =”,这将使我的正则表达式失效。所以我想告诉它“这可能在这里,但不匹配,只是忽略它”。

4 个答案:

答案 0 :(得分:3)

请记住,使用正则表达式解析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;

这种模式允许以下几点:

  • 它不关心标签

  • 中的属性顺序或数量
  • 它不关心键/值对的数量(它需要全部)

  • 它忽略了网址

  • 中没有键/值的标记
  • 它允许空格(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);   

答案 1 :(得分:1)

描述

这个正则表达式将:

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

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

enter image description here

实施例

Live demo

示例文字

请注意mouseover attibute中相当困难的边缘情况

<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组获取报价,然后在内部使用该报价以确保使用正确的报价来关闭href值
组2-4从var1,var2和var3中获取值 第5组获取<a...> ... </a>

之间的内部字符串
[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

答案 2 :(得分:0)

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

您的正则表达式应更改为

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

OR

'/<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>'
                                                                 ^^^^^^^^^

答案 3 :(得分:0)

我认为这应该有效:

^<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>