正则表达式是寻找HTML行的正确工具吗?

4

我有一个PHP脚本,它从服务器上提取一些内容,但问题是包含内容的行每天都会变化,所以我不能只提取特定的行。然而,这个内容包含在一个具有唯一id的div中。使用正则表达式搜索这个唯一id,然后将其所在的行传回我的脚本是否可行(并且是否是最佳方法)?

示例:

HTML文件:

<html><head><title>Example</title></head>
<body>
<div id="Alpha"> Blah blah blah </div>
<div id="Beta"> Blah Blah Blah </div>
</body>
</html>

假设我正在寻找具有id为alpha的开放div标签。代码应该返回3,因为第三行是具有id为alpha的div。


3
展示一下你的HTML长什么样子,以及你希望得到什么结果。 - ghostdog74
1
我同意ghostdog74的观点。给我们展示一些代码,我们可以帮你看看。我有一些使用正则表达式创建Amazon图书信息解析器的经验,所以我可能能够提供一些技巧。 - NawaMan
7个回答

3
一般情况下,不建议使用正则表达式。但是如果您确定该div始终只有一行或没有另一个div在其中,那么您可以毫无问题地使用它。类似于/<div id=\"mydivid\">(.*?)</div>/或类似的内容。
否则,更明智的方法是使用DOMDocument编辑:从您的HTML示例中可以看出,我的答案是"可以"。正则表达式是一个非常好的工具。
我假设您将HTML作为连续文本而不是行(这将略有不同)。我还假设您想要行号而不是行内容。
以下是提取它的PHP代码(仅供参考)。
$HTML =
"<html><head><title>Example</title></head>
<body>
<div id=\"Alpha\"> Blah blah blah </div>
<div id=\"Beta\"> Blah Blah Blah </div>
</body>
</html>";

$ID = "Alpha";

function GetLineOfDIV($HTML, $ID) {
    $RegEx_Alpha = '/\n(<div id="'.$ID.'">.*?<\/div>)\n/m';
    $Index       = preg_match($RegEx_Alpha, $HTML, $Match, PREG_OFFSET_CAPTURE);
    $Match       = $Match[1]; // Only the one in '(...)'
    if ($Match == "")
        return -1;

    //$MatchStr    = $Match[0]; Since you do not want it, so we comment it out.
    $MatchOffset = $Match[1];

    $StartLines = preg_split("/\n/", $HTML, -1, PREG_SPLIT_OFFSET_CAPTURE);
    foreach($StartLines as $I => $StartLine) {
        $LineOffset = $StartLine[1];
        if ($MatchOffset <= $LineOffset)
            return $I + 1;
    }
    return count($StartLines);
}

echo GetLineOfDIV($HTML, $ID);

I hope I give you some idea.


3

5
呃,那篇文章完全没有建筑理论来解释为什么正则表达式是一个坏主意。而且文章本身甚至说:“认为应该禁止使用正则表达式来处理HTML是一种好的方式,但我认为这与要求每个琐碎的HTML处理任务都由全面的解析引擎处理一样错误。” - poundifdef
4
重点是这是一个已解决的问题。你是否通过打开套接字并传递手动编写的字节数组在代码中进行原始HTTP提交?不会...即使对于“微不足道”的示例,也有库可以为你完成这个任务。使用正则表达式解析HTML是一种糟糕的方式,因此你很可能会因为某些输入而引入错误。使用一些现有的库很可能比你编写一个正确的正则表达式模式更快。 - Josh
正则表达式非常适合匹配在内存中加载整个文件后的单个标签<nodename\b[^<>]*>。然而,关闭该标签并获取整个元素是另一回事。 - ZJR
1
重点不在于正则表达式是否能被破解,而在于它是否以一种可能被破解的方式应用。例如,如果您有一些已知的 HTML 需要提取数据,那么它就可以正常工作。事实上,我昨天刚刚做了这件事——即使某些非常畸形的东西会破坏它,也没有关系,因为它不是用来筛选用户输入或未知数据集的,那里才是问题所在。从来没有绝对的从来。 - Elle H
@Iain Fraser:你提到的标签即使在有效的、格式良好的HTML上也绝对不安全,不能用正则表达式解析。一个<script>标签、CDATA或HTML注释在<form>或<body>标签内部可以有效地包含</form>或</body>并使一个天真的正则表达式出错。HTML解析问题包含了太多的边缘情况,无法优雅地通过正则表达式处理。更不要谈论那些非常真实的格式错误的HTML了。为了上帝的爱,人们,请使用HTML解析器! - Asaph
显示剩余7条评论

3
冒着给已经越过疯狂山脉的Jeff提供更多点赞的风险,请点击这里。争论来回反复,但是如果你只是编写一个简单的一次性或很少使用的脚本,那么可以使用正则表达式;如果它更加复杂,并且需要可靠性和很少的未来调整,则建议使用HTML解析器。 HTML是一种难以驯服的野兽。选择正确的工具来完成工作...也许在您的情况下是正则表达式,也可能是完整的解析器。

3
@beggs 我完全理解你的观点。仅凭正则表达式是无法创建完整的HTML解析器的。然而,在某些情况和条件下,HTML的某些部分可以被视为纯文本,这时候使用正则表达式就足够了。 - NawaMan
@NamaMan,同意。我是“适合工作的正确工具”的粉丝,而不是“对于一个拥有锤子的人,所有东西都像个钉子”。选择正确的工具需要更多项目知识,这在SO上我们可能无法获得,我们只能帮助指引方向。 - beggs

1

既然行号对你很重要,而不是 div 的实际内容,我倾向于根本不使用正则表达式。我可能会将字符串 explode() 成一个数组,并通过循环查找标记。就像这样:

<?php
$myContent = "[your string of html here]";
$myArray = explode("\n", $myContent);
$arraylen = count($myArray); // So you don't waste time counting the array at every loop
$lineNo = 0;
for($i = 0; $i < $arraylen; $i++)
{
     $pos = strpos($myArray[$i], 'id="Alpha"');
     if($pos !== false)
     {
          $lineNo = $i+1;
          break;
     }
}
?>

免责声明:我没有一个可用的PHP安装程序来测试这个,所以可能需要一些调试。

希望这可以帮助您,因为我认为实现解析引擎只是为了做一些如此简单的事情,特别是如果它只是一次性的话,那么这只会浪费您的时间。


编辑:如果内容对您现阶段也很重要,那么您可以结合其他答案提供的适当正则表达式来使用它。


编辑2:哦,算了吧...这是我的两分钱:

"/<div.*?id=\"Alpha\".*?>.*?(<div.*//div>)*.*?//div>/m"

在编程中,(<div.*//div>) 告诉正则表达式引擎可以找到嵌套的 div 标签,并将它们合并到匹配结果中,而不仅仅停在第一个 </div> 上。然而,这只解决了单层嵌套的问题。如果有更多层次的嵌套,那么正则表达式就无法胜任了,抱歉 :(。

/m 还让正则表达式引擎忽略换行符,因此你不必在表达式中到处添加 [\S\s]

再次抱歉,目前我没有测试环境,所以你可能需要进行调试。

祝好 Iain


1

不要使用正则表达式,而是使用专门处理(混乱的)HTML的解析器。这将使您的应用程序在HTML略微更改时变得不那么脆弱,并且您不必每次想要提取新数据块时手工制作自定义正则表达式。

请参阅此Stack Overflow页面:PHP成熟的HTML解析器


1

涉及到唯一的ID,听起来很有前途,但由于它将是一个DIV,而不一定是单行HTML,因此构建正则表达式将会很困难,并且通常反对使用正则表达式解析HTML。

不建议这样做。


0

@楼主,既然你的需求这么简单,你可以直接使用字符串方法

$f = fopen("file","r");
if($f){
    $s="";
    while( !feof($f) ){
        $i+=1;
        $line = fgets($f,4096);        
        if (stripos($line,'<div id="Alpha">')!==FALSE){
            print "line number: $i\n";
        }
    }
    fclose($f);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接