0

这是我一直在做的一个有趣的小家伙。我找到了很多解决方案,但没有一个是真正合适的。目标是“仅当连续有 3 个或更多时才匹配 p 标签”

所以我觉得这应该是正确的,但事实并非如此。

<p.*>(.*)<\/p>(?=\s?<p){3,}

基本上,用我的话来说就是:

  • 将 ap 标签与标签内的任何内容匹配
  • 匹配任何内容,直到看到结束的 P 标记
  • 仅匹配前面的(2 行以上)当且后跟
    • 一个空格字符(可能),然后是一个 < p
    • 如果这种情况发生 3 次或更多次

问题是这在 Javascript 中运行良好,但在 PHP 中却不行。PHP 说

Compilation failed: nothing to repeat at offset 28

我尝试了不同轮次的括号来给它“​​没有重复”,但这会导致错误的正则表达式。

是的,这是用于网络抓取,但不,我正在做研究而不是做坏事。

有什么想法吗?谢谢!

4

3 回答 3

1

状态机 XML 解析器(SAX 解析器)似乎最适合我。这是一个例子:

class StateHelper {

    function __construct($filename) {
        $this->p_count = 0;
        $this->p_elements = array();
        $this->in_p = FALSE;
        $this->minimum_in_succession = 2;
        $this->successive_element_data = array();
        $parser = xml_parser_create();
        xml_set_element_handler($parser, array($this, 'start_element'), NULL);
        xml_set_character_data_handler($parser, array($this, 'character_data'));

        $fp = fopen($filename, 'r')
            or die ("Cannot open $filename");

        while ($data = fread($fp, 4096)) {
            xml_parse($parser, $data, feof($fp)) or 
                die(sprintf('XML ERROR: %s at line %d',
                xml_error_string(xml_get_error_code($parser)),
                xml_get_current_line_number($parser)));
        }
        xml_parser_free($parser);
        $this->start_element(NULL, "end", NULL);
    }

    function start_element($parser, $element_name, $element_attrs) {
        if ($element_name == 'P') {
            $this->p_count += 1;
            $this->in_p = TRUE;
        } else {
            if ($this->p_count >= $this->minimum_in_succession) {
                $this->successive_element_data[] = $this->p_elements;
            }
            $this->p_elements = array();
            $this->p_count = 0;
            $this->in_p = FALSE;
        }
    }

    function character_data($parser, $data) {
        if ($this->in_p && strlen(trim($data))) {
            $this->p_elements[] = $data;
        }
    }
}

$parseState = new StateHelper("example.html");
print_r($parseState->successive_element_data);

示例.html*

<html>
    <head>
    </head>
    <body>
        <p>Foo1</p>
        <p>Foo2</p>
        <p>Foo3</p>
        <div>
            <p>Bar1</p>
            <p>Bar2</p>
        </div>
        <ul>
            <li>
                <p>Baz1</p>
                <p>Baz2</p>
                <p>Baz3</p>
                <p>Baz4</p>
            </li>
        </ul>
    </body>
</html>

输出

Array
(
    [0] => Array
        (
            [0] => Foo1
            [1] => Foo2
            [2] => Foo3
        )

    [1] => Array
        (
            [0] => Baz1
            [1] => Baz2
            [2] => Baz3
            [3] => Baz4
        )

)
于 2012-08-28T05:00:40.920 回答
0

为什么不改用XPath?那么表达式就是:

//p[name(following-sibling::*[1]) = 'p' and name(following-sibling::*[2]) = 'p']

该查询将查找p文档中所有p紧随其后的两个位置。

示例(演示):

$html = <<< HTML
<div>
    <p>lore</p>
    <p>ipsum</p>
    <p>dolor</p>
    <br/>
    <p>sit</p>
    <p>amet</p> 
</div>
HTML;

我们只想找到这个片段中的第一个元素。代码将是:

$query = "//p[
    name(following-sibling::*[1]) = 'p' and 
    name(following-sibling::*[2]) = 'p'
]";

print_r(xpath_match_all($query, $html));

输出:

Array(
    [0] => Array(
        [0] => <p>lore</p>
    )
    [1] => Array(
        [0] => lore
    )
)

结果数组包含该查询的outerHTML 和innerHTML。

当然,您不必使用该xpath_match_all功能。它只是一个便利实用程序。有关替代方法,请参阅如何在 PHP 中解析和处理 HTML/XML?

于 2012-08-28T07:25:34.240 回答
0

PHP 可能会给你这个错误,因为你的零宽度断言重复是没用的,perl 和 javascript 都不会警告你。

如果你匹配一次,你可以匹配任意多次,因为它实际上不会消耗任何东西。

根据您打算做什么,您可能能够摆脱正则表达式。但是,如果您需要以任何方式真正了解您的 HTML,您最好使用 HTML 解析库。

你需要做什么?

于 2012-08-28T05:11:24.353 回答