如何解析HTML/XML并从中提取信息?


当前回答

注意,这个答案推荐了那些已经废弃了10多年的图书馆。

phpQuery和QueryPath在复制流畅的jQuery API方面非常相似。这也是为什么它们是在PHP中正确解析HTML的两种最简单的方法。

QueryPath示例

基本上,您首先从HTML字符串创建一个可查询的DOM树:

 $qp = qp("<html><body><h1>title</h1>..."); // or give filename or URL

结果对象包含HTML文档的完整树表示。可以使用DOM方法遍历它。但常见的方法是使用jQuery中的CSS选择器:

 $qp->find("div.classname")->children()->...;

 foreach ($qp->find("p img") as $img) {
     print qp($img)->attr("src");
 }

大多数情况下,您希望为->find()使用简单的#id和.class或DIV标记选择器。但您也可以使用XPath语句,这有时会更快。另外,典型的jQuery方法,如->children()和->text(),特别是->attr(),简化了提取正确的HTML片段。(并且已经解码了它们的SGML实体。)

 $qp->xpath("//div/p[1]");  // get first paragraph in a div

QueryPath还允许将新标记注入流(->追加),然后输出并美化更新的文档(->写入HTML)。它不仅可以解析格式错误的HTML,还可以解析各种XML方言(带有名称空间),甚至可以从HTML微格式(XFN、vCard)中提取数据。

 $qp->find("a[target=_blank]")->toggleClass("usability-blunder");

.

phpQuery还是QueryPath?

一般来说,QueryPath更适合处理文档。而phpQuery还实现了一些伪AJAX方法(仅HTTP请求),以更接近于jQuery。据称,phpQuery通常比QueryPath更快(因为总体特性较少)。

有关差异的更多信息,请参阅tagbyte.org上的wayback机器上的比较

优势

简单可靠易于使用的替代方案->查找(“a img,a object,div a”)正确的数据取消捕获(与正则表达式grepping相比)

其他回答

我创建了一个名为HTML5DOMDocument的库,可以在https://github.com/ivopetkov/html5-dom-document-php

它还支持查询选择器,我认为这在您的情况下非常有用。下面是一些示例代码:

$dom = new IvoPetkov\HTML5DOMDocument();
$dom->loadHTML('<!DOCTYPE html><html><body><h1>Hello</h1><div class="content">This is some text</div></body></html>');
echo $dom->querySelector('h1')->innerHTML;

处理HTML/XML DOM的方法有很多,其中大多数已经提到。因此,我不会亲自列出这些。

我只想补充一点,我个人更喜欢使用DOM扩展,以及为什么:

iit充分利用了底层C代码的性能优势它是OO PHP(并允许我对其进行子类化)它的级别相当低(这允许我将其用作更高级行为的非臃肿基础)它提供对DOM的每个部分的访问(不像SimpleXml,它忽略了一些鲜为人知的XML特性)它具有用于DOM爬行的语法,与原生Javascript中使用的语法类似。

虽然我怀念为DOMDocument使用CSS选择器的能力,但有一种非常简单和方便的方法可以添加此功能:将DOMDocument子类化,并将类似于querySelectorAll和querySelector的JS方法添加到子类中。

为了解析选择器,我建议使用Symfony框架中的非常简约的CsSelector组件。该组件只是将CSS选择器转换为XPath选择器,然后可以将其输入到DOMX路径中以检索相应的Nodelist。

然后,您可以使用这个(仍然是非常低级的)子类作为更高级类的基础,例如解析非常特定的XML类型或添加更多类似jQuery的行为。

下面的代码直接来自我的DOM查询库,并使用了我描述的技术。

对于HTML分析:

namespace PowerTools;

use \Symfony\Component\CssSelector\CssSelector as CssSelector;

class DOM_Document extends \DOMDocument {
    public function __construct($data = false, $doctype = 'html', $encoding = 'UTF-8', $version = '1.0') {
        parent::__construct($version, $encoding);
        if ($doctype && $doctype === 'html') {
            @$this->loadHTML($data);
        } else {
            @$this->loadXML($data);
        }
    }

    public function querySelectorAll($selector, $contextnode = null) {
        if (isset($this->doctype->name) && $this->doctype->name == 'html') {
            CssSelector::enableHtmlExtension();
        } else {
            CssSelector::disableHtmlExtension();
        }
        $xpath = new \DOMXpath($this);
        return $xpath->query(CssSelector::toXPath($selector, 'descendant::'), $contextnode);
    }

    [...]

    public function loadHTMLFile($filename, $options = 0) {
        $this->loadHTML(file_get_contents($filename), $options);
    }

    public function loadHTML($source, $options = 0) {
        if ($source && $source != '') {
            $data = trim($source);
            $html5 = new HTML5(array('targetDocument' => $this, 'disableHtmlNsInDom' => true));
            $data_start = mb_substr($data, 0, 10);
            if (strpos($data_start, '<!DOCTYPE ') === 0 || strpos($data_start, '<html>') === 0) {
                $html5->loadHTML($data);
            } else {
                @$this->loadHTML('<!DOCTYPE html><html><head><meta charset="' . $encoding . '" /></head><body></body></html>');
                $t = $html5->loadHTMLFragment($data);
                $docbody = $this->getElementsByTagName('body')->item(0);
                while ($t->hasChildNodes()) {
                    $docbody->appendChild($t->firstChild);
                }
            }
        }
    }

    [...]
}

另请参见Symfony的创建者Fabien Potencier关于为Symfony创建CsSelector组件的决定以及如何使用它的CSS选择器解析XML文档。

不使用正则表达式解析HTML有几个原因。但是,如果您完全控制将生成什么HTML,那么可以使用简单的正则表达式。

上面是一个通过正则表达式解析HTML的函数。请注意,此函数非常敏感,要求HTML遵守某些规则,但它在许多场景中都非常有效。如果您想要一个简单的解析器,并且不想安装库,请尝试一下:

function array_combine_($keys, $values) {
    $result = array();
    foreach ($keys as $i => $k) {
        $result[$k][] = $values[$i];
    }
    array_walk($result, create_function('&$v', '$v = (count($v) == 1)? array_pop($v): $v;'));

    return $result;
}

function extract_data($str) {
    return (is_array($str))
        ? array_map('extract_data', $str)
        : ((!preg_match_all('#<([A-Za-z0-9_]*)[^>]*>(.*?)</\1>#s', $str, $matches))
            ? $str
            : array_map(('extract_data'), array_combine_($matches[1], $matches[2])));
}

print_r(extract_data(file_get_contents("http://www.google.com/")));

您可以尝试使用类似HTMLTidy的东西来清理任何“损坏”的HTML,并将HTML转换为XHTML,然后可以使用XML解析器解析。

JSON和来自XML的数组分为三行:

$xml = simplexml_load_string($xml_string);
$json = json_encode($xml);
$array = json_decode($json,TRUE);

大大!