通过类名获取DOM元素

145

我正在使用PHP DOM,并尝试获取DOM节点中具有特定类名的元素。有什么最佳方法可以获取这个子元素吗?

更新:最终我使用了Mechanize for PHP,它更容易使用。


7个回答

179

更新:使用Xpath版本的*[@class~='my-class'] CSS选择器

在回复hakre的评论后,我变得很好奇并查看了Zend_Dom_Query背后的代码。看起来上面的选择器编译为以下xpath(未经测试):

[contains(concat(' ', normalize-space(@class), ' '), ' my-class ')]

因此PHP代码将如下所示:

$dom = new DomDocument();
$dom->load($filePath);
$finder = new DomXPath($dom);
$classname="my-class";
$nodes = $finder->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' $classname ')]");

基本上,我们在这里所做的就是规范化class属性,使得即使只有一个类也被空格限定,并且完整的类列表也被空格限定。然后在该类中添加一个空格以搜索我们正在查找的类。这样我们就能有效地找到并仅查找my-class 的实例。


使用xpath选择器?

$dom = new DomDocument();
$dom->load($filePath);
$finder = new DomXPath($dom);
$classname="my-class";
$nodes = $finder->query("//*[contains(@class, '$classname')]");

如果它只是同一类型的元素,您可以将*替换为特定的标签名称。

如果您需要使用非常复杂的选择器来完成很多工作,我建议使用Zend_Dom_Query,它支持CSS选择器语法(类似于jQuery):

$finder = new Zend_Dom_Query($html);
$classname = 'my-class';
$nodes = $finder->query("*[class~=\"$classname\"]");

1
能否只选择第一个元素?因为它也会找到my-class2类,但是非常不错。 - hakre
我认为你不能没有xpath2... 然而,Zend_Dom_Query的示例确实做到了这一点。如果您不想在项目中使用该组件,则可以查看他们如何将CSS选择器转换为XPath。也许DomXPath支持xpath 2.0-我不确定。 - prodigitalson
1
因为class可以有多个类,例如:<a class="my-link link-button nav-item"> - prodigitalson
2
@prodigitalson:这是不正确的,因为它没有反映出空格,请尝试使用 //*[contains(concat(' ', normalize-space(@class), ' '), ' classname ')](非常有用的信息:CSS选择器和XPath表达式)。 - hakre
2
@babonk:是的,你需要使用containsconcat结合使用...我们只是在讨论在搜索类时填充空格的具体细节,是在两侧都填充还是只填充一侧。不过两种方法都可以。 - prodigitalson
显示剩余4条评论

27
如果您希望获取该类的innerhtml而不包含"zend",您可以使用以下方法:
$dom = new DomDocument();
$dom->load($filePath);
$classname = 'main-article';
$finder = new DomXPath($dom);
$nodes = $finder->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' $classname ')]");
$tmp_dom = new DOMDocument(); 
foreach ($nodes as $node) 
    {
    $tmp_dom->appendChild($tmp_dom->importNode($node,true));
    }
$innerHTML.=trim($tmp_dom->saveHTML()); 
echo $innerHTML;

19

我认为接受的方式更好,但我猜这个方法也可能起作用

function getElementByClass(&$parentNode, $tagName, $className, $offset = 0) {
    $response = false;

    $childNodeList = $parentNode->getElementsByTagName($tagName);
    $tagCount = 0;
    for ($i = 0; $i < $childNodeList->length; $i++) {
        $temp = $childNodeList->item($i);
        if (stripos($temp->getAttribute('class'), $className) !== false) {
            if ($tagCount == $offset) {
                $response = $temp;
                break;
            }

            $tagCount++;
        }

    }

    return $response;
}

2
这个例子在哪里?如果有的话会很好。 - robue-a7119895
太好了。我已经得到了具有类的元素。现在我想编辑元素的内容,比如将子元素附加到包含该类的元素中。如何附加子元素并重新创建整个HTML?请帮忙。这是我所做的。$classResult = getElementByClass($dom, 'div', 'm-signature-pad'); $classResult->nodeValue = ''; $enode = $dom->createElement('img'); $enode->setAttribute('src', $signatureImage); $classResult->appendChild($enode); - Keyur
1
对于使用PHP进行DOM修改,我认为最好使用phpQuery https://github.com/punkave/phpQuery - dav

14

还有另一种方法,不使用DomXPathZend_Dom_Query

基于dav的原始函数,我编写了以下函数,返回匹配参数的父节点的所有子节点。

function getElementsByClass(&$parentNode, $tagName, $className) {
    $nodes=array();

    $childNodeList = $parentNode->getElementsByTagName($tagName);
    for ($i = 0; $i < $childNodeList->length; $i++) {
        $temp = $childNodeList->item($i);
        if (stripos($temp->getAttribute('class'), $className) !== false) {
            $nodes[]=$temp;
        }
    }

    return $nodes;
}

假设你有一个变量$html,其中包含以下HTML代码:

<html>
 <body>
  <div id="content_node">
    <p class="a">I am in the content node.</p>
    <p class="a">I am in the content node.</p>
    <p class="a">I am in the content node.</p>    
  </div>
  <div id="footer_node">
    <p class="a">I am in the footer node.</p>
  </div>
 </body>
</html>

使用getElementsByClass非常简单:

$dom = new DOMDocument('1.0', 'utf-8');
$dom->loadHTML($html);
$content_node=$dom->getElementById("content_node");

$div_a_class_nodes=getElementsByClass($content_node, 'div', 'a');//will contain the three nodes under "content_node".

13

DOMDocument 的输入速度较慢,而 phpQuery 存在严重的内存泄漏问题。我最终使用了:

https://github.com/wasinger/htmlpagedom

选取类的示例:

include 'includes/simple_html_dom.php';

$doc = str_get_html($html);
$href = $doc->find('.lastPage')[0]->href;

我希望这也能帮助到其他人。


如此简单,如此美丽!与 PHP 的本地 DOM 处理相比,其可用性达到了最高水平!请点赞,这是最有用的答案。 - Sliq

5

PHP 的原生 DOM 处理太糟糕了,建议您使用此 HTML 解析包或其他现代 HTML 解析包,可以用几行代码处理它:

安装 paquettg/php-html-parser,使用以下命令:

composer require paquettg/php-html-parser

然后在同一文件夹中创建一个 .php 文件,内容如下:

<?php

// load dependencies via Composer
require __DIR__ . '/vendor/autoload.php';

use PHPHtmlParser\Dom;

$dom = new Dom;
$dom->loadFromUrl("https://example.com");
$links = $dom->find('.classname a');

foreach ($links as $link) {
    echo $link->getAttribute('href');
}

P.S. 您可以在Composer的主页上找到有关如何安装Composer的信息。


4

我倾向于使用Symfony。他们的库非常不错。

使用DomCrawler 组件

示例:

$browser = new HttpBrowser(HttpClient::create());
$crawler = $browser->request('GET', 'example.com');
$class = $crawler->filter('.class')->first();

浏览器工具包和DomCrawler组件之间有相当大的能力差距! - Chris

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